layout_builder_ipe-1.0.x-dev/layout_builder_ipe.module

layout_builder_ipe.module
<?php

/**
 * @file
 * Module file for Layout Builder IPE.
 */

use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
use Drupal\layout_builder\Form\OverridesEntityForm;
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;

/**
 * Implements hook_help().
 */
function layout_builder_ipe_help($route_name, RouteMatchInterface $arg) {
  switch ($route_name) {
    case 'help.page.layout_builder_ipe':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Layout Builder IPE module provides frontend In-Place-Editing (IPE) for Layout Builder, similar to what Panels IPE used to do in Drupal 7.') . '</p>';

      // Add a link to the Drupal.org project.
      $output .= '<p>';
      $output .= t('Visit the <a href=":project_link">Layout Builder IPE</a> on Drupal.org for more information.', [
        ':project_link' => 'https://www.drupal.org/project/layout_builder_ipe',
      ]);
      $output .= '</p>';
      return $output;
  }
}

/**
 * Implements hook_entity_view_alter().
 *
 * This attaches the IPE frontend to entity displays.
 */
function layout_builder_ipe_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  $current_route_name = \Drupal::routeMatch()->getRouteName();
  if (!$entity->id() || !$entity->hasLinkTemplate('canonical') || !$entity->toUrl()->isRouted() || $current_route_name != $entity->toUrl()->getRouteName()) {
    // We can only act on full page views on the canonical url.
    return;
  }

  $layout_builder_ipe = layout_builder_ipe_service();
  if ($entity != $layout_builder_ipe->getContentEntityFromRoute()) {
    // This entity is not the routes main entity, so skip this.
    return;
  }
  $section_storage = $layout_builder_ipe->getSectionStorage($display->getMode());
  if (!$section_storage) {
    return;
  }
  $can_override = $section_storage->getPluginId() == 'defaults' && $section_storage->isOverridable();
  $is_overridden = $section_storage->getPluginId() == 'overrides';
  if (!$can_override && !$is_overridden) {
    return;
  }
  if (!$display->getThirdPartySetting('layout_builder_ipe', 'enabled', FALSE)) {
    // Disabled in the display settings.
    return;
  }

  $layout_builder_ipe->attachIpe($build, $section_storage, $entity);
}

/**
 * Implements hook_preprocess_page().
 *
 * This attaches the IPE frontend to page manager pages.
 */
function layout_builder_ipe_preprocess_page(&$variables) {
  $layout_builder_ipe = layout_builder_ipe_service();
  $entity = $layout_builder_ipe->getEntity();
  if (!$entity) {
    return;
  }
  $section_storage = $layout_builder_ipe->getSectionStorageForEntity($entity);
  if (!$section_storage || !$layout_builder_ipe->access($section_storage, $entity, FALSE)->isAllowed()) {
    return;
  }

  // Check for gin_lb and add additional styling.
  if ($layout_builder_ipe->isGinLb()) {
    $variables['page']['content']['#attached']['library'][] = 'layout_builder_ipe/gin_lb';
    if ($layout_builder_ipe->needsGinLegacy()) {
      $variables['page']['content']['#attached']['library'][] = 'gin/legacy_css';
    }
  }

  if ($section_storage->getPluginId() != 'page_manager') {
    return;
  }

  // Some themes declare their content under an additional key, so let's try
  // that.
  $build = NULL;
  $active_theme = $layout_builder_ipe->getActiveTheme();
  if (array_key_exists($active_theme . '_content', $variables['page']['content'])) {
    $build = &$variables['page']['content'][$active_theme . '_content'];
  }
  elseif (array_key_exists('content', $variables['page']['content'])) {
    $build = &$variables['page']['content']['content'];
  }
  else {
    // Otherwise we fall back to the only item we know.
    $build = &$variables['page']['content'];
  }
  $layout_builder_ipe->attachIpe($build, $section_storage, $entity);
}

/**
 * Implements hook_gin_lb_is_layout_builder_route_alter().
 *
 * Classify IPE routes as Layout Builder so that Gin LB get's attached.
 */
function layout_builder_ipe_gin_lb_is_layout_builder_route_alter(&$gin_lb_is_layout_builder_route, $context) {
  $route_name = \Drupal::routeMatch()->getRouteName() ?? NULL;
  if (!$route_name) {
    return;
  }
  if (strpos($route_name, 'layout_builder_ipe.') === 0) {
    $gin_lb_is_layout_builder_route = TRUE;
  }
  // Also check for POST data. If it has an op, we mark this as not being a
  // layout builder route, because the display will return to the entity view.
  // Without this, the secondary toolbar from Gin LB will stay visible on page
  // manager pages.
  if (strpos($route_name, 'layout_builder_ipe.page_variant.') === 0 && \Drupal::request()->request->has('op')) {
    $gin_lb_is_layout_builder_route = FALSE;
  }
}

/**
 * Implements hook_gin_lb_show_toolbar_alter().
 *
 * Force IPE routes to use the toolbar.
 */
function layout_builder_ipe_gin_lb_show_toolbar_alter(&$gin_lb_show_toolbar) {
  $route_name = \Drupal::routeMatch()->getRouteName() ?? NULL;
  if ($route_name && strpos($route_name, 'layout_builder_ipe.') === 0) {
    $gin_lb_show_toolbar = TRUE;
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function layout_builder_ipe_form_entity_view_display_edit_form_alter(array &$form, FormStateInterface $form_state) {
  /** @var \Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm $form_object */
  $form_object = $form_state->getFormObject();
  /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $display */
  $display = $form_object->getEntity();
  $entity_type = \Drupal::entityTypeManager()->getDefinition($display->getTargetEntityTypeId());
  $form['layout']['layout_builder_ipe'] = [
    '#type' => 'checkbox',
    '#title' => t('Use the Layout Builder IPE frontend'),
    '#description' => t('This only applies if the @type is displayed on its own page', [
      '@type' => strtolower($entity_type->getLabel()),
    ]),
    '#default_value' => $display->getThirdPartySetting('layout_builder_ipe', 'enabled', FALSE),
    '#states' => [
      'disabled' => [
        ':input[name="layout[allow_custom]"]' => ['checked' => FALSE],
      ],
      'invisible' => [
        ':input[name="layout[enabled]"]' => ['checked' => FALSE],
      ],
    ],
    '#access' => \Drupal::currentUser()->hasPermission('administer layout builder ipe'),
  ];
  $form['#entity_builders'][] = 'layout_builder_ipe_form_entity_view_display_form_builder';
}

/**
 * Entity builder for the entity view display form.
 */
function layout_builder_ipe_form_entity_view_display_form_builder(string $entity_type, LayoutEntityDisplayInterface $display, array &$form, FormStateInterface $form_state) {
  $layout = $form_state->getValue(['layout']);
  $layout_builder_enabled = !empty($layout['enabled']) && !empty($layout['allow_custom']) && !empty($layout['layout_builder_ipe']);
  $display->setThirdPartySetting('layout_builder_ipe', 'enabled', $layout_builder_enabled);
}

/**
 * Implements hook_ENTITY_TYPE_update().
 *
 * Clear the route cache, so that the access checks for the layout page can be
 * applied immediately.
 */
function layout_builder_ipe_entity_view_display_update(EntityViewDisplay $entity) {
  Cache::invalidateTags([
    'local_task',
  ]);
  \Drupal::service('router.builder')->rebuild();
}

/**
 * Implements hook_form_alter().
 *
 * Modify forms for IPE enabled entities, so that we can identify these in
 * post submission or pre save hooks, mostly to support the improved entity
 * change detection and the features around concurrent editing.
 *
 * @see layout_builder_ipe_entity_presave()
 */
function layout_builder_ipe_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  $form_object = $form_state->getFormObject();
  if (!$form_object instanceof ContentEntityForm) {
    return;
  }
  $entity = $form_object->getEntity();

  if ($entity->isNew()) {
    // For new entities we don't need to do anything.
    return;
  }

  $layout_builder_ipe = layout_builder_ipe_service();
  if (!$layout_builder_ipe->ipeEnabled($entity)) {
    return;
  }

  // Get the original entity for comparison.
  $original_entity = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());

  // Add a token to identify this form as part of Layout Builder IPE.
  $form['layout_builder_ipe_token'] = [
    '#type' => 'hidden',
    '#value' => $layout_builder_ipe->getEditToken($original_entity),
  ];

  // Check if this is a layout builder form for entity overrides.
  if ($form_object instanceof OverridesEntityForm) {

    // Set some form handlers.
    /** @var \Drupal\layout_builder_ipe\LayoutBuilder\LayoutBuilderSubmitForm $layout_builder_submit_form */
    $layout_builder_submit_form = \Drupal::service('layout_builder_ipe.layout_builder_submit_form');
    $layout_builder_submit_form->setSubmitFormHandler($form, $form_state);

    /** @var \Drupal\layout_builder_ipe\LayoutBuilder\LayoutBuilderConfirmForm $layout_builder_confirm_form */
    $layout_builder_confirm_form = \Drupal::service('layout_builder_ipe.layout_builder_confirm_form');
    $layout_builder_confirm_form->setConfirmButtonHandler($form, $form_state, 'discard_changes');
    $layout_builder_confirm_form->setConfirmButtonHandler($form, $form_state, 'revert');

    $section_storage = $layout_builder_ipe->getSectionStorageForEntity($original_entity);

    // Add a hash for the current layout state. This is used in pre save hooks
    // and validations to determine whether the layout has been changed or not.
    $form['layout_builder_ipe_layout_hash'] = [
      '#type' => 'hidden',
      '#value' => \Drupal::request()->get('layout_builder_ipe_layout_hash') ?? $layout_builder_ipe->hashLayout($section_storage),
    ];
  }
  elseif ($form_object instanceof ContentEntityForm) {
    // Add a hash for the current entity state. This is used in pre save hooks
    // and validations to determine whether the entity has been changed or not.
    $form['layout_builder_ipe_entity_hash'] = [
      '#type' => 'hidden',
      '#value' => \Drupal::request()->get('layout_builder_ipe_entity_hash') ?? $layout_builder_ipe->hashEntity($original_entity),
    ];
  }

}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * This is used to make some modifications to the confirmable forms in the
 * layout builder interface.
 */
function layout_builder_ipe_form_layout_builder_discard_changes_alter(array &$form, FormStateInterface $form_state) {
  /** @var \Drupal\layout_builder_ipe\LayoutBuilder\LayoutBuilderConfirmForm $layout_builder_confirm_form */
  $layout_builder_confirm_form = \Drupal::service('layout_builder_ipe.layout_builder_confirm_form');
  $layout_builder_confirm_form->alterConfirmationForm($form, $form_state, 'layout_builder_discard_changes');
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * This is used to make some modifications to the confirmable forms in the
 * layout builder interface.
 */
function layout_builder_ipe_form_layout_builder_revert_overrides_alter(array &$form, FormStateInterface $form_state) {
  /** @var \Drupal\layout_builder_ipe\LayoutBuilder\LayoutBuilderConfirmForm $layout_builder_confirm_form */
  $layout_builder_confirm_form = \Drupal::service('layout_builder_ipe.layout_builder_confirm_form');
  $layout_builder_confirm_form->alterConfirmationForm($form, $form_state, 'layout_builder_revert_overrides');
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * This is used to make some modifications to the confirmable forms in the
 * layout builder interface.
 */
function layout_builder_ipe_form_layout_builder_add_block_alter(array &$form, FormStateInterface $form_state) {
  // Add our own submit handler.
  $form['#submit'][] = 'layout_builder_ipe_add_block_form_submit';
}

/**
 * Submit handler for the "Add block" form.
 */
function layout_builder_ipe_add_block_form_submit(array &$form, FormStateInterface $form_state) {
  $layout_builder_ui = \Drupal::service('layout_builder_ipe.layout_builder_ui');
  $layout_builder_ui->blockFormSubmit($form, $form_state);
}

/**
 * Implements hook_entity_presave().
 *
 * Reset most entity data besides layouts to it's original values to account
 * for the scenario where user A starts working on the layout of an entity,
 * another user B edits the entity data (e.g. title, specific field values,
 * ...) and saves the entity, and then user A saves it's layout changes. In
 * this case we don't want the changes from user B to be overwritten by the
 * outdated data of user A.
 */
function layout_builder_ipe_entity_presave(EntityInterface $entity) {
  if (!$entity instanceof ContentEntityInterface) {
    return;
  }
  if ($entity->isNew() || !isset($entity->original)) {
    return;
  }
  /** @var  */
  $layout_builder_ipe = layout_builder_ipe_service();
  if (!$layout_builder_ipe->ipeEnabled($entity) || !$layout_builder_ipe->useOverrideEntityChangedConstraint()) {
    // IPE is not enabled, so ignore.
    return;
  }
  if (!$layout_builder_ipe->isLayoutBuilderIpeFormSubmission($entity)) {
    // This entity save operation is not the result of a form submission, so
    // ignore this too. Otherwise programmatic updates to nodes won't be
    // possible anymore.
    return;
  }

  if (\Drupal::request()->get('layout_builder_ipe_layout_hash')) {
    // This is an edit to the layout, se we reset all fields besides layout,
    // changed and owner.
    $exclude_fields = [
      'changed',
      'uid',
      'vid',
      OverridesSectionStorage::FIELD_NAME,
    ];
    $fields = $entity->getFields();
    foreach ($fields as $field) {
      $field_name = $field->getName();
      if (in_array($field_name, $exclude_fields)) {
        continue;
      }
      $entity->$field_name = $entity->original->$field_name;
    }
  }
  else {
    // This is an edit to the node form, se we reset the layout.
    $field_name = OverridesSectionStorage::FIELD_NAME;
    $entity->$field_name = $entity->original->$field_name;
  }
}

/**
 * Implements hook_link_alter().
 *
 * This is used add a position argument to the add block links.
 */
function layout_builder_ipe_link_alter(&$variables) {
  /** @var Drupal\Core\Url $url */
  $url = $variables['url'];

  if (!$url->isRouted() || $url->getRouteName() != 'layout_builder.add_block') {
    return;
  }

  $position = \Drupal::request()->query->get('position');
  if ($position === NULL) {
    return;
  }
  $query = $variables['options']['query'] ?? [];
  $query['position'] = $position;
  $variables['options']['query'] = $query;
}

/**
 * Implements hook_validation_constraint_alter().
 */
function layout_builder_ipe_validation_constraint_alter(array &$definitions) {
  $layout_builder_ipe = layout_builder_ipe_service();
  if (isset($definitions['EntityChanged']) && $layout_builder_ipe->useOverrideEntityChangedConstraint()) {
    $definitions['EntityChanged']['class'] = 'Drupal\layout_builder_ipe\Validation\Constraint\LayoutBuilderEntityChangedConstraint';
  }
}

/**
 * Get the layout builder IPE service class.
 *
 * @return \Drupal\layout_builder_ipe\LayoutBuilderIpeService
 *   The layout builder IPE service.
 */
function layout_builder_ipe_service() {
  return \Drupal::service('layout_builder_ipe');
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc