lb_plus-1.0.x-dev/src/Element/LayoutBuilderPlus.php

src/Element/LayoutBuilderPlus.php
<?php

namespace Drupal\lb_plus\Element;

use Drupal\Core\Url;
use Drupal\Core\Render\Element;
use Drupal\Core\Ajax\AjaxHelperTrait;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\layout_builder\Section;
use Drupal\lb_plus\SectionStorageHandler;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\layout_builder\LayoutBuilderEvents;
use Drupal\layout_builder\Element\LayoutBuilder;
use Drupal\lb_plus\Event\BlockToolIndicatorEvent;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\lb_plus\Event\SectionToolIndicatorEvent;
use Drupal\layout_builder\Event\PrepareLayoutEvent;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\edit_plus_lb\Entity\LayoutBuilderEntityViewDisplay;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage;
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;

/**
 * Defines a render element for the LB+ UI.
 *
 * @RenderElement("layout_builder_plus")
 *
 * @internal
 *   Plugin classes are internal.
 */
class LayoutBuilderPlus extends Element\RenderElementBase implements ContainerFactoryPluginInterface {

  use AjaxHelperTrait;
  use LayoutBuilderContextTrait;
  use LayoutBuilderHighlightTrait;

  protected UuidInterface $uuid;
  protected ?string $nestedStoragePath;
  protected ModuleHandlerInterface $moduleHandler;
  protected ConfigFactoryInterface $configFactory;
  protected SectionStorageInterface $sectionStorage;
  protected EventDispatcherInterface $eventDispatcher;
  protected SectionStorageHandler $sectionStorageHandler;
  protected ElementInfoManagerInterface $elementInfoManager;
  protected ?SectionStorageInterface $layoutBlockSectionStorage;
  protected LayoutTempstoreRepositoryInterface $tempstoreRepository;

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('layout_builder.tempstore_repository'),
      $container->get('lb_plus.section_storage_handler'),
      $container->get('plugin.manager.element_info'),
      $container->get('event_dispatcher'),
      $container->get('module_handler'),
      $container->get('config.factory'),
      $container->get('uuid')
    );
  }

  public function __construct(array $configuration, $plugin_id, $plugin_definition, LayoutTempstoreRepositoryInterface $tempstore_repository, SectionStorageHandler $section_storage_handler, ElementInfoManagerInterface $element_info_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, UuidInterface $uuid) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->sectionStorageHandler = $section_storage_handler;
    $this->elementInfoManager = $element_info_manager;
    $this->tempstoreRepository = $tempstore_repository;
    $this->eventDispatcher = $event_dispatcher;
    $this->moduleHandler = $module_handler;
    $this->configFactory = $config_factory;
    $this->uuid = $uuid;
  }

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    return [
      '#section_storage' => NULL,
      '#pre_render' => [
        [$this, 'preRender'],
      ],
    ];
  }

  /**
   * Pre-render callback: Renders the Layout Builder UI.
   */
  public function preRender($element) {
    if ($element['#section_storage'] instanceof SectionStorageInterface) {
      $this->sectionStorage = $element['#section_storage'];
      $this->nestedStoragePath = $element['#nested_storage_path'] ?? NULL;
      if (!$this->sectionStorage instanceof OverridesSectionStorage) {
        // Use Core's Layout Builder for any section storages we aren't aware of
        // like NavigationSectionStorage.
        $element_definition = $this->elementInfoManager->getDefinition('layout_builder');
        $original_layout_builder_element = LayoutBuilder::create(\Drupal::getContainer(), [], 'layout_builder', $element_definition);
        return $original_layout_builder_element->preRender($element);
      }
      elseif ($this->isLayoutBlock()) {
        $this->layoutBlockSectionStorage = $this->sectionStorageHandler->getCurrentSectionStorage($this->sectionStorage, $this->nestedStoragePath);
      }

      $element['layout_builder'] = $this->layout();

    }
    return $element;
  }

  /**
   * Renders the Layout UI.
   *
   * @return array
   *   A render array.
   */
  protected function layout() {
    $this->prepareLayout($this->currentSectionStorage());

    $output = [];
    if ($this->isAjax()) {
      $output['status_messages'] = [
        '#type' => 'status_messages',
      ];
    }
    $count = 0;
    $sections_count = $this->currentSectionStorage()->count();
    if ($sections_count) {
      // Build the admin controls for each section.
      for ($i = 0; $i < $sections_count; $i++) {
        $output[] = $this->buildAdministrativeSection($this->currentSectionStorage(), $count);
        $count++;
      }
    }
    else {
      // Show a default blank page.
      $output['add_block'] = [
        '#markup' => '<div id="lb-plus-blank-page" class="blank-page-wrapper"><i class="fas fa-th-large" aria-hidden="true"></i><p>Drag and drop a block here to begin creating your layout.</p></div>',
      ];
    }

    // As the Layout Builder UI is typically displayed using the frontend theme,
    // it is not marked as an administrative page at the route level even though
    // it performs an administrative task. Mark this as an administrative page
    // for JavaScript.
    $output['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;

    $output['#attached']['drupalSettings']['LB+'] = [
      'sectionStorageType' => $this->sectionStorage->getStorageType(),
      'sectionStorage' => $this->sectionStorage->getStorageId(),
      'isLayoutBlock' => $this->isLayoutBlock(),
    ];
    if ($this->isLayoutBlock()) {
      $output['#attached']['drupalSettings']['LB+']['nestedStoragePath'] = $this->nestedStoragePath;
      $storage_component_uuid = SectionStorageHandler::decodeNestedStoragePath($this->nestedStoragePath);
      $output['#attributes']['data-nested-storage-uuid'] = end($storage_component_uuid);
      $exit_nested_layout = [
        'exit_nested_layout' => [
          '#markup' => $this->t('<div id="exit-nested-layout" title="Exit nested layout"><div id="exit-nested-layout-x"></div></div>'),
        ],
      ];
      $output = array_merge($exit_nested_layout, $output);
    }
    else {
      $output['#attributes']['id'] = 'layout-builder';
    }
    $output['#type'] = 'container';
    $output['#attributes']['class'][] = 'layout-builder';
    $output['#attributes']['class'][] = 'active';
    // Mark this UI as uncacheable.
    $output['#cache']['max-age'] = 0;
    return $output;
  }

  /**
   * Prepares a layout for use in the UI.
   *
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
   */
  protected function prepareLayout(SectionStorageInterface $section_storage) {
    $event = new PrepareLayoutEvent($section_storage);
    $this->eventDispatcher->dispatch($event, LayoutBuilderEvents::PREPARE_LAYOUT);
  }

  /**
   * Builds the render array for the layout section while editing.
   *
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
   * @param int $section_delta
   *   The delta of the section.
   *
   * @return array
   *   The render array for a given section.
   */
  protected function buildAdministrativeSection(SectionStorageInterface $section_storage, $section_delta) {
    $section = $section_storage->getSection($section_delta);
    // Add a UUID so we can keep track during sorting.
    $section_uuid = $section->getThirdPartySetting('lb_plus', 'uuid');
    if (empty($section_uuid)) {
      $section_uuid = $this->uuid->generate();
      $section->setThirdPartySetting('lb_plus', 'uuid', $section_uuid);
      $this->sectionStorageHandler->updateSectionStorage($this->sectionStorage, $this->nestedStoragePath, $section_storage);
    }

    $layout = $section->getLayout($this->getPopulatedContexts($section_storage));

    $contexts = $this->getPopulatedContexts($section_storage);
    if ($this->isLayoutBlock()) {
      // Make the top level parent entity context available to nested layouts.
      $context_id = $this->sectionStorageHandler->mapContextToParentEntity($this->sectionStorage, 'layout_builder.entity');
      $contexts[$context_id] = $this->getPopulatedContexts($this->sectionStorage)[$context_id];
    }

    $section_render = $section->toRenderArray($contexts, TRUE);
    LayoutBuilderEntityViewDisplay::getSectionAttributes($section_delta, $section_render, $this->sectionStorage);

    $build = [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['lb-plus-section', 'hover', 'layout-builder__section'],
        'id' => $section_uuid,
        'data-layout-delta' => $section_delta,
        'data-nested-storage-path' => $this->nestedStoragePath,
        'data-layout-update-url' => Url::fromRoute('lb_plus.js.move_block', [
          'section_storage_type' => $this->sectionStorage->getStorageType(),
          'section_storage' => $this->sectionStorage->getStorageId(),
        ])->toString(),
        'data-layout-builder-highlight-id' => "section-update-$section_delta",
      ],
      'section' => $section_render,
    ];

    // Let modules gather information for Section Tool Indicators.
    $event = $this->eventDispatcher->dispatch(new SectionToolIndicatorEvent($build, $this->sectionStorage->getStorageType(), $this->sectionStorage->getStorageId(), $section_delta, $section_uuid, $this->nestedStoragePath));
    $build = $event->getBuild();

    $layout_definition = $layout->getPluginDefinition();
    $build['#layout'] = $layout_definition;

    foreach ($layout_definition->getRegions() as $region => $info) {
      $build['section'][$region]['#attributes']['class'][] = 'layout__region';
      $build['section'][$region]['#attributes']['class'][] = 'js-layout-builder-region';
      $build['section'][$region]['#attributes']['region'] = $region;
      if (!empty($build['section'][$region])) {
        foreach (Element::children($build['section'][$region]) as $uuid) {
          $build['section'][$region][$uuid]['#attributes']['class'][] = 'js-layout-builder-block';
          $build['section'][$region][$uuid]['#attributes']['class'][] = 'layout-builder-block';
          $build['section'][$region][$uuid]['#attributes']['data-block-uuid'] = $uuid;
          $build['section'][$region][$uuid]['#attributes']['data-layout-builder-highlight-id'] = $this->blockUpdateHighlightId($uuid);

          [
            $build,
            $is_layout_block,
            $nested_storage_path
          ] = $this->addContextualLinks($region, $section_delta, $uuid, $build, $section);

          // Let modules gather information for Block Tool Indicators.
          $event = $this->eventDispatcher->dispatch(new BlockToolIndicatorEvent($build, $this->sectionStorage->getStorageType(), $this->sectionStorage->getStorageId(), $section_delta, $region, $uuid, $is_layout_block, $is_layout_block ? $nested_storage_path : $this->nestedStoragePath));
          $build = $event->getBuild();
        }
      }
    }
    return [
      'layout-builder__section' => $build,
    ];
  }

  /**
   * Current section storage.
   *
   * @return \Drupal\layout_builder\SectionStorageInterface
   *   The current section storage.
   */
  private function currentSectionStorage() {
    return $this->layoutBlockSectionStorage ?? $this->sectionStorage;
  }

  /**
   * Is layout block.
   *
   * @return bool
   *   Whether this layout builder is a nested layout block.
   */
  private function isLayoutBlock() {
    return !empty($this->nestedStoragePath);
  }

  /**
   * Add contextual links
   *
   * @param int|string $region
   *   The region.
   * @param int $section_delta
   *   The section delta.
   * @param mixed $uuid
   *   The uuid.
   * @param array $build
   *   The administrative section.
   * @param \Drupal\layout_builder\Section $section
   *   The current section.
   *
   * @return array
   *   - The administrative section.
   *   - It layout block.
   *   - Nested storage path.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function addContextualLinks(int|string $region, int $section_delta, mixed $uuid, array $build, Section $section): array {
    $contextual_links = (bool) $this->configFactory->get('lb_plus.settings')->get('contextual_links');
    if ($contextual_links) {
      $build['section'][$region][$uuid]['#contextual_links'] = [
        'layout_builder_block' => [
          'route_parameters' => [
            'section_storage_type' => $this->sectionStorage->getStorageType(),
            'section_storage' => $this->sectionStorage->getStorageId(),
            'nested_storage_path' => $this->nestedStoragePath,
            'region' => $region,
            'delta' => $section_delta,
            'uuid' => $uuid,
          ],
          'metadata' => [
            'operations' => 'move:update:remove:duplicate',
          ],
        ],
      ];
    }
    // Add an edit layout contextual link for layout blocks.
    $nested_storage_path = NULL;
    $is_layout_block = $this->sectionStorageHandler->isLayoutBlock($section->getComponent($uuid)->getPlugin());
    if ($is_layout_block) {
      $nested_storage_path = SectionStorageHandler::encodeNestedStoragePath([
        $section_delta,
        $uuid,
      ]);
      if (!empty($this->nestedStoragePath)) {
        $nested_storage_path = "$this->nestedStoragePath&$nested_storage_path";
      }
      if ($contextual_links) {
        // Edit layout block layout.
        $build['section'][$region][$uuid]['#contextual_links']['lb_plus_layout_block'] = [
          'route_parameters' => [
            'section_storage_type' => $this->sectionStorage->getStorageType(),
            'section_storage' => $this->sectionStorage->getStorageId(),
            'nested_storage_path' => $nested_storage_path,
            'region' => $region,
            'delta' => $section_delta,
            'uuid' => $uuid,
          ],
        ];

      }
    }
    return [$build, $is_layout_block, $nested_storage_path];
  }

}

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

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