Skip to content
layout_builder.module 8.19 KiB
Newer Older
<?php

/**
 * @file
 * Provides hook implementations for Layout Builder.
 */

use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;

/**
 * Implements hook_help().
 */
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>';
    }
    return $output;
  }

  switch ($route_name) {
    case 'help.page.layout_builder':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Layout Builder provides layout building utility.') . '</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>';
      return $output;
  }
}

/**
 * Implements hook_entity_type_alter().
 */
function layout_builder_entity_type_alter(array &$entity_types) {
  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
  foreach ($entity_types as $entity_type) {
    if ($entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasLinkTemplate('canonical') && $entity_type->hasViewBuilderClass()) {
      $entity_type->setLinkTemplate('layout-builder', $entity_type->getLinkTemplate('canonical') . '/layout');
    }
  }
}

/**
 * Removes the Layout Builder field both visually and from the #fields handling.
 *
 * This prevents any interaction with this field. It is rendered directly
 * in layout_builder_entity_view_alter().
 *
 * @internal
 */
function _layout_builder_hide_layout_field(array &$form) {
  unset($form['fields']['layout_builder__layout']);
  $key = array_search('layout_builder__layout', $form['#fields']);
  if ($key !== FALSE) {
    unset($form['#fields'][$key]);
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for \Drupal\field_ui\Form\EntityFormDisplayEditForm.
 */
function layout_builder_form_entity_form_display_edit_form_alter(&$form, FormStateInterface $form_state) {
  _layout_builder_hide_layout_field($form);
}

/**
 * Implements hook_form_FORM_ID_alter() for \Drupal\field_ui\Form\EntityViewDisplayEditForm.
 */
function layout_builder_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state) {
  /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
  $display = $form_state->getFormObject()->getEntity();
  $entity_type = \Drupal::entityTypeManager()->getDefinition($display->getTargetEntityTypeId());

  _layout_builder_hide_layout_field($form);

  // @todo Expand to work for all view modes in
  //   https://www.drupal.org/node/2907413.
  if (!in_array($display->getMode(), ['full', 'default'], TRUE)) {
    return;
  }

  $form['layout'] = [
    '#type' => 'details',
    '#open' => TRUE,
    '#title' => t('Layout options'),
    '#tree' => TRUE,
  ];
  // @todo Unchecking this box is a destructive action, this should be made
  //   clear to the user in https://www.drupal.org/node/2914484.
  $form['layout']['allow_custom'] = [
    '#type' => 'checkbox',
    '#title' => t('Allow each @entity to have its layout customized.', [
      '@entity' => $entity_type->getSingularLabel(),
    ]),
    '#default_value' => $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE),
  ];

  $form['#entity_builders'][] = 'layout_builder_form_entity_view_display_edit_entity_builder';
}

/**
 * Entity builder for layout options on the entity view display form.
 *
 * @see layout_builder_form_entity_view_display_edit_form_alter()
 */
function layout_builder_form_entity_view_display_edit_entity_builder($entity_type_id, EntityViewDisplayInterface $display, &$form, FormStateInterface &$form_state) {
  $new_value = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE);
  $display->setThirdPartySetting('layout_builder', 'allow_custom', $new_value);
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function layout_builder_entity_view_display_presave(EntityViewDisplayInterface $display) {
  $original_value = isset($display->original) ? $display->original->getThirdPartySetting('layout_builder', 'allow_custom', FALSE) : FALSE;
  $new_value = $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE);
  if ($original_value !== $new_value) {
    $entity_type_id = $display->getTargetEntityTypeId();
    $bundle = $display->getTargetBundle();

    if ($new_value) {
      layout_builder_add_layout_section_field($entity_type_id, $bundle);
    }
    elseif ($field = FieldConfig::loadByName($entity_type_id, $bundle, 'layout_builder__layout')) {
      $field->delete();
    }
  }
}

/**
 * Adds a layout section field to a given bundle.
 *
 * @param string $entity_type_id
 *   The entity type ID.
 * @param string $bundle
 *   The bundle.
 * @param string $field_name
 *   (optional) The name for the layout section field. Defaults to
 *   'layout_builder__layout'.
 *
 * @return \Drupal\field\FieldConfigInterface
 *   A layout section field.
 */
function layout_builder_add_layout_section_field($entity_type_id, $bundle, $field_name = 'layout_builder__layout') {
  $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
  if (!$field) {
    $field_storage = FieldStorageConfig::loadByName($entity_type_id, $field_name);
    if (!$field_storage) {
      $field_storage = FieldStorageConfig::create([
        'entity_type' => $entity_type_id,
        'field_name' => $field_name,
        'type' => 'layout_section',
      ]);
      $field_storage->save();
    }

    $field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => $bundle,
      'label' => t('Layout'),
    ]);
    $field->save();
  }
  return $field;
}

/**
 * Implements hook_entity_view_alter().
 */
function layout_builder_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  if ($display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE) && !$entity->layout_builder__layout->isEmpty()) {
    $contexts = \Drupal::service('context.repository')->getAvailableContexts();
    // @todo Use EntityContextDefinition after resolving
    //   https://www.drupal.org/node/2932462.
    $contexts['layout_builder.entity'] = new Context(new ContextDefinition("entity:{$entity->getEntityTypeId()}", new TranslatableMarkup('@entity being viewed', ['@entity' => $entity->getEntityType()->getLabel()])), $entity);
    $sections = $entity->layout_builder__layout->getSections();
    foreach ($sections as $delta => $section) {
      $build['_layout_builder'][$delta] = $section->toRenderArray($contexts);
    // If field layout is active, that is all that needs to be removed.
    if (\Drupal::moduleHandler()->moduleExists('field_layout') && isset($build['_field_layout'])) {
      unset($build['_field_layout']);
      return;
    }

    /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions */
    $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle());
    // Remove all display-configurable fields.
    foreach (array_keys($display->getComponents()) as $name) {
      if ($name !== 'layout_builder__layout' && isset($field_definitions[$name]) && $field_definitions[$name]->isDisplayConfigurable('view')) {
        unset($build[$name]);
      }
    }
  }
}