Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
<?php
/**
* @file
* Provides hook implementations for Layout Builder.
*/
2019-02-13 19:51:49 +00:00
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Cache\CacheableMetadata;
Issue #2957425 by tedbow, johndevman, mpotter, tim.plunkett, hawkeye.twolf, alexpott, Berdir, samuel.mortenson, xjm, kevincrafts, jibran, amateescu, larowlan, twfahey, EclipseGc, sjerdo, japerry, mtodor, phenaproxima, johnzzon, mglaman: Allow the inline creation of non-reusable Custom Blocks in the layout builder
2018-08-25 17:07:16 +00:00
use Drupal\Core\Entity\EntityInterface;
2019-02-13 04:09:13 +00:00
use Drupal\Core\Entity\FieldableEntityInterface;
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
use Drupal\Core\Form\FormStateInterface;
2019-02-13 19:51:49 +00:00
use Drupal\Core\Link;
2019-01-09 21:24:56 +00:00
use Drupal\Core\Render\Element;
2018-01-15 21:13:53 +00:00
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
2018-01-30 22:28:03 +00:00
use Drupal\field\FieldConfigInterface;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage;
2019-02-13 04:09:13 +00:00
use Drupal\layout_builder\Form\DefaultsEntityForm;
2018-01-30 22:28:03 +00:00
use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm;
2019-02-13 04:09:13 +00:00
use Drupal\layout_builder\Form\OverridesEntityForm;
2018-07-23 09:11:33 +00:00
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\layout_builder\Plugin\Block\ExtraFieldBlock;
Issue #2957425 by tedbow, johndevman, mpotter, tim.plunkett, hawkeye.twolf, alexpott, Berdir, samuel.mortenson, xjm, kevincrafts, jibran, amateescu, larowlan, twfahey, EclipseGc, sjerdo, japerry, mtodor, phenaproxima, johnzzon, mglaman: Allow the inline creation of non-reusable Custom Blocks in the layout builder
2018-08-25 17:07:16 +00:00
use Drupal\layout_builder\InlineBlockEntityOperations;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
2018-11-22 11:59:49 +00:00
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
/**
* Implements hook_help().
*/
2018-01-15 21:13:53 +00:00
function layout_builder_help($route_name, RouteMatchInterface $route_match) {
// Add help text to the Layout Builder UI.
if ($route_match->getRouteObject()->getOption('_layout_builder')) {
$output = '<p>' . t('This layout builder tool allows you to configure the layout of the main content area.') . '</p>';
if (\Drupal::currentUser()->hasPermission('administer blocks')) {
$output .= '<p>' . t('To manage other areas of the page, use the <a href="@block-ui">block administration page</a>.', ['@block-ui' => Url::fromRoute('block.admin_display')->toString()]) . '</p>';
}
else {
$output .= '<p>' . t('To manage other areas of the page, use the block administration page.') . '</p>';
}
2019-02-21 17:22:25 +00:00
$output .= '<p>' . t('Forms and links inside the content of the layout builder tool have been disabled.') . '</p>';
2018-01-15 21:13:53 +00:00
return $output;
}
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
switch ($route_name) {
case 'help.page.layout_builder':
$output = '<h3>' . t('About') . '</h3>';
2019-03-05 21:05:44 +00:00
$output .= '<p>' . t('Layout Builder allows you to use layouts to customize how content, custom blocks, and other <a href=":field_help" title="Field module help, with background on content entities">content entities</a> are displayed.', [':field_help' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>';
$output .= '<p>' . t('For more information, see the <a href=":layout-builder-documentation">online documentation for the Layout Builder module</a>.', [':layout-builder-documentation' => 'https://www.drupal.org/docs/8/core/modules/layout-builder']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Default layouts') . '</dt>';
$output .= '<dd>' . t('Layout Builder can be selectively enabled on the "Manage Display" page in the <a href=":field_ui">Field UI</a>. This allows you to control the output of each type of display individually. For example, a "Basic page" might have view modes such as Full and Teaser, with each view mode having different layouts selected.', [':field_ui' => Url::fromRoute('help.page', ['name' => 'field_ui'])->toString()]) . '</dd>';
$output .= '<dt>' . t('Overridden layouts') . '</dt>';
$output .= '<dd>' . t('If enabled, each individual content item can have a custom layout. Once the layout for an individual content item is overridden, changes to the Default layout will no longer affect it. Overridden layouts may be reverted to return to matching and being synchronized to their Default layout.') . '</dd>';
$output .= '</dl>';
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
return $output;
}
}
/**
* Implements hook_entity_type_alter().
*/
function layout_builder_entity_type_alter(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
2018-01-30 22:28:03 +00:00
$entity_types['entity_view_display']
->setClass(LayoutBuilderEntityViewDisplay::class)
->setStorageClass(LayoutBuilderEntityViewDisplayStorage::class)
2019-02-13 04:09:13 +00:00
->setFormClass('layout_builder', DefaultsEntityForm::class)
2018-01-30 22:28:03 +00:00
->setFormClass('edit', LayoutBuilderEntityViewDisplayForm::class);
2019-02-13 04:09:13 +00:00
// Ensure every fieldable entity type has a layout form.
foreach ($entity_types as $entity_type) {
if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
$entity_type->setFormClass('layout_builder', OverridesEntityForm::class);
}
}
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
}
/**
2018-01-30 22:28:03 +00:00
* Implements hook_form_FORM_ID_alter() for \Drupal\field_ui\Form\EntityFormDisplayEditForm.
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
*/
2018-01-30 22:28:03 +00:00
function layout_builder_form_entity_form_display_edit_form_alter(&$form, FormStateInterface $form_state) {
// Hides the Layout Builder field. It is rendered directly in
// \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::buildMultiple().
2018-11-22 11:59:49 +00:00
unset($form['fields'][OverridesSectionStorage::FIELD_NAME]);
$key = array_search(OverridesSectionStorage::FIELD_NAME, $form['#fields']);
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
if ($key !== FALSE) {
unset($form['#fields'][$key]);
}
}
/**
2018-01-30 22:28:03 +00:00
* Implements hook_field_config_insert().
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
*/
2018-01-30 22:28:03 +00:00
function layout_builder_field_config_insert(FieldConfigInterface $field_config) {
// Clear the sample entity for this entity type and bundle.
2018-02-05 03:19:43 +00:00
$sample_entity_generator = \Drupal::service('layout_builder.sample_entity_generator');
$sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
2018-01-30 22:28:03 +00:00
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
}
/**
2018-01-30 22:28:03 +00:00
* Implements hook_field_config_delete().
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
*/
2018-01-30 22:28:03 +00:00
function layout_builder_field_config_delete(FieldConfigInterface $field_config) {
// Clear the sample entity for this entity type and bundle.
2018-02-05 03:19:43 +00:00
$sample_entity_generator = \Drupal::service('layout_builder.sample_entity_generator');
$sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
2018-01-30 22:28:03 +00:00
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
Issue #2905922 by tim.plunkett, tedbow, xjm, EclipseGc, webchick, vijaycs85, larowlan, andrewmacpherson, droplet, Bojhan, mgifford, drpal, phenaproxima, DyanneNova, japerry: Implementation issue for Layout Builder
2017-11-17 19:01:26 +00:00
}
2018-07-23 09:11:33 +00:00
/**
* Implements hook_entity_view_alter().
*
* ExtraFieldBlock block plugins add placeholders for each extra field which is
* configured to be displayed. Those placeholders are replaced by this hook.
* Modules that implement hook_entity_extra_field_info() use their
* implementations of hook_entity_view_alter() to add the rendered output of
* the extra fields they provide, so we cannot get the rendered output of extra
* fields before this point in the view process.
* layout_builder_module_implements_alter() moves this implementation of
* hook_entity_view_alter() to the end of the list.
*
* @see \Drupal\layout_builder\Plugin\Block\ExtraFieldBlock::build()
* @see layout_builder_module_implements_alter()
*/
function layout_builder_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
2019-01-09 21:24:56 +00:00
// Only replace extra fields when Layout Builder has been used to alter the
// build. See \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::buildMultiple().
if (isset($build['_layout_builder']) && !Element::isEmpty($build['_layout_builder'])) {
2018-07-23 09:11:33 +00:00
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
$field_manager = \Drupal::service('entity_field.manager');
$extra_fields = $field_manager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
if (!empty($extra_fields['display'])) {
foreach ($extra_fields['display'] as $field_name => $extra_field) {
// If the extra field is not set replace with an empty array to avoid
// the placeholder text from being rendered.
$replacement = isset($build[$field_name]) ? $build[$field_name] : [];
ExtraFieldBlock::replaceFieldPlaceholder($build, $replacement, $field_name);
// After the rendered field in $build has been copied over to the
// ExtraFieldBlock block we must remove it from its original location or
// else it will be rendered twice.
unset($build[$field_name]);
}
}
}
}
/**
* Implements hook_builder_module_implements_alter().
*/
function layout_builder_module_implements_alter(&$implementations, $hook) {
if ($hook === 'entity_view_alter') {
// Ensure that this module's implementation of hook_entity_view_alter() runs
// last so that other modules that use this hook to render extra fields will
// run before it.
$group = $implementations['layout_builder'];
unset($implementations['layout_builder']);
$implementations['layout_builder'] = $group;
}
}
Issue #2957425 by tedbow, johndevman, mpotter, tim.plunkett, hawkeye.twolf, alexpott, Berdir, samuel.mortenson, xjm, kevincrafts, jibran, amateescu, larowlan, twfahey, EclipseGc, sjerdo, japerry, mtodor, phenaproxima, johnzzon, mglaman: Allow the inline creation of non-reusable Custom Blocks in the layout builder
2018-08-25 17:07:16 +00:00
/**
* Implements hook_entity_presave().
*/
function layout_builder_entity_presave(EntityInterface $entity) {
if (\Drupal::moduleHandler()->moduleExists('block_content')) {
/** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */
$entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class);
$entity_operations->handlePreSave($entity);
}
}
/**
* Implements hook_entity_delete().
*/
function layout_builder_entity_delete(EntityInterface $entity) {
if (\Drupal::moduleHandler()->moduleExists('block_content')) {
/** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */
$entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class);
$entity_operations->handleEntityDelete($entity);
}
}
/**
* Implements hook_cron().
*/
function layout_builder_cron() {
if (\Drupal::moduleHandler()->moduleExists('block_content')) {
/** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */
$entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class);
$entity_operations->removeUnused();
}
}
2019-01-31 22:29:57 +00:00
/**
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
*/
function layout_builder_plugin_filter_block__layout_builder_alter(array &$definitions, array $extra) {
// @todo Restore the page title block in https://www.drupal.org/node/2938129.
unset($definitions['page_title_block']);
}
Issue #2957425 by tedbow, johndevman, mpotter, tim.plunkett, hawkeye.twolf, alexpott, Berdir, samuel.mortenson, xjm, kevincrafts, jibran, amateescu, larowlan, twfahey, EclipseGc, sjerdo, japerry, mtodor, phenaproxima, johnzzon, mglaman: Allow the inline creation of non-reusable Custom Blocks in the layout builder
2018-08-25 17:07:16 +00:00
/**
* Implements hook_plugin_filter_TYPE_alter().
*/
function layout_builder_plugin_filter_block_alter(array &$definitions, array $extra, $consumer) {
// @todo Determine the 'inline_block' blocks should be allowed outside
// of layout_builder https://www.drupal.org/node/2979142.
2018-11-12 20:45:33 +00:00
if ($consumer !== 'layout_builder' || !isset($extra['list']) || $extra['list'] !== 'inline_blocks') {
Issue #2957425 by tedbow, johndevman, mpotter, tim.plunkett, hawkeye.twolf, alexpott, Berdir, samuel.mortenson, xjm, kevincrafts, jibran, amateescu, larowlan, twfahey, EclipseGc, sjerdo, japerry, mtodor, phenaproxima, johnzzon, mglaman: Allow the inline creation of non-reusable Custom Blocks in the layout builder
2018-08-25 17:07:16 +00:00
foreach ($definitions as $id => $definition) {
if ($definition['id'] === 'inline_block') {
unset($definitions[$id]);
}
}
}
}
/**
* Implements hook_ENTITY_TYPE_access().
*/
function layout_builder_block_content_access(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\block_content\BlockContentInterface $entity */
if ($operation === 'view' || $entity->isReusable() || empty(\Drupal::service('inline_block.usage')->getUsage($entity->id()))) {
// If the operation is 'view' or this is reusable block or if this is
// non-reusable that isn't used by this module then don't alter the access.
return AccessResult::neutral();
}
if ($account->hasPermission('configure any layout')) {
return AccessResult::allowed();
}
return AccessResult::forbidden();
}
2018-11-28 11:59:59 +00:00
/**
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
*/
function layout_builder_plugin_filter_block__block_ui_alter(array &$definitions, array $extra) {
foreach ($definitions as $id => $definition) {
2019-01-13 21:47:56 +00:00
// Filter out any layout_builder-provided block that has required context
// definitions.
if ($definition['provider'] === 'layout_builder' && !empty($definition['context_definitions'])) {
/** @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context_definition */
foreach ($definition['context_definitions'] as $context_definition) {
if ($context_definition->isRequired()) {
2018-11-28 11:59:59 +00:00
unset($definitions[$id]);
break;
}
}
}
}
}
2018-12-10 22:18:57 +00:00
2019-01-07 22:00:47 +00:00
/**
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
*/
function layout_builder_plugin_filter_layout__layout_builder_alter(array &$definitions, array $extra) {
// Remove layouts provide by layout discovery that are not needed because of
// layouts provided by this module.
$duplicate_layouts = [
'layout_twocol',
'layout_twocol_bricks',
'layout_threecol_25_50_25',
'layout_threecol_33_34_33',
];
foreach ($duplicate_layouts as $duplicate_layout) {
/** @var \Drupal\Core\Layout\LayoutDefinition[] $definitions */
if (isset($definitions[$duplicate_layout])) {
if ($definitions[$duplicate_layout]->getProvider() === 'layout_discovery') {
unset($definitions[$duplicate_layout]);
}
}
}
// Move the one column layout to the top.
if (isset($definitions['layout_onecol']) && $definitions['layout_onecol']->getProvider() === 'layout_discovery') {
$one_col = $definitions['layout_onecol'];
unset($definitions['layout_onecol']);
$definitions = [
'layout_onecol' => $one_col,
] + $definitions;
}
}
2019-02-13 19:51:49 +00:00
2019-03-01 03:39:47 +00:00
/**
* Implements hook_plugin_filter_TYPE_alter().
*/
function layout_builder_plugin_filter_layout_alter(array &$definitions, array $extra, $consumer) {
// Hide the blank layout plugin from listings.
unset($definitions['layout_builder_blank']);
}
2019-02-13 19:51:49 +00:00
/**
* Implements hook_system_breadcrumb_alter().
*/
function layout_builder_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) {
// Remove the extra 'Manage display' breadcrumb for Layout Builder defaults.
if ($route_match->getRouteObject()->hasOption('_layout_builder') && $route_match->getParameter('section_storage_type') === 'defaults') {
$links = array_filter($breadcrumb->getLinks(), function (Link $link) use ($route_match) {
$entity_type_id = $route_match->getParameter('entity_type_id');
return $link->getUrl()->getRouteName() !== "entity.entity_view_display.$entity_type_id.default";
});
// Links cannot be removed from an existing breadcrumb object. Create a new
// object but carry over the cacheable metadata.
$cacheability = CacheableMetadata::createFromObject($breadcrumb);
$breadcrumb = new Breadcrumb();
$breadcrumb->setLinks($links);
$breadcrumb->addCacheableDependency($cacheability);
}
}