Initial Drupal 11 with DDEV setup
This commit is contained in:
		@ -0,0 +1,82 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Access;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Cache\CacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Routing\Access\AccessInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\Routing\Route;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides an access check for the Layout Builder defaults.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup layout_builder_access
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderAccessCheck implements AccessInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LayoutBuilderAccessCheck class.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The route match.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    protected RouteMatchInterface $route_match,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks routing access to the layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param \Drupal\Core\Session\AccountInterface $account
 | 
			
		||||
   *   The current user.
 | 
			
		||||
   * @param \Symfony\Component\Routing\Route $route
 | 
			
		||||
   *   The route to check against.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Access\AccessResultInterface
 | 
			
		||||
   *   The access result.
 | 
			
		||||
   */
 | 
			
		||||
  public function access(SectionStorageInterface $section_storage, AccountInterface $account, Route $route) {
 | 
			
		||||
    $operation = $route->getRequirement('_layout_builder_access');
 | 
			
		||||
    $access = $section_storage->access($operation, $account, TRUE);
 | 
			
		||||
 | 
			
		||||
    // Check for the global permission unless the section storage checks
 | 
			
		||||
    // permissions itself.
 | 
			
		||||
    if (!$section_storage->getPluginDefinition()->get('handles_permission_check')) {
 | 
			
		||||
      $access = $access->andIf(AccessResult::allowedIfHasPermission($account, 'configure any layout'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Disables access to inline blocks add_block routes if the section storage
 | 
			
		||||
    // opts out.
 | 
			
		||||
    // Check if inline block access should be disabled.
 | 
			
		||||
    if ($operation === 'add_block' && !($section_storage->getPluginDefinition()->get('allow_inline_blocks') ?? TRUE)) {
 | 
			
		||||
      $route_name = $this->route_match->getRouteName();
 | 
			
		||||
      $is_inline_block = str_starts_with((string) $this->route_match->getParameter('plugin_id'), 'inline_block:');
 | 
			
		||||
 | 
			
		||||
      if ($route_name === 'layout_builder.choose_inline_block' || ($route_name === 'layout_builder.add_block' && $is_inline_block)) {
 | 
			
		||||
        $access = $access->andIf(AccessResult::forbidden());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($access instanceof RefinableCacheableDependencyInterface) {
 | 
			
		||||
      // @todo https://www.drupal.org/project/drupal/issues/3446509 Decide if
 | 
			
		||||
      // this logic needs to be changed.
 | 
			
		||||
      if ($section_storage instanceof CacheableDependencyInterface) {
 | 
			
		||||
        $access->addCacheableDependency($section_storage);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $access->setCacheMaxAge(0);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $access;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Access;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessibleInterface;
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Accessible class to allow access for inline blocks in the Layout Builder.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutPreviewAccessAllowed implements AccessibleInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
 | 
			
		||||
    if ($operation === 'view') {
 | 
			
		||||
      return $return_as_object ? AccessResult::allowed() : TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    // The layout builder preview should only need 'view' access.
 | 
			
		||||
    return $return_as_object ? AccessResult::forbidden() : FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,84 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Annotation;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Annotation\Plugin;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a Section Storage type annotation object.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionStorage\SectionStorageManager
 | 
			
		||||
 * @see plugin_api
 | 
			
		||||
 *
 | 
			
		||||
 * @Annotation
 | 
			
		||||
 */
 | 
			
		||||
class SectionStorage extends Plugin {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $id;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin weight, optional (defaults to 0).
 | 
			
		||||
   *
 | 
			
		||||
   * When an entity with layout is rendered, section storage plugins are
 | 
			
		||||
   * checked, in order of their weight, to determine which one should be used
 | 
			
		||||
   * to render the layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  public $weight = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Any required context definitions, optional.
 | 
			
		||||
   *
 | 
			
		||||
   * When an entity with layout is rendered, all section storage plugins which
 | 
			
		||||
   * match a particular set of contexts are checked, in order of their weight,
 | 
			
		||||
   * to determine which plugin should be used to render the layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext()
 | 
			
		||||
   */
 | 
			
		||||
  public $context_definitions = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates that this section storage handles its own permission checking.
 | 
			
		||||
   *
 | 
			
		||||
   * If FALSE, the 'configure any layout' permission will be required during
 | 
			
		||||
   * routing access. If TRUE, Layout Builder will not enforce any access
 | 
			
		||||
   * restrictions for the storage, so the section storage's implementation of
 | 
			
		||||
   * access() must perform the access checking itself. Defaults to FALSE.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\Access\LayoutBuilderAccessCheck
 | 
			
		||||
   */
 | 
			
		||||
  public $handles_permission_check = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates that this section storage allows inline block creation.
 | 
			
		||||
   *
 | 
			
		||||
   * If TRUE, the 'Create content block' link will be shown as part of the
 | 
			
		||||
   * choose block off-canvas dialog. If FALSE, the link will be hidden and will
 | 
			
		||||
   * not be possible to add new inline blocks from the Layout Builder UI.
 | 
			
		||||
   * Defaults to TRUE.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\Controller\ChooseBlockController
 | 
			
		||||
   */
 | 
			
		||||
  public bool $allow_inline_blocks = TRUE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function get() {
 | 
			
		||||
    return new SectionStorageDefinition($this->definition);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,73 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Attribute;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Attribute\Plugin;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a SectionStorage attribute.
 | 
			
		||||
 *
 | 
			
		||||
 * Plugin Namespace: Plugin\SectionStorage
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionStorage\SectionStorageManager
 | 
			
		||||
 * @see plugin_api
 | 
			
		||||
 */
 | 
			
		||||
#[\Attribute(\Attribute::TARGET_CLASS)]
 | 
			
		||||
class SectionStorage extends Plugin {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a SectionStorage attribute.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $id
 | 
			
		||||
   *   The plugin ID.
 | 
			
		||||
   * @param int $weight
 | 
			
		||||
   *   (optional) The plugin weight.
 | 
			
		||||
   *   When an entity with layout is rendered, section storage plugins are
 | 
			
		||||
   *   checked, in order of their weight, to determine which one should be used
 | 
			
		||||
   *   to render the layout.
 | 
			
		||||
   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface[] $context_definitions
 | 
			
		||||
   *   (optional) Any required context definitions.
 | 
			
		||||
   *   When an entity with layout is rendered, all section storage plugins which
 | 
			
		||||
   *   match a particular set of contexts are checked, in order of their weight,
 | 
			
		||||
   *   to determine which plugin should be used to render the layout.
 | 
			
		||||
   *   @see \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext()
 | 
			
		||||
   * @param bool $handles_permission_check
 | 
			
		||||
   *   (optional) Indicates that this section storage handles its own
 | 
			
		||||
   *   permission checking. If FALSE, the 'configure any layout' permission
 | 
			
		||||
   *   will be required during routing access. If TRUE, Layout Builder will
 | 
			
		||||
   *   not enforce any access restrictions for the storage, so the section
 | 
			
		||||
   *   storage's implementation of access() must perform the access checking
 | 
			
		||||
   *   itself.
 | 
			
		||||
   * @param bool $allow_inline_blocks
 | 
			
		||||
   *   (optional) If TRUE, the 'Create content block' link will be shown as
 | 
			
		||||
   *   part of the choose block off-canvas dialog. If FALSE, the link will be
 | 
			
		||||
   *   hidden and will not be possible to add new inline blocks from the Layout
 | 
			
		||||
   *   Builder UI.
 | 
			
		||||
   * @param string|null $deriver
 | 
			
		||||
   *   (optional) The deriver class.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    public readonly string $id,
 | 
			
		||||
    public readonly int $weight = 0,
 | 
			
		||||
    public readonly array $context_definitions = [],
 | 
			
		||||
    public readonly bool $handles_permission_check = FALSE,
 | 
			
		||||
    public readonly bool $allow_inline_blocks = TRUE,
 | 
			
		||||
    public readonly ?string $deriver = NULL,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function get(): SectionStorageDefinition {
 | 
			
		||||
    return new SectionStorageDefinition([
 | 
			
		||||
      'id' => $this->id,
 | 
			
		||||
      'class' => $this->class,
 | 
			
		||||
      'weight' => $this->weight,
 | 
			
		||||
      'context_definitions' => $this->context_definitions,
 | 
			
		||||
      'handles_permission_check' => $this->handles_permission_check,
 | 
			
		||||
      'allow_inline_blocks' => $this->allow_inline_blocks,
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 | 
			
		||||
use Drupal\Core\Block\BlockManagerInterface;
 | 
			
		||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a cache tag invalidator that clears the block cache.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class ExtraFieldBlockCacheTagInvalidator implements CacheTagsInvalidatorInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new ExtraFieldBlockCacheTagInvalidator.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Block\BlockManagerInterface $blockManager
 | 
			
		||||
   *   The block manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(protected BlockManagerInterface $blockManager) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function invalidateTags(array $tags) {
 | 
			
		||||
    if (in_array('entity_field_info', $tags, TRUE)) {
 | 
			
		||||
      if ($this->blockManager instanceof CachedDiscoveryInterface) {
 | 
			
		||||
        $this->blockManager->clearCachedDefinitions();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,98 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Cache\Context\CalculatedCacheContextInterface;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Determines whether Layout Builder is active for a given entity type or not.
 | 
			
		||||
 *
 | 
			
		||||
 * Cache context ID: 'layout_builder_is_active:%entity_type_id', e.g.
 | 
			
		||||
 * 'layout_builder_is_active:node' (to vary by whether custom layout overrides
 | 
			
		||||
 * are allowed for the Node entity specified by the route parameter).
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderIsActiveCacheContext implements CalculatedCacheContextInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current route match.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Routing\RouteMatchInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $routeMatch;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * LayoutBuilderCacheContext constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The current route match.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(RouteMatchInterface $route_match) {
 | 
			
		||||
    $this->routeMatch = $route_match;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getLabel() {
 | 
			
		||||
    return t('Layout Builder');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getContext($entity_type_id = NULL) {
 | 
			
		||||
    if (!$entity_type_id) {
 | 
			
		||||
      throw new \LogicException('Missing entity type ID');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $display = $this->getDisplay($entity_type_id);
 | 
			
		||||
    return ($display && $display->isOverridable()) ? '1' : '0';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCacheableMetadata($entity_type_id = NULL) {
 | 
			
		||||
    if (!$entity_type_id) {
 | 
			
		||||
      throw new \LogicException('Missing entity type ID');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $cacheable_metadata = new CacheableMetadata();
 | 
			
		||||
    if ($display = $this->getDisplay($entity_type_id)) {
 | 
			
		||||
      $cacheable_metadata->addCacheableDependency($display);
 | 
			
		||||
    }
 | 
			
		||||
    return $cacheable_metadata;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the entity view display for a given entity type and view mode.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $entity_type_id
 | 
			
		||||
   *   The entity type ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface|null
 | 
			
		||||
   *   The entity view display, if it exists.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDisplay($entity_type_id) {
 | 
			
		||||
    if ($entity = $this->routeMatch->getParameter($entity_type_id)) {
 | 
			
		||||
      if ($entity instanceof FieldableEntityInterface) {
 | 
			
		||||
        // @todo Expand to work for all view modes in
 | 
			
		||||
        //   https://www.drupal.org/node/2907413.
 | 
			
		||||
        $view_mode = 'full';
 | 
			
		||||
        $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
 | 
			
		||||
        if ($display instanceof LayoutEntityDisplayInterface) {
 | 
			
		||||
          return $display;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\Context\RouteNameCacheContext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Determines if an entity is being viewed in the Layout Builder UI.
 | 
			
		||||
 *
 | 
			
		||||
 * Cache context ID: 'route.name.is_layout_builder_ui'.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderUiCacheContext extends RouteNameCacheContext {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getLabel() {
 | 
			
		||||
    return t('Layout Builder user interface');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getContext() {
 | 
			
		||||
    $route_name = $this->routeMatch->getRouteName();
 | 
			
		||||
    if ($route_name && str_starts_with($route_name, 'layout_builder.')) {
 | 
			
		||||
      return 'is_layout_builder_ui.0';
 | 
			
		||||
    }
 | 
			
		||||
    return 'is_layout_builder_ui.1';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,55 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Context;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a wrapper around getting contexts from a section storage object.
 | 
			
		||||
 */
 | 
			
		||||
trait LayoutBuilderContextTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The context repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $contextRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the context repository service.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
 | 
			
		||||
   *   The context repository service.
 | 
			
		||||
   */
 | 
			
		||||
  protected function contextRepository() {
 | 
			
		||||
    if (!$this->contextRepository) {
 | 
			
		||||
      $this->contextRepository = \Drupal::service('context.repository');
 | 
			
		||||
    }
 | 
			
		||||
    return $this->contextRepository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all populated contexts, both global and section-storage-specific.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
 | 
			
		||||
   *   The array of context objects.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getPopulatedContexts(SectionStorageInterface $section_storage): array {
 | 
			
		||||
    // Get all known globally available contexts IDs.
 | 
			
		||||
    $available_context_ids = array_keys($this->contextRepository()->getAvailableContexts());
 | 
			
		||||
    // Filter to those that are populated.
 | 
			
		||||
    $contexts = array_filter($this->contextRepository()->getRuntimeContexts($available_context_ids), function (ContextInterface $context) {
 | 
			
		||||
      return $context->hasContextValue();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Add in the per-section_storage contexts.
 | 
			
		||||
    $contexts += $section_storage->getContextsDuringPreview();
 | 
			
		||||
    return $contexts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,77 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxHelperTrait;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RedirectResponse;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a controller to add a new section.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Controller classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class AddSectionController implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxHelperTrait;
 | 
			
		||||
  use LayoutRebuildTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * AddSectionController constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds the new section.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section to splice.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID of the layout to add.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\HttpFoundation\Response
 | 
			
		||||
   *   The controller response.
 | 
			
		||||
   */
 | 
			
		||||
  public function build(SectionStorageInterface $section_storage, int $delta, $plugin_id) {
 | 
			
		||||
    $section_storage->insertSection($delta, new Section($plugin_id));
 | 
			
		||||
 | 
			
		||||
    $this->layoutTempstoreRepository->set($section_storage);
 | 
			
		||||
 | 
			
		||||
    if ($this->isAjax()) {
 | 
			
		||||
      return $this->rebuildAndClose($section_storage);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $url = $section_storage->getLayoutBuilderUrl();
 | 
			
		||||
      return new RedirectResponse($url->setAbsolute()->toString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,267 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxHelperTrait;
 | 
			
		||||
use Drupal\Core\Block\BlockManagerInterface;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a controller to choose a new block.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Controller classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class ChooseBlockController implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxHelperTrait;
 | 
			
		||||
  use LayoutBuilderContextTrait;
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Block\BlockManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $blockManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current user.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Session\AccountInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $currentUser;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * ChooseBlockController constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
 | 
			
		||||
   *   The block manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\Core\Session\AccountInterface $current_user
 | 
			
		||||
   *   The current user.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(BlockManagerInterface $block_manager, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user) {
 | 
			
		||||
    $this->blockManager = $block_manager;
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->currentUser = $current_user;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('plugin.manager.block'),
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('current_user')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the UI for choosing a new block.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section to splice.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region the block is going in.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function build(SectionStorageInterface $section_storage, int $delta, $region) {
 | 
			
		||||
    if (($section_storage->getPluginDefinition()->get('allow_inline_blocks') ?? TRUE) && $this->entityTypeManager->hasDefinition('block_content_type') && $types = $this->entityTypeManager->getStorage('block_content_type')->loadMultiple()) {
 | 
			
		||||
      if (count($types) === 1) {
 | 
			
		||||
        $type = reset($types);
 | 
			
		||||
        $plugin_id = 'inline_block:' . $type->id();
 | 
			
		||||
        if ($this->blockManager->hasDefinition($plugin_id)) {
 | 
			
		||||
          $url = Url::fromRoute('layout_builder.add_block', [
 | 
			
		||||
            'section_storage_type' => $section_storage->getStorageType(),
 | 
			
		||||
            'section_storage' => $section_storage->getStorageId(),
 | 
			
		||||
            'delta' => $delta,
 | 
			
		||||
            'region' => $region,
 | 
			
		||||
            'plugin_id' => $plugin_id,
 | 
			
		||||
          ]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $url = Url::fromRoute('layout_builder.choose_inline_block', [
 | 
			
		||||
          'section_storage_type' => $section_storage->getStorageType(),
 | 
			
		||||
          'section_storage' => $section_storage->getStorageId(),
 | 
			
		||||
          'delta' => $delta,
 | 
			
		||||
          'region' => $region,
 | 
			
		||||
        ]);
 | 
			
		||||
      }
 | 
			
		||||
      if (isset($url)) {
 | 
			
		||||
        $build['add_block'] = [
 | 
			
		||||
          '#type' => 'link',
 | 
			
		||||
          '#url' => $url,
 | 
			
		||||
          '#title' => $this->t('Create @entity_type', [
 | 
			
		||||
            '@entity_type' => $this->entityTypeManager->getDefinition('block_content')->getSingularLabel(),
 | 
			
		||||
          ]),
 | 
			
		||||
          '#attributes' => $this->getAjaxAttributes(),
 | 
			
		||||
          '#access' => $this->currentUser->hasPermission('create and edit custom blocks'),
 | 
			
		||||
        ];
 | 
			
		||||
        $build['add_block']['#attributes']['class'][] = 'inline-block-create-button';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $build['filter'] = [
 | 
			
		||||
      '#type' => 'search',
 | 
			
		||||
      '#title' => $this->t('Filter by block name'),
 | 
			
		||||
      '#title_display' => 'invisible',
 | 
			
		||||
      '#size' => 30,
 | 
			
		||||
      '#placeholder' => $this->t('Filter by block name'),
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => ['js-layout-builder-filter'],
 | 
			
		||||
        'title' => $this->t('Enter a part of the block name to filter by.'),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $block_categories['#type'] = 'container';
 | 
			
		||||
    $block_categories['#attributes']['class'][] = 'block-categories';
 | 
			
		||||
    $block_categories['#attributes']['class'][] = 'js-layout-builder-categories';
 | 
			
		||||
    $block_categories['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockAddHighlightId($delta, $region);
 | 
			
		||||
 | 
			
		||||
    $definitions = $this->blockManager->getFilteredDefinitions('layout_builder', $this->getPopulatedContexts($section_storage), [
 | 
			
		||||
      'section_storage' => $section_storage,
 | 
			
		||||
      'delta' => $delta,
 | 
			
		||||
      'region' => $region,
 | 
			
		||||
    ]);
 | 
			
		||||
    $grouped_definitions = $this->blockManager->getGroupedDefinitions($definitions);
 | 
			
		||||
    foreach ($grouped_definitions as $category => $blocks) {
 | 
			
		||||
      $block_categories[$category]['#type'] = 'details';
 | 
			
		||||
      $block_categories[$category]['#attributes']['class'][] = 'js-layout-builder-category';
 | 
			
		||||
      $block_categories[$category]['#open'] = TRUE;
 | 
			
		||||
      $block_categories[$category]['#title'] = $category;
 | 
			
		||||
      $block_categories[$category]['links'] = $this->getBlockLinks($section_storage, $delta, $region, $blocks);
 | 
			
		||||
    }
 | 
			
		||||
    $build['block_categories'] = $block_categories;
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the UI for choosing a new inline block.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section to splice.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region the block is going in.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function inlineBlockList(SectionStorageInterface $section_storage, int $delta, $region) {
 | 
			
		||||
    $definitions = $this->blockManager->getFilteredDefinitions('layout_builder', $this->getPopulatedContexts($section_storage), [
 | 
			
		||||
      'section_storage' => $section_storage,
 | 
			
		||||
      'region' => $region,
 | 
			
		||||
      'list' => 'inline_blocks',
 | 
			
		||||
    ]);
 | 
			
		||||
    $blocks = $this->blockManager->getGroupedDefinitions($definitions);
 | 
			
		||||
    $build = [];
 | 
			
		||||
    $inline_blocks_category = (string) $this->t('Inline blocks');
 | 
			
		||||
    if (isset($blocks[$inline_blocks_category])) {
 | 
			
		||||
      $build['links'] = $this->getBlockLinks($section_storage, $delta, $region, $blocks[$inline_blocks_category]);
 | 
			
		||||
      $build['links']['#attributes']['class'][] = 'inline-block-list';
 | 
			
		||||
      foreach ($build['links']['#links'] as &$link) {
 | 
			
		||||
        $link['attributes']['class'][] = 'inline-block-list__item';
 | 
			
		||||
      }
 | 
			
		||||
      $build['back_button'] = [
 | 
			
		||||
        '#type' => 'link',
 | 
			
		||||
        '#url' => Url::fromRoute('layout_builder.choose_block',
 | 
			
		||||
          [
 | 
			
		||||
            'section_storage_type' => $section_storage->getStorageType(),
 | 
			
		||||
            'section_storage' => $section_storage->getStorageId(),
 | 
			
		||||
            'delta' => $delta,
 | 
			
		||||
            'region' => $region,
 | 
			
		||||
          ]
 | 
			
		||||
        ),
 | 
			
		||||
        '#title' => $this->t('Back'),
 | 
			
		||||
        '#attributes' => $this->getAjaxAttributes(),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    $build['links']['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockAddHighlightId($delta, $region);
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a render array of block links.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section to splice.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region the block is going in.
 | 
			
		||||
   * @param array $blocks
 | 
			
		||||
   *   The information for each block.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The block links render array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getBlockLinks(SectionStorageInterface $section_storage, int $delta, $region, array $blocks) {
 | 
			
		||||
    $links = [];
 | 
			
		||||
    foreach ($blocks as $block_id => $block) {
 | 
			
		||||
      $attributes = $this->getAjaxAttributes();
 | 
			
		||||
      $attributes['class'][] = 'js-layout-builder-block-link';
 | 
			
		||||
      $link = [
 | 
			
		||||
        'title' => $block['admin_label'],
 | 
			
		||||
        'url' => Url::fromRoute('layout_builder.add_block',
 | 
			
		||||
          [
 | 
			
		||||
            'section_storage_type' => $section_storage->getStorageType(),
 | 
			
		||||
            'section_storage' => $section_storage->getStorageId(),
 | 
			
		||||
            'delta' => $delta,
 | 
			
		||||
            'region' => $region,
 | 
			
		||||
            'plugin_id' => $block_id,
 | 
			
		||||
          ]
 | 
			
		||||
        ),
 | 
			
		||||
        'attributes' => $attributes,
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      $links[] = $link;
 | 
			
		||||
    }
 | 
			
		||||
    return [
 | 
			
		||||
      '#theme' => 'links',
 | 
			
		||||
      '#links' => $links,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get dialog attributes if an ajax request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The attributes array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getAjaxAttributes() {
 | 
			
		||||
    if ($this->isAjax()) {
 | 
			
		||||
      return [
 | 
			
		||||
        'class' => ['use-ajax'],
 | 
			
		||||
        'data-dialog-type' => 'dialog',
 | 
			
		||||
        'data-dialog-renderer' => 'off_canvas',
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,111 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxHelperTrait;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\Core\Layout\LayoutPluginManagerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginFormInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a controller to choose a new section.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Controller classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class ChooseSectionController implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxHelperTrait;
 | 
			
		||||
  use LayoutBuilderContextTrait;
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * ChooseSectionController constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_manager
 | 
			
		||||
   *   The layout manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutPluginManagerInterface $layout_manager) {
 | 
			
		||||
    $this->layoutManager = $layout_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('plugin.manager.core.layout')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Choose a layout plugin to add as a section.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section to splice.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function build(SectionStorageInterface $section_storage, int $delta) {
 | 
			
		||||
    $items = [];
 | 
			
		||||
    $definitions = $this->layoutManager->getFilteredDefinitions('layout_builder', $this->getPopulatedContexts($section_storage), ['section_storage' => $section_storage]);
 | 
			
		||||
    foreach ($definitions as $plugin_id => $definition) {
 | 
			
		||||
      $layout = $this->layoutManager->createInstance($plugin_id);
 | 
			
		||||
      $item = [
 | 
			
		||||
        '#type' => 'link',
 | 
			
		||||
        '#title' => [
 | 
			
		||||
          'icon' => $definition->getIcon(60, 80, 1, 3),
 | 
			
		||||
          'label' => [
 | 
			
		||||
            '#type' => 'container',
 | 
			
		||||
            '#children' => $definition->getLabel(),
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
        '#url' => Url::fromRoute(
 | 
			
		||||
          $layout instanceof PluginFormInterface ? 'layout_builder.configure_section' : 'layout_builder.add_section',
 | 
			
		||||
          [
 | 
			
		||||
            'section_storage_type' => $section_storage->getStorageType(),
 | 
			
		||||
            'section_storage' => $section_storage->getStorageId(),
 | 
			
		||||
            'delta' => $delta,
 | 
			
		||||
            'plugin_id' => $plugin_id,
 | 
			
		||||
          ]
 | 
			
		||||
        ),
 | 
			
		||||
      ];
 | 
			
		||||
      if ($this->isAjax()) {
 | 
			
		||||
        $item['#attributes']['class'][] = 'use-ajax';
 | 
			
		||||
        $item['#attributes']['data-dialog-type'][] = 'dialog';
 | 
			
		||||
        $item['#attributes']['data-dialog-renderer'][] = 'off_canvas';
 | 
			
		||||
      }
 | 
			
		||||
      $items[$plugin_id] = $item;
 | 
			
		||||
    }
 | 
			
		||||
    $output['layouts'] = [
 | 
			
		||||
      '#theme' => 'item_list__layouts',
 | 
			
		||||
      '#items' => $items,
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => [
 | 
			
		||||
          'layout-selection',
 | 
			
		||||
        ],
 | 
			
		||||
        'data-layout-builder-target-highlight-id' => $this->sectionAddHighlightId($delta),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Assertion\Inspector;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a controller to provide the Layout Builder admin UI.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Controller classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderController {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides a title callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The title for the layout page.
 | 
			
		||||
   */
 | 
			
		||||
  public function title(SectionStorageInterface $section_storage) {
 | 
			
		||||
    assert(Inspector::assertStringable($section_storage->label()), 'Section storage label is expected to be a string.');
 | 
			
		||||
    return $this->t('Edit layout for %label', ['%label' => $section_storage->label() ?? $section_storage->getStorageType() . ' ' . $section_storage->getStorageId()]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders the Layout UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function layout(SectionStorageInterface $section_storage) {
 | 
			
		||||
    return [
 | 
			
		||||
      '#type' => 'layout_builder',
 | 
			
		||||
      '#section_storage' => $section_storage,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxResponse;
 | 
			
		||||
use Drupal\Core\Ajax\CloseDialogCommand;
 | 
			
		||||
use Drupal\Core\Ajax\ReplaceCommand;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides AJAX responses to rebuild the Layout Builder.
 | 
			
		||||
 */
 | 
			
		||||
trait LayoutRebuildTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rebuilds the layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Ajax\AjaxResponse
 | 
			
		||||
   *   An AJAX response to either rebuild the layout and close the dialog, or
 | 
			
		||||
   *   reload the page.
 | 
			
		||||
   */
 | 
			
		||||
  protected function rebuildAndClose(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $response = $this->rebuildLayout($section_storage);
 | 
			
		||||
    $response->addCommand(new CloseDialogCommand('#drupal-off-canvas'));
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rebuilds the layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Ajax\AjaxResponse
 | 
			
		||||
   *   An AJAX response to either rebuild the layout and close the dialog, or
 | 
			
		||||
   *   reload the page.
 | 
			
		||||
   */
 | 
			
		||||
  protected function rebuildLayout(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $response = new AjaxResponse();
 | 
			
		||||
    $layout = [
 | 
			
		||||
      '#type' => 'layout_builder',
 | 
			
		||||
      '#section_storage' => $section_storage,
 | 
			
		||||
    ];
 | 
			
		||||
    $response->addCommand(new ReplaceCommand('#layout-builder', $layout));
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,91 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a controller to move a block.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Controller classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class MoveBlockController implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  use LayoutRebuildTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * LayoutController constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Moves a block to another region.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta_from
 | 
			
		||||
   *   The delta of the original section.
 | 
			
		||||
   * @param int $delta_to
 | 
			
		||||
   *   The delta of the destination section.
 | 
			
		||||
   * @param string $region_to
 | 
			
		||||
   *   The new region for this block.
 | 
			
		||||
   * @param string $block_uuid
 | 
			
		||||
   *   The UUID for this block.
 | 
			
		||||
   * @param string|null $preceding_block_uuid
 | 
			
		||||
   *   (optional) If provided, the UUID of the block to insert this block after.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Ajax\AjaxResponse
 | 
			
		||||
   *   An AJAX response.
 | 
			
		||||
   */
 | 
			
		||||
  public function build(SectionStorageInterface $section_storage, int $delta_from, int $delta_to, $region_to, $block_uuid, $preceding_block_uuid = NULL) {
 | 
			
		||||
    $section = $section_storage->getSection($delta_from);
 | 
			
		||||
 | 
			
		||||
    $component = $section->getComponent($block_uuid);
 | 
			
		||||
    $section->removeComponent($block_uuid);
 | 
			
		||||
 | 
			
		||||
    // If the block is moving from one section to another, update the original
 | 
			
		||||
    // section and load the new one.
 | 
			
		||||
    if ($delta_from !== $delta_to) {
 | 
			
		||||
      $section = $section_storage->getSection($delta_to);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If a preceding block was specified, insert after that. Otherwise add the
 | 
			
		||||
    // block to the front.
 | 
			
		||||
    $component->setRegion($region_to);
 | 
			
		||||
    if (isset($preceding_block_uuid)) {
 | 
			
		||||
      $section->insertAfterComponent($preceding_block_uuid, $component);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $section->insertComponent(0, $component);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->layoutTempstoreRepository->set($section_storage);
 | 
			
		||||
    return $this->rebuildLayout($section_storage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an interface for an object that stores layout sections for defaults.
 | 
			
		||||
 */
 | 
			
		||||
interface DefaultsSectionStorageInterface extends SectionStorageInterface, ThirdPartySettingsInterface, LayoutBuilderEnabledInterface, LayoutBuilderOverridableInterface {}
 | 
			
		||||
							
								
								
									
										468
									
								
								web/core/modules/layout_builder/src/Element/LayoutBuilder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										468
									
								
								web/core/modules/layout_builder/src/Element/LayoutBuilder.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,468 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Element;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Html;
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\Core\Ajax\AjaxHelperTrait;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginFormInterface;
 | 
			
		||||
use Drupal\Core\Render\Attribute\RenderElement;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\Core\Render\Element\RenderElementBase;
 | 
			
		||||
use Drupal\Core\Security\Attribute\TrustedCallback;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
 | 
			
		||||
use Drupal\layout_builder\Event\PrepareLayoutEvent;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderEvents;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a render element for building the Layout Builder UI.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[RenderElement('layout_builder')]
 | 
			
		||||
class LayoutBuilder extends RenderElementBase implements ContainerFactoryPluginInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxHelperTrait;
 | 
			
		||||
  use LayoutBuilderContextTrait;
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The event dispatcher.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $eventDispatcher;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LayoutBuilder.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param mixed $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
 | 
			
		||||
   *   The event dispatcher service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
    $this->eventDispatcher = $event_dispatcher;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('event_dispatcher')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getInfo() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#section_storage' => NULL,
 | 
			
		||||
      '#pre_render' => [
 | 
			
		||||
        [$this, 'preRender'],
 | 
			
		||||
      ],
 | 
			
		||||
      '#process' => [
 | 
			
		||||
        [static::class, 'layoutBuilderElementGetKeys'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form element #process callback.
 | 
			
		||||
   *
 | 
			
		||||
   * Save the layout builder element array parents as a property on the top form
 | 
			
		||||
   * element, so that they can be used to access the element within the form
 | 
			
		||||
   * render array later.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $element
 | 
			
		||||
   *   The render array for the layout builder element.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   Form state object.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The render array for the complete form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The layout builder element render array after processing.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController
 | 
			
		||||
   */
 | 
			
		||||
  public static function layoutBuilderElementGetKeys(array $element, FormStateInterface $form_state, array &$form): array {
 | 
			
		||||
    $form['#layout_builder_element_keys'] = $element['#array_parents'];
 | 
			
		||||
    $form['#pre_render'][] = [static::class, 'renderLayoutBuilderAfterForm'];
 | 
			
		||||
    $form['#post_render'][] = [static::class, 'addRenderedLayoutBuilder'];
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API #pre_render callback for form containing layout builder element.
 | 
			
		||||
   *
 | 
			
		||||
   * Because the layout builder element can contain components with forms, it
 | 
			
		||||
   * needs to exist outside forms within the DOM, to avoid nested form tags.
 | 
			
		||||
   * The layout builder element is rendered to markup here and saved, and later
 | 
			
		||||
   * the saved markup will be appended after the form markup.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The rendered form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Renders the layout builder element, if it exists, and adds it to the
 | 
			
		||||
   *   form.
 | 
			
		||||
   *
 | 
			
		||||
   * @see ::addRenderedLayoutBuilder()
 | 
			
		||||
   */
 | 
			
		||||
  #[TrustedCallback]
 | 
			
		||||
  public static function renderLayoutBuilderAfterForm(array $form): array {
 | 
			
		||||
    if (isset($form['#layout_builder_element_keys'])) {
 | 
			
		||||
      $layout_builder_element = &NestedArray::getValue($form, $form['#layout_builder_element_keys']);
 | 
			
		||||
      // Save the rendered layout builder HTML to a non-rendering child key.
 | 
			
		||||
      // Since this method is a pre_render callback, it is assumed that it is
 | 
			
		||||
      // called while rendering with an active render context, so that the
 | 
			
		||||
      // cache metadata and attachments bubble correctly.
 | 
			
		||||
      $form['#layout_builder_markup'] = \Drupal::service('renderer')->render($layout_builder_element);
 | 
			
		||||
      // Remove the layout builder child element within form array.
 | 
			
		||||
      $layout_builder_element = [];
 | 
			
		||||
    }
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API #post_render callback that adds layout builder markup to form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $html
 | 
			
		||||
   *   The rendered form.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form render array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The render string with any layout builder markup added.
 | 
			
		||||
   */
 | 
			
		||||
  #[TrustedCallback]
 | 
			
		||||
  public static function addRenderedLayoutBuilder(string $html, array $form): string {
 | 
			
		||||
    if (isset($form['#layout_builder_markup'])) {
 | 
			
		||||
      $html .= $form['#layout_builder_markup'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $html;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Pre-render callback: Renders the Layout Builder UI.
 | 
			
		||||
   */
 | 
			
		||||
  public function preRender($element) {
 | 
			
		||||
    if ($element['#section_storage'] instanceof SectionStorageInterface) {
 | 
			
		||||
      $element['layout_builder'] = $this->layout($element['#section_storage']);
 | 
			
		||||
    }
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders the Layout UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function layout(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $this->prepareLayout($section_storage);
 | 
			
		||||
 | 
			
		||||
    $output = [];
 | 
			
		||||
    if ($this->isAjax()) {
 | 
			
		||||
      $output['status_messages'] = [
 | 
			
		||||
        '#type' => 'status_messages',
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    $count = 0;
 | 
			
		||||
    for ($i = 0; $i < $section_storage->count(); $i++) {
 | 
			
		||||
      $output[] = $this->buildAddSectionLink($section_storage, $count);
 | 
			
		||||
      $output[] = $this->buildAdministrativeSection($section_storage, $count);
 | 
			
		||||
      $count++;
 | 
			
		||||
    }
 | 
			
		||||
    $output[] = $this->buildAddSectionLink($section_storage, $count);
 | 
			
		||||
    $output['#attached']['library'][] = 'layout_builder/drupal.layout_builder';
 | 
			
		||||
    // 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['#type'] = 'container';
 | 
			
		||||
    $output['#attributes']['id'] = 'layout-builder';
 | 
			
		||||
    $output['#attributes']['class'][] = 'layout-builder';
 | 
			
		||||
    $output['#attributes']['class'][] = Html::getClass('layout-builder--' . $section_storage->getPluginId());
 | 
			
		||||
    // 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 a link to add a new section at a given delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section to splice.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array for a link.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildAddSectionLink(SectionStorageInterface $section_storage, $delta) {
 | 
			
		||||
    $storage_type = $section_storage->getStorageType();
 | 
			
		||||
    $storage_id = $section_storage->getStorageId();
 | 
			
		||||
 | 
			
		||||
    // If the delta and the count are the same, it is either the end of the
 | 
			
		||||
    // layout or an empty layout.
 | 
			
		||||
    if ($delta === count($section_storage)) {
 | 
			
		||||
      if ($delta === 0) {
 | 
			
		||||
        $title = $this->t('Add section');
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $title = $this->t('Add section <span class="visually-hidden">at end of layout</span>');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // If the delta and the count are different, it is either the beginning of
 | 
			
		||||
    // the layout or in between two sections.
 | 
			
		||||
    else {
 | 
			
		||||
      if ($delta === 0) {
 | 
			
		||||
        $title = $this->t('Add section <span class="visually-hidden">at start of layout</span>');
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $title = $this->t('Add section <span class="visually-hidden">between @first and @second</span>', ['@first' => $delta, '@second' => $delta + 1]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
      'link' => [
 | 
			
		||||
        '#type' => 'link',
 | 
			
		||||
        '#title' => $title,
 | 
			
		||||
        '#url' => Url::fromRoute('layout_builder.choose_section',
 | 
			
		||||
          [
 | 
			
		||||
            'section_storage_type' => $storage_type,
 | 
			
		||||
            'section_storage' => $storage_id,
 | 
			
		||||
            'delta' => $delta,
 | 
			
		||||
          ],
 | 
			
		||||
          [
 | 
			
		||||
            'attributes' => [
 | 
			
		||||
              'class' => [
 | 
			
		||||
                'use-ajax',
 | 
			
		||||
                'layout-builder__link',
 | 
			
		||||
                'layout-builder__link--add',
 | 
			
		||||
              ],
 | 
			
		||||
              'data-dialog-type' => 'dialog',
 | 
			
		||||
              'data-dialog-renderer' => 'off_canvas',
 | 
			
		||||
            ],
 | 
			
		||||
          ]
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => ['layout-builder__add-section'],
 | 
			
		||||
        'data-layout-builder-highlight-id' => $this->sectionAddHighlightId($delta),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the render array for the layout section while editing.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for a given section.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildAdministrativeSection(SectionStorageInterface $section_storage, $delta) {
 | 
			
		||||
    $storage_type = $section_storage->getStorageType();
 | 
			
		||||
    $storage_id = $section_storage->getStorageId();
 | 
			
		||||
    $section = $section_storage->getSection($delta);
 | 
			
		||||
 | 
			
		||||
    $layout = $section->getLayout($this->getPopulatedContexts($section_storage));
 | 
			
		||||
    $layout_settings = $section->getLayoutSettings();
 | 
			
		||||
    $section_label = !empty($layout_settings['label']) ? $layout_settings['label'] : $this->t('Section @section', ['@section' => $delta + 1]);
 | 
			
		||||
 | 
			
		||||
    $build = $section->toRenderArray($this->getPopulatedContexts($section_storage), TRUE);
 | 
			
		||||
    $layout_definition = $layout->getPluginDefinition();
 | 
			
		||||
 | 
			
		||||
    $region_labels = $layout_definition->getRegionLabels();
 | 
			
		||||
    foreach ($layout_definition->getRegions() as $region => $info) {
 | 
			
		||||
      if (!empty($build[$region])) {
 | 
			
		||||
        foreach (Element::children($build[$region]) as $uuid) {
 | 
			
		||||
          $build[$region][$uuid]['#attributes']['class'][] = 'js-layout-builder-block';
 | 
			
		||||
          $build[$region][$uuid]['#attributes']['class'][] = 'layout-builder-block';
 | 
			
		||||
          $build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
 | 
			
		||||
          $build[$region][$uuid]['#attributes']['data-layout-builder-highlight-id'] = $this->blockUpdateHighlightId($uuid);
 | 
			
		||||
          $build[$region][$uuid]['#contextual_links'] = [
 | 
			
		||||
            'layout_builder_block' => [
 | 
			
		||||
              'route_parameters' => [
 | 
			
		||||
                'section_storage_type' => $storage_type,
 | 
			
		||||
                'section_storage' => $storage_id,
 | 
			
		||||
                'delta' => $delta,
 | 
			
		||||
                'region' => $region,
 | 
			
		||||
                'uuid' => $uuid,
 | 
			
		||||
              ],
 | 
			
		||||
              // Add metadata about the current operations available in
 | 
			
		||||
              // contextual links. This will invalidate the client-side cache of
 | 
			
		||||
              // links that were cached before the 'move' link was added.
 | 
			
		||||
              // @see layout_builder.links.contextual.yml
 | 
			
		||||
              'metadata' => [
 | 
			
		||||
                'operations' => 'move:update:remove',
 | 
			
		||||
              ],
 | 
			
		||||
            ],
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $build[$region]['layout_builder_add_block']['link'] = [
 | 
			
		||||
        '#type' => 'link',
 | 
			
		||||
        // Add one to the current delta since it is zero-indexed.
 | 
			
		||||
        '#title' => $this->t('Add block <span class="visually-hidden">in @section, @region region</span>', ['@section' => $section_label, '@region' => $region_labels[$region]]),
 | 
			
		||||
        '#url' => Url::fromRoute('layout_builder.choose_block',
 | 
			
		||||
          [
 | 
			
		||||
            'section_storage_type' => $storage_type,
 | 
			
		||||
            'section_storage' => $storage_id,
 | 
			
		||||
            'delta' => $delta,
 | 
			
		||||
            'region' => $region,
 | 
			
		||||
          ],
 | 
			
		||||
          [
 | 
			
		||||
            'attributes' => [
 | 
			
		||||
              'class' => [
 | 
			
		||||
                'use-ajax',
 | 
			
		||||
                'layout-builder__link',
 | 
			
		||||
                'layout-builder__link--add',
 | 
			
		||||
              ],
 | 
			
		||||
              'data-dialog-type' => 'dialog',
 | 
			
		||||
              'data-dialog-renderer' => 'off_canvas',
 | 
			
		||||
            ],
 | 
			
		||||
          ]
 | 
			
		||||
        ),
 | 
			
		||||
      ];
 | 
			
		||||
      $build[$region]['layout_builder_add_block']['#type'] = 'container';
 | 
			
		||||
      $build[$region]['layout_builder_add_block']['#attributes'] = [
 | 
			
		||||
        'class' => ['layout-builder__add-block'],
 | 
			
		||||
        'data-layout-builder-highlight-id' => $this->blockAddHighlightId($delta, $region),
 | 
			
		||||
      ];
 | 
			
		||||
      $build[$region]['layout_builder_add_block']['#weight'] = 1000;
 | 
			
		||||
      $build[$region]['#attributes']['data-region'] = $region;
 | 
			
		||||
      $build[$region]['#attributes']['class'][] = 'layout-builder__region';
 | 
			
		||||
      $build[$region]['#attributes']['class'][] = 'js-layout-builder-region';
 | 
			
		||||
      $build[$region]['#attributes']['role'] = 'group';
 | 
			
		||||
      $build[$region]['#attributes']['aria-label'] = $this->t('@region region in @section', [
 | 
			
		||||
        '@region' => $info['label'],
 | 
			
		||||
        '@section' => $section_label,
 | 
			
		||||
      ]);
 | 
			
		||||
 | 
			
		||||
      // Get weights of all children for use by the region label.
 | 
			
		||||
      $weights = array_map(function ($a) {
 | 
			
		||||
        return $a['#weight'] ?? 0;
 | 
			
		||||
      }, $build[$region]);
 | 
			
		||||
 | 
			
		||||
      // The region label is made visible when the move block dialog is open.
 | 
			
		||||
      $build[$region]['region_label'] = [
 | 
			
		||||
        '#type' => 'container',
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          'class' => ['layout__region-info', 'layout-builder__region-label'],
 | 
			
		||||
          // A more detailed version of this information is already read by
 | 
			
		||||
          // screen readers, so this label can be hidden from them.
 | 
			
		||||
          'aria-hidden' => TRUE,
 | 
			
		||||
        ],
 | 
			
		||||
        '#markup' => $this->t('Region: @region', ['@region' => $info['label']]),
 | 
			
		||||
        // Ensures the region label is displayed first.
 | 
			
		||||
        '#weight' => min($weights) - 1,
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $build['#attributes']['data-layout-update-url'] = Url::fromRoute('layout_builder.move_block', [
 | 
			
		||||
      'section_storage_type' => $storage_type,
 | 
			
		||||
      'section_storage' => $storage_id,
 | 
			
		||||
    ])->toString();
 | 
			
		||||
 | 
			
		||||
    $build['#attributes']['data-layout-delta'] = $delta;
 | 
			
		||||
    $build['#attributes']['class'][] = 'layout-builder__layout';
 | 
			
		||||
    $build['#attributes']['data-layout-builder-highlight-id'] = $this->sectionUpdateHighlightId($delta);
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => ['layout-builder__section'],
 | 
			
		||||
        'role' => 'group',
 | 
			
		||||
        'aria-label' => $section_label,
 | 
			
		||||
      ],
 | 
			
		||||
      'remove' => [
 | 
			
		||||
        '#type' => 'link',
 | 
			
		||||
        '#title' => $this->t('Remove @section', ['@section' => $section_label]),
 | 
			
		||||
        '#url' => Url::fromRoute('layout_builder.remove_section', [
 | 
			
		||||
          'section_storage_type' => $storage_type,
 | 
			
		||||
          'section_storage' => $storage_id,
 | 
			
		||||
          'delta' => $delta,
 | 
			
		||||
        ]),
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          'class' => [
 | 
			
		||||
            'use-ajax',
 | 
			
		||||
            'layout-builder__link',
 | 
			
		||||
            'layout-builder__link--remove',
 | 
			
		||||
          ],
 | 
			
		||||
          'data-dialog-type' => 'dialog',
 | 
			
		||||
          'data-dialog-renderer' => 'off_canvas',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      // The section label is added to sections without a "Configure section"
 | 
			
		||||
      // link, and is only visible when the move block dialog is open.
 | 
			
		||||
      'section_label' => [
 | 
			
		||||
        '#markup' => $this->t('<span class="layout-builder__section-label" aria-hidden="true">@section</span>', ['@section' => $section_label]),
 | 
			
		||||
        '#access' => !$layout instanceof PluginFormInterface,
 | 
			
		||||
      ],
 | 
			
		||||
      'configure' => [
 | 
			
		||||
        '#type' => 'link',
 | 
			
		||||
        '#title' => $this->t('Configure @section', ['@section' => $section_label]),
 | 
			
		||||
        '#access' => $layout instanceof PluginFormInterface,
 | 
			
		||||
        '#url' => Url::fromRoute('layout_builder.configure_section', [
 | 
			
		||||
          'section_storage_type' => $storage_type,
 | 
			
		||||
          'section_storage' => $storage_id,
 | 
			
		||||
          'delta' => $delta,
 | 
			
		||||
        ]),
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          'class' => [
 | 
			
		||||
            'use-ajax',
 | 
			
		||||
            'layout-builder__link',
 | 
			
		||||
            'layout-builder__link--configure',
 | 
			
		||||
          ],
 | 
			
		||||
          'data-dialog-type' => 'dialog',
 | 
			
		||||
          'data-dialog-renderer' => 'off_canvas',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'layout-builder__section' => $build,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,535 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Entity;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\ConfigurableInterface;
 | 
			
		||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
 | 
			
		||||
use Drupal\Component\Plugin\PluginBase;
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Config\Action\Attribute\ActionMethod;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
 | 
			
		||||
use Drupal\Core\Entity\EntityStorageInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\Context;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\Context\EntityContext;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\field\Entity\FieldConfig;
 | 
			
		||||
use Drupal\field\Entity\FieldStorageConfig;
 | 
			
		||||
use Drupal\layout_builder\LayoutEntityHelperTrait;
 | 
			
		||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
use Drupal\layout_builder\SectionComponent;
 | 
			
		||||
use Drupal\layout_builder\SectionListTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides an entity view display entity that has a layout.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface {
 | 
			
		||||
 | 
			
		||||
  use LayoutEntityHelperTrait;
 | 
			
		||||
  use SectionListTrait;
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity field manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityFieldManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $values, $entity_type) {
 | 
			
		||||
    // Set $entityFieldManager before calling the parent constructor because the
 | 
			
		||||
    // constructor will call init() which then calls setComponent() which needs
 | 
			
		||||
    // $entityFieldManager.
 | 
			
		||||
    $this->entityFieldManager = \Drupal::service('entity_field.manager');
 | 
			
		||||
    parent::__construct($values, $entity_type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isOverridable() {
 | 
			
		||||
    return $this->isLayoutBuilderEnabled() && $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  #[ActionMethod(adminLabel: new TranslatableMarkup('Toggle overridable layouts'), pluralize: FALSE, name: 'allowLayoutOverrides')]
 | 
			
		||||
  public function setOverridable($overridable = TRUE) {
 | 
			
		||||
    $this->setThirdPartySetting('layout_builder', 'allow_custom', $overridable);
 | 
			
		||||
    // Enable Layout Builder if it's not already enabled and overriding.
 | 
			
		||||
    if ($overridable && !$this->isLayoutBuilderEnabled()) {
 | 
			
		||||
      $this->enableLayoutBuilder();
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isLayoutBuilderEnabled() {
 | 
			
		||||
    // Layout Builder must not be enabled for the '_custom' view mode that is
 | 
			
		||||
    // used for on-the-fly rendering of fields in isolation from the entity.
 | 
			
		||||
    if ($this->isCustomMode()) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    return (bool) $this->getThirdPartySetting('layout_builder', 'enabled');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  #[ActionMethod(adminLabel: new TranslatableMarkup('Enable Layout Builder'), pluralize: FALSE)]
 | 
			
		||||
  public function enableLayoutBuilder() {
 | 
			
		||||
    $this->setThirdPartySetting('layout_builder', 'enabled', TRUE);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  #[ActionMethod(adminLabel: new TranslatableMarkup('Disable Layout Builder'), pluralize: FALSE)]
 | 
			
		||||
  public function disableLayoutBuilder() {
 | 
			
		||||
    $this->setOverridable(FALSE);
 | 
			
		||||
    $this->setThirdPartySetting('layout_builder', 'enabled', FALSE);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSections() {
 | 
			
		||||
    return $this->getThirdPartySetting('layout_builder', 'sections', []);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setSections(array $sections) {
 | 
			
		||||
    // Third-party settings must be completely unset instead of stored as an
 | 
			
		||||
    // empty array.
 | 
			
		||||
    if (!$sections) {
 | 
			
		||||
      $this->unsetThirdPartySetting('layout_builder', 'sections');
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->setThirdPartySetting('layout_builder', 'sections', array_values($sections));
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function preSave(EntityStorageInterface $storage) {
 | 
			
		||||
 | 
			
		||||
    $original_value = $this->getOriginal()?->isOverridable() ?? FALSE;
 | 
			
		||||
    $new_value = $this->isOverridable();
 | 
			
		||||
    if ($original_value !== $new_value) {
 | 
			
		||||
      $entity_type_id = $this->getTargetEntityTypeId();
 | 
			
		||||
      $bundle = $this->getTargetBundle();
 | 
			
		||||
 | 
			
		||||
      if ($new_value) {
 | 
			
		||||
        $this->addSectionField($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $this->removeSectionField($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parent::preSave($storage);
 | 
			
		||||
 | 
			
		||||
    $already_enabled = $this->getOriginal()?->isLayoutBuilderEnabled() ?? FALSE;
 | 
			
		||||
    $set_enabled = $this->isLayoutBuilderEnabled();
 | 
			
		||||
    if ($already_enabled !== $set_enabled) {
 | 
			
		||||
      if ($set_enabled) {
 | 
			
		||||
        // Loop through all existing field-based components and add them as
 | 
			
		||||
        // section-based components.
 | 
			
		||||
        $components = $this->getComponents();
 | 
			
		||||
        // Sort the components by weight.
 | 
			
		||||
        uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
 | 
			
		||||
        foreach ($components as $name => $component) {
 | 
			
		||||
          $this->setComponent($name, $component);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // When being disabled, remove all existing section data.
 | 
			
		||||
        $this->removeAllSections();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save(): int {
 | 
			
		||||
    $return = parent::save();
 | 
			
		||||
    if (!\Drupal::moduleHandler()->moduleExists('layout_builder_expose_all_field_blocks')) {
 | 
			
		||||
      // Invalidate the block cache in order to regenerate field block
 | 
			
		||||
      // definitions.
 | 
			
		||||
      \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
 | 
			
		||||
    }
 | 
			
		||||
    return $return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes a layout section field if it is no longer needed.
 | 
			
		||||
   *
 | 
			
		||||
   * Because the field is shared across all view modes, the field will only be
 | 
			
		||||
   * removed if no other view modes are using it.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $entity_type_id
 | 
			
		||||
   *   The entity type ID.
 | 
			
		||||
   * @param string $bundle
 | 
			
		||||
   *   The bundle.
 | 
			
		||||
   * @param string $field_name
 | 
			
		||||
   *   The name for the layout section field.
 | 
			
		||||
   */
 | 
			
		||||
  protected function removeSectionField($entity_type_id, $bundle, $field_name) {
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $storage */
 | 
			
		||||
    $storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId());
 | 
			
		||||
    $query = $storage->getQuery()
 | 
			
		||||
      ->condition('targetEntityType', $this->getTargetEntityTypeId())
 | 
			
		||||
      ->condition('bundle', $this->getTargetBundle())
 | 
			
		||||
      ->condition('mode', $this->getMode(), '<>')
 | 
			
		||||
      ->condition('third_party_settings.layout_builder.allow_custom', TRUE);
 | 
			
		||||
    $enabled = (bool) $query->count()->execute();
 | 
			
		||||
    if (!$enabled && $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name)) {
 | 
			
		||||
      $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
 | 
			
		||||
   *   The name for the layout section field.
 | 
			
		||||
   */
 | 
			
		||||
  protected function addSectionField($entity_type_id, $bundle, $field_name) {
 | 
			
		||||
    $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',
 | 
			
		||||
          'locked' => TRUE,
 | 
			
		||||
        ]);
 | 
			
		||||
        $field_storage->setTranslatable(FALSE);
 | 
			
		||||
        $field_storage->save();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $field = FieldConfig::create([
 | 
			
		||||
        'field_storage' => $field_storage,
 | 
			
		||||
        'bundle' => $bundle,
 | 
			
		||||
        'label' => $this->t('Layout'),
 | 
			
		||||
      ]);
 | 
			
		||||
      $field->setTranslatable(FALSE);
 | 
			
		||||
      $field->save();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function createCopy($mode) {
 | 
			
		||||
    // Disable Layout Builder and remove any sections copied from the original.
 | 
			
		||||
    return parent::createCopy($mode)
 | 
			
		||||
      ->setSections([])
 | 
			
		||||
      ->disableLayoutBuilder();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDefaultRegion() {
 | 
			
		||||
    if ($this->hasSection(0)) {
 | 
			
		||||
      return $this->getSection(0)->getDefaultRegion();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent::getDefaultRegion();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wraps the context repository service.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
 | 
			
		||||
   *   The context repository service.
 | 
			
		||||
   */
 | 
			
		||||
  protected function contextRepository() {
 | 
			
		||||
    return \Drupal::service('context.repository');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates if this display is using the '_custom' view mode.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if this display is using the '_custom' view mode, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected function isCustomMode() {
 | 
			
		||||
    return $this->getOriginalMode() === static::CUSTOM_MODE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildMultiple(array $entities) {
 | 
			
		||||
    $build_list = parent::buildMultiple($entities);
 | 
			
		||||
 | 
			
		||||
    // Layout Builder can not be enabled for the '_custom' view mode that is
 | 
			
		||||
    // used for on-the-fly rendering of fields in isolation from the entity.
 | 
			
		||||
    if ($this->isCustomMode()) {
 | 
			
		||||
      return $build_list;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($entities as $id => $entity) {
 | 
			
		||||
      $build_list[$id]['_layout_builder'] = $this->buildSections($entity);
 | 
			
		||||
 | 
			
		||||
      // If there are any sections, remove all fields with configurable display
 | 
			
		||||
      // from the existing build. These fields are replicated within sections as
 | 
			
		||||
      // field blocks by ::setComponent().
 | 
			
		||||
      if (!Element::isEmpty($build_list[$id]['_layout_builder'])) {
 | 
			
		||||
        foreach ($build_list[$id] as $name => $build_part) {
 | 
			
		||||
          $field_definition = $this->getFieldDefinition($name);
 | 
			
		||||
          if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) {
 | 
			
		||||
            unset($build_list[$id][$name]);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $build_list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the render array for the sections of a given entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array representing the sections of the entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildSections(FieldableEntityInterface $entity) {
 | 
			
		||||
    $contexts = $this->getContextsForEntity($entity);
 | 
			
		||||
    $label = new TranslatableMarkup('@entity being viewed', [
 | 
			
		||||
      '@entity' => $entity->getEntityType()->getSingularLabel(),
 | 
			
		||||
    ]);
 | 
			
		||||
    $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label);
 | 
			
		||||
 | 
			
		||||
    $cacheability = new CacheableMetadata();
 | 
			
		||||
    $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
 | 
			
		||||
 | 
			
		||||
    $build = [];
 | 
			
		||||
    if ($storage) {
 | 
			
		||||
      foreach ($storage->getSections() as $delta => $section) {
 | 
			
		||||
        $build[$delta] = $section->toRenderArray($contexts);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // The render array is built based on decisions made by SectionStorage
 | 
			
		||||
    // plugins and therefore it needs to depend on the accumulated
 | 
			
		||||
    // cacheability of those decisions.
 | 
			
		||||
    $cacheability->applyTo($build);
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the available contexts for a given entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
 | 
			
		||||
   *   An array of context objects for a given entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getContextsForEntity(FieldableEntityInterface $entity) {
 | 
			
		||||
    $available_context_ids = array_keys($this->contextRepository()->getAvailableContexts());
 | 
			
		||||
    return [
 | 
			
		||||
      'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()),
 | 
			
		||||
      'entity' => EntityContext::fromEntity($entity),
 | 
			
		||||
      'display' => EntityContext::fromEntity($this),
 | 
			
		||||
    ] + $this->contextRepository()->getRuntimeContexts($available_context_ids);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Move this upstream in https://www.drupal.org/node/2939931.
 | 
			
		||||
   */
 | 
			
		||||
  public function label() {
 | 
			
		||||
    $bundle_info = \Drupal::service('entity_type.bundle.info')->getBundleInfo($this->getTargetEntityTypeId());
 | 
			
		||||
    $bundle_label = $bundle_info[$this->getTargetBundle()]['label'];
 | 
			
		||||
    $target_entity_type = $this->entityTypeManager()->getDefinition($this->getTargetEntityTypeId());
 | 
			
		||||
    return new TranslatableMarkup('@bundle @label', ['@bundle' => $bundle_label, '@label' => $target_entity_type->getPluralLabel()]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function calculateDependencies() {
 | 
			
		||||
    parent::calculateDependencies();
 | 
			
		||||
 | 
			
		||||
    foreach ($this->getSections() as $section) {
 | 
			
		||||
      $this->calculatePluginDependencies($section->getLayout());
 | 
			
		||||
      foreach ($section->getComponents() as $component) {
 | 
			
		||||
        $this->calculatePluginDependencies($component->getPlugin());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function onDependencyRemoval(array $dependencies) {
 | 
			
		||||
    $changed = parent::onDependencyRemoval($dependencies);
 | 
			
		||||
 | 
			
		||||
    // Loop through all sections and determine if the removed dependencies are
 | 
			
		||||
    // used by their layout plugins.
 | 
			
		||||
    foreach ($this->getSections() as $delta => $section) {
 | 
			
		||||
      $layout_dependencies = $this->getPluginDependencies($section->getLayout());
 | 
			
		||||
      $layout_removed_dependencies = $this->getPluginRemovedDependencies($layout_dependencies, $dependencies);
 | 
			
		||||
      if ($layout_removed_dependencies) {
 | 
			
		||||
        // @todo Allow the plugins to react to their dependency removal in
 | 
			
		||||
        //   https://www.drupal.org/project/drupal/issues/2579743.
 | 
			
		||||
        $this->removeSection($delta);
 | 
			
		||||
        $changed = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      // If the section is not removed, loop through all components.
 | 
			
		||||
      else {
 | 
			
		||||
        foreach ($section->getComponents() as $uuid => $component) {
 | 
			
		||||
          $plugin_dependencies = $this->getPluginDependencies($component->getPlugin());
 | 
			
		||||
          $component_removed_dependencies = $this->getPluginRemovedDependencies($plugin_dependencies, $dependencies);
 | 
			
		||||
          if ($component_removed_dependencies) {
 | 
			
		||||
            // @todo Allow the plugins to react to their dependency removal in
 | 
			
		||||
            //   https://www.drupal.org/project/drupal/issues/2579743.
 | 
			
		||||
            $section->removeComponent($uuid);
 | 
			
		||||
            $changed = TRUE;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $changed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setComponent($name, array $options = []) {
 | 
			
		||||
    parent::setComponent($name, $options);
 | 
			
		||||
 | 
			
		||||
    // Only continue if Layout Builder is enabled.
 | 
			
		||||
    if (!$this->isLayoutBuilderEnabled()) {
 | 
			
		||||
      return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve the updated options after the parent:: call.
 | 
			
		||||
    $options = $this->content[$name];
 | 
			
		||||
    // Provide backwards compatibility by converting to a section component.
 | 
			
		||||
    $field_definition = $this->getFieldDefinition($name);
 | 
			
		||||
    $extra_fields = $this->entityFieldManager->getExtraFields($this->getTargetEntityTypeId(), $this->getTargetBundle());
 | 
			
		||||
    $is_view_configurable_non_extra_field = $field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type']);
 | 
			
		||||
    if ($is_view_configurable_non_extra_field || isset($extra_fields['display'][$name])) {
 | 
			
		||||
      $configuration = [
 | 
			
		||||
        'label_display' => '0',
 | 
			
		||||
        'context_mapping' => ['entity' => 'layout_builder.entity'],
 | 
			
		||||
      ];
 | 
			
		||||
      if ($is_view_configurable_non_extra_field) {
 | 
			
		||||
        $configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
 | 
			
		||||
        $keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
 | 
			
		||||
        $configuration['formatter'] = array_intersect_key($options, $keys);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $configuration['id'] = 'extra_field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $section = $this->getDefaultSection();
 | 
			
		||||
      $region = $options['region'] ?? $section->getDefaultRegion();
 | 
			
		||||
      $new_component = (new SectionComponent(\Drupal::service('uuid')->generate(), $region, $configuration));
 | 
			
		||||
      $section->appendComponent($new_component);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a default section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Section
 | 
			
		||||
   *   The default section.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDefaultSection() {
 | 
			
		||||
    // If no section exists, append a new one.
 | 
			
		||||
    if (!$this->hasSection(0)) {
 | 
			
		||||
      $this->appendSection(new Section('layout_onecol'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Return the first section.
 | 
			
		||||
    return $this->getSection(0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the section storage manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
 | 
			
		||||
   *   The section storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  private function sectionStorageManager() {
 | 
			
		||||
    return \Drupal::service('plugin.manager.layout_builder.section_storage');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponent($name) {
 | 
			
		||||
    if ($this->isLayoutBuilderEnabled() && $section_component = $this->getSectionComponentForFieldName($name)) {
 | 
			
		||||
      $plugin = $section_component->getPlugin();
 | 
			
		||||
      if ($plugin instanceof ConfigurableInterface) {
 | 
			
		||||
        $configuration = $plugin->getConfiguration();
 | 
			
		||||
        if (isset($configuration['formatter'])) {
 | 
			
		||||
          return $configuration['formatter'];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return parent::getComponent($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the component for a given field name if any.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $field_name
 | 
			
		||||
   *   The field name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionComponent|null
 | 
			
		||||
   *   The section component if it is available.
 | 
			
		||||
   */
 | 
			
		||||
  private function getSectionComponentForFieldName($field_name) {
 | 
			
		||||
    // Loop through every component until the first match is found.
 | 
			
		||||
    foreach ($this->getSections() as $section) {
 | 
			
		||||
      foreach ($section->getComponents() as $component) {
 | 
			
		||||
        $plugin = $component->getPlugin();
 | 
			
		||||
        if ($plugin instanceof DerivativeInspectionInterface && in_array($plugin->getBaseId(), ['field_block', 'extra_field_block'], TRUE)) {
 | 
			
		||||
          // FieldBlock derivative IDs are in the format
 | 
			
		||||
          // [entity_type]:[bundle]:[field].
 | 
			
		||||
          [, , $field_block_field_name] = explode(PluginBase::DERIVATIVE_SEPARATOR, $plugin->getDerivativeId());
 | 
			
		||||
          if ($field_block_field_name === $field_name) {
 | 
			
		||||
            return $component;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Entity;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides storage for entity view display entities that have layouts.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Entity handlers are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderEntityViewDisplayStorage extends ConfigEntityStorage {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function mapToStorageRecord(EntityInterface $entity) {
 | 
			
		||||
    $record = parent::mapToStorageRecord($entity);
 | 
			
		||||
 | 
			
		||||
    if (!empty($record['third_party_settings']['layout_builder']['sections'])) {
 | 
			
		||||
      $record['third_party_settings']['layout_builder']['sections'] = array_map(function (Section $section) {
 | 
			
		||||
        return $section->toArray();
 | 
			
		||||
      }, $record['third_party_settings']['layout_builder']['sections']);
 | 
			
		||||
    }
 | 
			
		||||
    return $record;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function mapFromStorageRecords(array $records) {
 | 
			
		||||
    foreach ($records as &$record) {
 | 
			
		||||
      if (!empty($record['third_party_settings']['layout_builder']['sections'])) {
 | 
			
		||||
        $sections = &$record['third_party_settings']['layout_builder']['sections'];
 | 
			
		||||
        $sections = array_map([Section::class, 'fromArray'], $sections);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return parent::mapFromStorageRecords($records);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,71 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Entity;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\TempStore\SharedTempStoreFactory;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generates a sample entity for use by the Layout Builder.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderSampleEntityGenerator implements SampleEntityGeneratorInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The shared tempstore factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
 | 
			
		||||
   */
 | 
			
		||||
  protected $tempStoreFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * LayoutBuilderSampleEntityGenerator constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
 | 
			
		||||
   *   The tempstore factory.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager) {
 | 
			
		||||
    $this->tempStoreFactory = $temp_store_factory;
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function get($entity_type_id, $bundle_id) {
 | 
			
		||||
    $tempstore = $this->tempStoreFactory->get('layout_builder.sample_entity');
 | 
			
		||||
    if ($entity = $tempstore->get("$entity_type_id.$bundle_id")) {
 | 
			
		||||
      return $entity;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
 | 
			
		||||
    if (!$entity_storage instanceof ContentEntityStorageInterface) {
 | 
			
		||||
      throw new \InvalidArgumentException(sprintf('The "%s" entity storage is not supported', $entity_type_id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity = $entity_storage->createWithSampleValues($bundle_id);
 | 
			
		||||
    // Mark the sample entity as being a preview.
 | 
			
		||||
    $entity->in_preview = TRUE;
 | 
			
		||||
    $tempstore->set("$entity_type_id.$bundle_id", $entity);
 | 
			
		||||
    return $entity;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function delete($entity_type_id, $bundle_id) {
 | 
			
		||||
    $tempstore = $this->tempStoreFactory->get('layout_builder.sample_entity');
 | 
			
		||||
    $tempstore->delete("$entity_type_id.$bundle_id");
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Entity;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\Display\EntityDisplayInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderEnabledInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionListInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderOverridableInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides an interface for entity displays that have layout.
 | 
			
		||||
 */
 | 
			
		||||
interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface, LayoutBuilderEnabledInterface, LayoutBuilderOverridableInterface {}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Entity;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generates a sample entity.
 | 
			
		||||
 */
 | 
			
		||||
interface SampleEntityGeneratorInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a sample entity for a given entity type and bundle.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $entity_type_id
 | 
			
		||||
   *   The entity type ID.
 | 
			
		||||
   * @param string $bundle_id
 | 
			
		||||
   *   The bundle ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\EntityInterface
 | 
			
		||||
   *   An entity.
 | 
			
		||||
   */
 | 
			
		||||
  public function get($entity_type_id, $bundle_id);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes a sample entity for a given entity type and bundle.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $entity_type_id
 | 
			
		||||
   *   The entity type ID.
 | 
			
		||||
   * @param string $bundle_id
 | 
			
		||||
   *   The bundle ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function delete($entity_type_id, $bundle_id);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Event;
 | 
			
		||||
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Drupal\Component\EventDispatcher\Event;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event fired in #pre_render of \Drupal\layout_builder\Element\LayoutBuilder.
 | 
			
		||||
 *
 | 
			
		||||
 * Subscribers to this event can prepare section storage before rendering.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\LayoutBuilderEvents::PREPARE_LAYOUT
 | 
			
		||||
 * @see \Drupal\layout_builder\Element\LayoutBuilder::prepareLayout()
 | 
			
		||||
 */
 | 
			
		||||
class PrepareLayoutEvent extends Event {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new PrepareLayoutEvent.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage preparing the Layout.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSectionStorage(): SectionStorageInterface {
 | 
			
		||||
    return $this->sectionStorage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,138 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Event;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponseTrait;
 | 
			
		||||
use Drupal\Core\Plugin\PreviewAwarePluginInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionComponent;
 | 
			
		||||
use Drupal\Component\EventDispatcher\Event;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event fired when a section component's render array is being built.
 | 
			
		||||
 *
 | 
			
		||||
 * Subscribers to this event should manipulate the cacheability object and the
 | 
			
		||||
 * build array in this event.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAY
 | 
			
		||||
 */
 | 
			
		||||
class SectionComponentBuildRenderArrayEvent extends Event {
 | 
			
		||||
 | 
			
		||||
  use CacheableResponseTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section component whose render array is being built.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionComponent
 | 
			
		||||
   */
 | 
			
		||||
  protected $component;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The available contexts.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Plugin\Context\ContextInterface[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $contexts;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin for the section component being built.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Plugin\PluginInspectionInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $plugin;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether the component is in preview mode or not.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $inPreview;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The render array built by the event subscribers.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $build = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new SectionComponentBuildRenderArrayEvent object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent $component
 | 
			
		||||
   *   The section component whose render array is being built.
 | 
			
		||||
   * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
 | 
			
		||||
   *   The available contexts.
 | 
			
		||||
   * @param bool $in_preview
 | 
			
		||||
   *   (optional) Whether the component is in preview mode or not.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SectionComponent $component, array $contexts, $in_preview = FALSE) {
 | 
			
		||||
    $this->component = $component;
 | 
			
		||||
    $this->contexts = $contexts;
 | 
			
		||||
    $this->plugin = $component->getPlugin($contexts);
 | 
			
		||||
    $this->inPreview = $in_preview;
 | 
			
		||||
 | 
			
		||||
    if ($this->plugin instanceof PreviewAwarePluginInterface) {
 | 
			
		||||
      $this->plugin->setInPreview($in_preview);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the section component whose render array is being built.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionComponent
 | 
			
		||||
   *   The section component whose render array is being built.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponent() {
 | 
			
		||||
    return $this->component;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the available contexts.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array|\Drupal\Core\Plugin\Context\ContextInterface[]
 | 
			
		||||
   *   The available contexts.
 | 
			
		||||
   */
 | 
			
		||||
  public function getContexts() {
 | 
			
		||||
    return $this->contexts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the plugin for the section component being built.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Component\Plugin\PluginInspectionInterface
 | 
			
		||||
   *   The plugin for the section component being built.
 | 
			
		||||
   */
 | 
			
		||||
  public function getPlugin() {
 | 
			
		||||
    return $this->plugin;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determine if the component is in preview mode.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   Whether the component is in preview mode or not.
 | 
			
		||||
   */
 | 
			
		||||
  public function inPreview() {
 | 
			
		||||
    return $this->inPreview;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the render array in its current state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array built by the event subscribers.
 | 
			
		||||
   */
 | 
			
		||||
  public function getBuild() {
 | 
			
		||||
    return $this->build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the render array.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $build
 | 
			
		||||
   *   A render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function setBuild(array $build) {
 | 
			
		||||
    $this->build = $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,179 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\EventSubscriber;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Access\RefinableDependentAccessInterface;
 | 
			
		||||
use Drupal\Core\Block\BlockPluginInterface;
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\Core\Render\PreviewFallbackInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\layout_builder\Access\LayoutPreviewAccessAllowed;
 | 
			
		||||
use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
 | 
			
		||||
use Drupal\layout_builder\Plugin\Block\InlineBlock;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderEvents;
 | 
			
		||||
use Drupal\views\Plugin\Block\ViewsBlock;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Builds render arrays and handles access for all block components.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class BlockComponentRenderArray implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current user.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Session\AccountInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $currentUser;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a BlockComponentRenderArray object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Session\AccountInterface $current_user
 | 
			
		||||
   *   The current user.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(AccountInterface $current_user) {
 | 
			
		||||
    $this->currentUser = $current_user;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    $events[LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAY] = ['onBuildRender', 100];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds render arrays for block plugins and sets it on the event.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent $event
 | 
			
		||||
   *   The section component render event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
 | 
			
		||||
    $block = $event->getPlugin();
 | 
			
		||||
    if (!$block instanceof BlockPluginInterface) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set block access dependency even if we are not checking access on
 | 
			
		||||
    // this level. The block itself may render another
 | 
			
		||||
    // RefinableDependentAccessInterface object and need to pass on this value.
 | 
			
		||||
    if ($block instanceof RefinableDependentAccessInterface) {
 | 
			
		||||
      $contexts = $event->getContexts();
 | 
			
		||||
      if (isset($contexts['layout_builder.entity'])) {
 | 
			
		||||
        if ($entity = $contexts['layout_builder.entity']->getContextValue()) {
 | 
			
		||||
          if ($event->inPreview()) {
 | 
			
		||||
            // If previewing in Layout Builder allow access.
 | 
			
		||||
            $block->setAccessDependency(new LayoutPreviewAccessAllowed());
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            $block->setAccessDependency($entity);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Only check access if the component is not being previewed.
 | 
			
		||||
    if ($event->inPreview()) {
 | 
			
		||||
      $access = AccessResult::allowed()->setCacheMaxAge(0);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $access = $block->access($this->currentUser, TRUE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $event->addCacheableDependency($access);
 | 
			
		||||
    if ($access->isAllowed()) {
 | 
			
		||||
      $event->addCacheableDependency($block);
 | 
			
		||||
 | 
			
		||||
      // @todo Revisit after https://www.drupal.org/node/3027653, as this will
 | 
			
		||||
      //   provide a better way to remove contextual links from Views blocks.
 | 
			
		||||
      //   Currently, doing this requires setting
 | 
			
		||||
      //   \Drupal\views\ViewExecutable::$showAdminLinks() to false before the
 | 
			
		||||
      //   Views block is built.
 | 
			
		||||
      if ($block instanceof ViewsBlock && $event->inPreview()) {
 | 
			
		||||
        $block->getViewExecutable()->setShowAdminLinks(FALSE);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $content = $block->build();
 | 
			
		||||
      // @todo Remove when https://www.drupal.org/node/3164389 is resolved.
 | 
			
		||||
      if (!is_array($content)) {
 | 
			
		||||
        throw new \UnexpectedValueException(sprintf('The block "%s" did not return an array', get_class($block)));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // We don't output the block render data if there are no render elements
 | 
			
		||||
      // found, but we want to capture the cache metadata from the block
 | 
			
		||||
      // regardless.
 | 
			
		||||
      $event->addCacheableDependency(CacheableMetadata::createFromRenderArray($content));
 | 
			
		||||
 | 
			
		||||
      $is_content_empty = Element::isEmpty($content);
 | 
			
		||||
      $is_placeholder_ready = $event->inPreview() && $block instanceof PreviewFallbackInterface;
 | 
			
		||||
      // If the content is empty and no placeholder is available, return.
 | 
			
		||||
      if ($is_content_empty && !$is_placeholder_ready) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $build = [
 | 
			
		||||
        // @todo Move this to BlockBase in https://www.drupal.org/node/2931040.
 | 
			
		||||
        '#theme' => 'block',
 | 
			
		||||
        '#configuration' => $block->getConfiguration(),
 | 
			
		||||
        '#plugin_id' => $block->getPluginId(),
 | 
			
		||||
        '#base_plugin_id' => $block->getBaseId(),
 | 
			
		||||
        '#derivative_plugin_id' => $block->getDerivativeId(),
 | 
			
		||||
        '#in_preview' => $event->inPreview(),
 | 
			
		||||
        '#weight' => $event->getComponent()->getWeight(),
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      // Place the $content returned by the block plugin into a 'content' child
 | 
			
		||||
      // element, as a way to allow the plugin to have complete control of its
 | 
			
		||||
      // properties and rendering (for instance, its own #theme) without
 | 
			
		||||
      // conflicting with the properties used above, or alternate ones used by
 | 
			
		||||
      // alternate block rendering approaches in contributed modules. However,
 | 
			
		||||
      // the use of a child element is an implementation detail of this
 | 
			
		||||
      // particular block rendering approach. Semantically, the content returned
 | 
			
		||||
      // by the block plugin, and in particular, attributes and contextual links
 | 
			
		||||
      // are information that belong to the entire block. Therefore, we must
 | 
			
		||||
      // move these properties from $content and merge them into the top-level
 | 
			
		||||
      // element.
 | 
			
		||||
      if (isset($content['#attributes'])) {
 | 
			
		||||
        $build['#attributes'] = $content['#attributes'];
 | 
			
		||||
        unset($content['#attributes']);
 | 
			
		||||
      }
 | 
			
		||||
      // Hide contextual links for inline blocks until the UX issues surrounding
 | 
			
		||||
      // editing them directly are resolved.
 | 
			
		||||
      // @see https://www.drupal.org/project/drupal/issues/3075308
 | 
			
		||||
      if (!$block instanceof InlineBlock && !empty($content['#contextual_links'])) {
 | 
			
		||||
        $build['#contextual_links'] = $content['#contextual_links'];
 | 
			
		||||
      }
 | 
			
		||||
      $build['content'] = $content;
 | 
			
		||||
 | 
			
		||||
      if ($event->inPreview()) {
 | 
			
		||||
        if ($block instanceof PreviewFallbackInterface) {
 | 
			
		||||
          $preview_fallback_string = $block->getPreviewFallbackString();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $preview_fallback_string = $this->t('"@block" block', ['@block' => $block->label()]);
 | 
			
		||||
        }
 | 
			
		||||
        // @todo Use new label methods so
 | 
			
		||||
        //   data-layout-content-preview-placeholder-label doesn't have to use
 | 
			
		||||
        //   preview fallback in https://www.drupal.org/node/2025649.
 | 
			
		||||
        $build['#attributes']['data-layout-content-preview-placeholder-label'] = $preview_fallback_string;
 | 
			
		||||
 | 
			
		||||
        if ($is_content_empty && $is_placeholder_ready) {
 | 
			
		||||
          $build['content']['#markup'] = $this->t('Placeholder for the @preview_fallback', ['@preview_fallback' => $block->getPreviewFallbackString()]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $event->setBuild($build);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,88 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\EventSubscriber;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Messenger\MessengerInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\layout_builder\Event\PrepareLayoutEvent;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderEvents;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An event subscriber to prepare section storage.
 | 
			
		||||
 *
 | 
			
		||||
 * Section storage works via the
 | 
			
		||||
 * \Drupal\layout_builder\Event\PrepareLayoutEvent.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Event\PrepareLayoutEvent
 | 
			
		||||
 * @see \Drupal\layout_builder\Element\LayoutBuilder::prepareLayout()
 | 
			
		||||
 */
 | 
			
		||||
class PrepareLayout implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The messenger service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Messenger\MessengerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $messenger;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new PrepareLayout.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The tempstore repository.
 | 
			
		||||
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
 | 
			
		||||
   *   The messenger service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
    $this->messenger = $messenger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    $events[LayoutBuilderEvents::PREPARE_LAYOUT][] = ['onPrepareLayout', 10];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepares a layout for use in the UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Event\PrepareLayoutEvent $event
 | 
			
		||||
   *   The prepare layout event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onPrepareLayout(PrepareLayoutEvent $event) {
 | 
			
		||||
    $section_storage = $event->getSectionStorage();
 | 
			
		||||
 | 
			
		||||
    // If the layout has pending changes, add a warning.
 | 
			
		||||
    if ($this->layoutTempstoreRepository->has($section_storage)) {
 | 
			
		||||
      $this->messenger->addWarning($this->t('You have unsaved changes.'));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // If the layout is an override that has not yet been overridden, copy the
 | 
			
		||||
      // sections from the corresponding default.
 | 
			
		||||
      if ($section_storage instanceof OverridesSectionStorageInterface && !$section_storage->isOverridden()) {
 | 
			
		||||
        $sections = $section_storage->getDefaultSectionStorage()->getSections();
 | 
			
		||||
        foreach ($sections as $section) {
 | 
			
		||||
          $section_storage->appendSection($section);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // Add storage to tempstore regardless of what the storage is.
 | 
			
		||||
      $this->layoutTempstoreRepository->set($section_storage);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,160 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\EventSubscriber;
 | 
			
		||||
 | 
			
		||||
use Drupal\block_content\BlockContentEvents;
 | 
			
		||||
use Drupal\block_content\BlockContentInterface;
 | 
			
		||||
use Drupal\block_content\Event\BlockContentGetDependencyEvent;
 | 
			
		||||
use Drupal\Core\Database\Connection;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\layout_builder\InlineBlockUsageInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutEntityHelperTrait;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An event subscriber that returns an access dependency for inline blocks.
 | 
			
		||||
 *
 | 
			
		||||
 * When used within the layout builder the access dependency for inline blocks
 | 
			
		||||
 * will be explicitly set but if access is evaluated outside of the layout
 | 
			
		||||
 * builder then the dependency may not have been set.
 | 
			
		||||
 *
 | 
			
		||||
 * A known example of when the access dependency will not have been set is when
 | 
			
		||||
 * determining 'view' or 'download' access to a file entity that is attached
 | 
			
		||||
 * to a content block via a field that is using the private file system. The
 | 
			
		||||
 * file access handler will evaluate access on the content block without setting
 | 
			
		||||
 * the dependency.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\file\FileAccessControlHandler::checkAccess()
 | 
			
		||||
 * @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
 | 
			
		||||
 */
 | 
			
		||||
class SetInlineBlockDependency implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  use LayoutEntityHelperTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The database connection.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Database\Connection
 | 
			
		||||
   */
 | 
			
		||||
  protected $database;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The inline block usage service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\InlineBlockUsageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $usage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs SetInlineBlockDependency object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\Core\Database\Connection $database
 | 
			
		||||
   *   The database connection.
 | 
			
		||||
   * @param \Drupal\layout_builder\InlineBlockUsageInterface $usage
 | 
			
		||||
   *   The inline block usage service.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
 | 
			
		||||
   *   The section storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, InlineBlockUsageInterface $usage, SectionStorageManagerInterface $section_storage_manager) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->database = $database;
 | 
			
		||||
    $this->usage = $usage;
 | 
			
		||||
    $this->sectionStorageManager = $section_storage_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      BlockContentEvents::BLOCK_CONTENT_GET_DEPENDENCY => 'onGetDependency',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handles the BlockContentEvents::INLINE_BLOCK_GET_DEPENDENCY event.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\block_content\Event\BlockContentGetDependencyEvent $event
 | 
			
		||||
   *   The event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onGetDependency(BlockContentGetDependencyEvent $event) {
 | 
			
		||||
    if ($dependency = $this->getInlineBlockDependency($event->getBlockContentEntity())) {
 | 
			
		||||
      $event->setAccessDependency($dependency);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the access dependency of an inline block.
 | 
			
		||||
   *
 | 
			
		||||
   * If the block is used in an entity that entity will be returned as the
 | 
			
		||||
   * dependency.
 | 
			
		||||
   *
 | 
			
		||||
   * For revisionable entities the entity will only be returned if it is used in
 | 
			
		||||
   * the latest revision of the entity. For inline blocks that are not used in
 | 
			
		||||
   * the latest revision but are used in a previous revision the entity will not
 | 
			
		||||
   * be returned because calling
 | 
			
		||||
   * \Drupal\Core\Access\AccessibleInterface::access() will only check access on
 | 
			
		||||
   * the latest revision. Therefore if the previous revision of the entity was
 | 
			
		||||
   * returned as the dependency access would be granted to inline block
 | 
			
		||||
   * regardless of whether the user has access to the revision in which the
 | 
			
		||||
   * inline block was used.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\block_content\BlockContentInterface $block_content
 | 
			
		||||
   *   The block content entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\EntityInterface|null
 | 
			
		||||
   *   Returns the layout dependency.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
 | 
			
		||||
   * @see \Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray::onBuildRender()
 | 
			
		||||
   */
 | 
			
		||||
  protected function getInlineBlockDependency(BlockContentInterface $block_content) {
 | 
			
		||||
    $layout_entity_info = $this->usage->getUsage($block_content->id());
 | 
			
		||||
    if (empty($layout_entity_info)) {
 | 
			
		||||
      // If the block does not have usage information then we cannot set a
 | 
			
		||||
      // dependency. It may be used by another module besides layout builder.
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $layout_entity_storage = $this->entityTypeManager->getStorage($layout_entity_info->layout_entity_type);
 | 
			
		||||
    $layout_entity = $layout_entity_storage->load($layout_entity_info->layout_entity_id);
 | 
			
		||||
    if ($this->isLayoutCompatibleEntity($layout_entity)) {
 | 
			
		||||
      if ($this->isBlockRevisionUsedInEntity($layout_entity, $block_content)) {
 | 
			
		||||
        return $layout_entity;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if a block content revision is used in an entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $layout_entity
 | 
			
		||||
   *   The layout entity.
 | 
			
		||||
   * @param \Drupal\block_content\BlockContentInterface $block_content
 | 
			
		||||
   *   The block content revision.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the block content revision is used as an inline block in the
 | 
			
		||||
   *   layout entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function isBlockRevisionUsedInEntity(EntityInterface $layout_entity, BlockContentInterface $block_content) {
 | 
			
		||||
    $sections_blocks_revision_ids = $this->getInlineBlockRevisionIdsInSections($this->getEntitySections($layout_entity));
 | 
			
		||||
    return in_array($block_content->getRevisionId(), $sections_blocks_revision_ids);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,108 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Field;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Field\FieldItemList;
 | 
			
		||||
use Drupal\Core\Field\FieldItemListInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
use Drupal\layout_builder\SectionListInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionListTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an item list class for layout section fields.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem
 | 
			
		||||
 */
 | 
			
		||||
class LayoutSectionItemList extends FieldItemList implements SectionListInterface {
 | 
			
		||||
 | 
			
		||||
  use SectionListTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Numerically indexed array of field items.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $list = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSections() {
 | 
			
		||||
    $sections = [];
 | 
			
		||||
    foreach ($this->list as $delta => $item) {
 | 
			
		||||
      $sections[$delta] = $item->section;
 | 
			
		||||
    }
 | 
			
		||||
    return $sections;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setSections(array $sections) {
 | 
			
		||||
    $this->list = [];
 | 
			
		||||
    $sections = array_values($sections);
 | 
			
		||||
    /** @var \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem $item */
 | 
			
		||||
    foreach ($sections as $section) {
 | 
			
		||||
      $item = $this->appendItem();
 | 
			
		||||
      $item->section = $section;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getEntity() {
 | 
			
		||||
    $entity = parent::getEntity();
 | 
			
		||||
 | 
			
		||||
    // Ensure the entity is updated with the latest value.
 | 
			
		||||
    $entity->set($this->getName(), $this->getValue());
 | 
			
		||||
    return $entity;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function preSave() {
 | 
			
		||||
    parent::preSave();
 | 
			
		||||
    // Loop through each section and reconstruct it to ensure that all default
 | 
			
		||||
    // values are present.
 | 
			
		||||
    foreach ($this->list as $item) {
 | 
			
		||||
      $item->section = Section::fromArray($item->section->toArray());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function equals(FieldItemListInterface $list_to_compare) {
 | 
			
		||||
    if (!$list_to_compare instanceof LayoutSectionItemList) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Convert arrays of section objects to array values for comparison.
 | 
			
		||||
    $convert = function (LayoutSectionItemList $list) {
 | 
			
		||||
      return array_map(function (Section $section) {
 | 
			
		||||
        return $section->toArray();
 | 
			
		||||
      }, $list->getSections());
 | 
			
		||||
    };
 | 
			
		||||
    return $convert($this) === $convert($list_to_compare);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Overrides \Drupal\Core\Field\FieldItemListInterface::defaultAccess().
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup layout_builder_access
 | 
			
		||||
   */
 | 
			
		||||
  public function defaultAccess($operation = 'view', ?AccountInterface $account = NULL) {
 | 
			
		||||
    // @todo Allow access in https://www.drupal.org/node/2942975.
 | 
			
		||||
    return AccessResult::forbidden();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								web/core/modules/layout_builder/src/Form/AddBlockForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								web/core/modules/layout_builder/src/Form/AddBlockForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\SectionComponent;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form to add a block.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class AddBlockForm extends ConfigureBlockFormBase {
 | 
			
		||||
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_add_block';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function submitLabel() {
 | 
			
		||||
    return $this->t('Add block');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the form for the block.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   An associative array containing the structure of the form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current state of the form.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage being configured.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region of the block.
 | 
			
		||||
   * @param string|null $plugin_id
 | 
			
		||||
   *   The plugin ID of the block to add.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The form array.
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $plugin_id = NULL) {
 | 
			
		||||
    // Only generate a new component once per form submission.
 | 
			
		||||
    if (!$component = $form_state->get('layout_builder__component')) {
 | 
			
		||||
      $component = new SectionComponent($this->uuidGenerator->generate(), $region, ['id' => $plugin_id]);
 | 
			
		||||
      $section_storage->getSection($delta)->appendComponent($component);
 | 
			
		||||
      $form_state->set('layout_builder__component', $component);
 | 
			
		||||
    }
 | 
			
		||||
    $form['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockAddHighlightId($delta, $region);
 | 
			
		||||
    return $this->doBuildForm($form, $form_state, $section_storage, $delta, $component);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,290 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Html;
 | 
			
		||||
use Drupal\Component\Uuid\UuidInterface;
 | 
			
		||||
use Drupal\Core\Ajax\AjaxFormHelperTrait;
 | 
			
		||||
use Drupal\Core\Block\BlockManagerInterface;
 | 
			
		||||
use Drupal\Core\Block\BlockPluginInterface;
 | 
			
		||||
use Drupal\Core\Form\BaseFormIdInterface;
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\SubformState;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginFormFactoryInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginWithFormsInterface;
 | 
			
		||||
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
 | 
			
		||||
use Drupal\layout_builder\Controller\LayoutRebuildTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionComponent;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a base form for configuring a block.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ConfigureBlockFormBase extends FormBase implements BaseFormIdInterface, WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxFormHelperTrait;
 | 
			
		||||
  use ContextAwarePluginAssignmentTrait;
 | 
			
		||||
  use LayoutBuilderContextTrait;
 | 
			
		||||
  use LayoutRebuildTrait;
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin being configured.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Block\BlockPluginInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $block;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Block\BlockManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $blockManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The UUID generator.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Uuid\UuidInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $uuidGenerator;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin form manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Plugin\PluginFormFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $pluginFormFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $delta;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current region.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $region;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The UUID of the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $uuid;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new block form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
 | 
			
		||||
   *   The context repository.
 | 
			
		||||
   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
 | 
			
		||||
   *   The block manager.
 | 
			
		||||
   * @param \Drupal\Component\Uuid\UuidInterface $uuid
 | 
			
		||||
   *   The UUID generator.
 | 
			
		||||
   * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager
 | 
			
		||||
   *   The plugin form manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, ContextRepositoryInterface $context_repository, BlockManagerInterface $block_manager, UuidInterface $uuid, PluginFormFactoryInterface $plugin_form_manager) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
    $this->contextRepository = $context_repository;
 | 
			
		||||
    $this->blockManager = $block_manager;
 | 
			
		||||
    $this->uuidGenerator = $uuid;
 | 
			
		||||
    $this->pluginFormFactory = $plugin_form_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository'),
 | 
			
		||||
      $container->get('context.repository'),
 | 
			
		||||
      $container->get('plugin.manager.block'),
 | 
			
		||||
      $container->get('uuid'),
 | 
			
		||||
      $container->get('plugin_form.factory')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getBaseFormId() {
 | 
			
		||||
    return 'layout_builder_configure_block';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the form for the block.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   An associative array containing the structure of the form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current state of the form.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage being configured.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent $component
 | 
			
		||||
   *   The section component containing the block.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The form array.
 | 
			
		||||
   */
 | 
			
		||||
  public function doBuildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL, $delta = NULL, ?SectionComponent $component = NULL) {
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    $this->delta = $delta;
 | 
			
		||||
    $this->uuid = $component->getUuid();
 | 
			
		||||
    $this->block = $component->getPlugin();
 | 
			
		||||
 | 
			
		||||
    $form_state->setTemporaryValue('gathered_contexts', $this->getPopulatedContexts($section_storage));
 | 
			
		||||
 | 
			
		||||
    $form['#tree'] = TRUE;
 | 
			
		||||
    $form['settings'] = [];
 | 
			
		||||
    $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
 | 
			
		||||
    $form['settings'] = $this->getPluginForm($this->block)->buildConfigurationForm($form['settings'], $subform_state);
 | 
			
		||||
 | 
			
		||||
    $form['actions']['submit'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->submitLabel(),
 | 
			
		||||
      '#button_type' => 'primary',
 | 
			
		||||
    ];
 | 
			
		||||
    if ($this->isAjax()) {
 | 
			
		||||
      $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
 | 
			
		||||
      // @todo static::ajaxSubmit() requires data-drupal-selector to be the same
 | 
			
		||||
      //   between the various Ajax requests. A bug in
 | 
			
		||||
      //   \Drupal\Core\Form\FormBuilder prevents that from happening unless
 | 
			
		||||
      //   $form['#id'] is also the same. Normally, #id is set to a unique HTML
 | 
			
		||||
      //   ID via Html::getUniqueId(), but here we bypass that in order to work
 | 
			
		||||
      //   around the data-drupal-selector bug. This is okay so long as we
 | 
			
		||||
      //   assume that this form only ever occurs once on a page. Remove this
 | 
			
		||||
      //   workaround in https://www.drupal.org/node/2897377.
 | 
			
		||||
      $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mark this as an administrative page for JavaScript ("Back to site" link).
 | 
			
		||||
    $form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the label for the submit button.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Submit label.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function submitLabel();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
 | 
			
		||||
    $this->getPluginForm($this->block)->validateConfigurationForm($form['settings'], $subform_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    // Call the plugin submit handler.
 | 
			
		||||
    $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
 | 
			
		||||
    $this->getPluginForm($this->block)->submitConfigurationForm($form, $subform_state);
 | 
			
		||||
 | 
			
		||||
    // If this block is context-aware, set the context mapping.
 | 
			
		||||
    if ($this->block instanceof ContextAwarePluginInterface) {
 | 
			
		||||
      $this->block->setContextMapping($subform_state->getValue('context_mapping', []));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $configuration = $this->block->getConfiguration();
 | 
			
		||||
 | 
			
		||||
    $section = $this->sectionStorage->getSection($this->delta);
 | 
			
		||||
    $section->getComponent($this->uuid)->setConfiguration($configuration);
 | 
			
		||||
 | 
			
		||||
    $this->layoutTempstoreRepository->set($this->sectionStorage);
 | 
			
		||||
    $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    return $this->rebuildAndClose($this->sectionStorage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the plugin form for a given block.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Block\BlockPluginInterface $block
 | 
			
		||||
   *   The block plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\PluginFormInterface
 | 
			
		||||
   *   The plugin form for the block.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getPluginForm(BlockPluginInterface $block) {
 | 
			
		||||
    if ($block instanceof PluginWithFormsInterface) {
 | 
			
		||||
      return $this->pluginFormFactory->createInstance($block, 'configure');
 | 
			
		||||
    }
 | 
			
		||||
    return $block;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the section storage object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   *   The section storage for the current form.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSectionStorage() {
 | 
			
		||||
    return $this->sectionStorage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the current layout section being edited by the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Section
 | 
			
		||||
   *   The current layout section.
 | 
			
		||||
   */
 | 
			
		||||
  public function getCurrentSection() {
 | 
			
		||||
    return $this->sectionStorage->getSection($this->delta);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the current component being edited by the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionComponent
 | 
			
		||||
   *   The current section component.
 | 
			
		||||
   */
 | 
			
		||||
  public function getCurrentComponent() {
 | 
			
		||||
    return $this->getCurrentSection()->getComponent($this->uuid);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,278 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Html;
 | 
			
		||||
use Drupal\Core\Ajax\AjaxFormHelperTrait;
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\SubformState;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\Core\Layout\LayoutInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginFormFactoryInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginFormInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginWithFormsInterface;
 | 
			
		||||
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
 | 
			
		||||
use Drupal\layout_builder\Controller\LayoutRebuildTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form for configuring a layout section.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class ConfigureSectionForm extends FormBase implements WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxFormHelperTrait;
 | 
			
		||||
  use LayoutBuilderContextTrait;
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
  use LayoutRebuildTrait;
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin being configured.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Layout\LayoutInterface|\Drupal\Core\Plugin\PluginFormInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layout;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section being configured.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\Section
 | 
			
		||||
   */
 | 
			
		||||
  protected $section;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin form manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Plugin\PluginFormFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $pluginFormFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $delta;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $pluginId;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates whether the section is being added or updated.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $isUpdate;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new ConfigureSectionForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager
 | 
			
		||||
   *   The plugin form manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, PluginFormFactoryInterface $plugin_form_manager) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
    $this->pluginFormFactory = $plugin_form_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository'),
 | 
			
		||||
      $container->get('plugin_form.factory')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_configure_section';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL, $delta = NULL, $plugin_id = NULL) {
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    $this->delta = $delta;
 | 
			
		||||
    $this->isUpdate = is_null($plugin_id);
 | 
			
		||||
    $this->pluginId = $plugin_id;
 | 
			
		||||
 | 
			
		||||
    $section = $this->getCurrentSection();
 | 
			
		||||
 | 
			
		||||
    if ($this->isUpdate) {
 | 
			
		||||
      if ($label = $section->getLayoutSettings()['label']) {
 | 
			
		||||
        $form['#title'] = $this->t('Configure @section', ['@section' => $label]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Passing available contexts to the layout plugin here could result in an
 | 
			
		||||
    // exception since the layout may not have a context mapping for a required
 | 
			
		||||
    // context slot on creation.
 | 
			
		||||
    $this->layout = $section->getLayout();
 | 
			
		||||
 | 
			
		||||
    $form_state->setTemporaryValue('gathered_contexts', $this->getPopulatedContexts($this->sectionStorage));
 | 
			
		||||
    $form['#tree'] = TRUE;
 | 
			
		||||
    $form['layout_settings'] = [];
 | 
			
		||||
    $subform_state = SubformState::createForSubform($form['layout_settings'], $form, $form_state);
 | 
			
		||||
    $form['layout_settings'] = $this->getPluginForm($this->layout)->buildConfigurationForm($form['layout_settings'], $subform_state);
 | 
			
		||||
 | 
			
		||||
    $form['actions']['submit'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->isUpdate ? $this->t('Update') : $this->t('Add section'),
 | 
			
		||||
      '#button_type' => 'primary',
 | 
			
		||||
    ];
 | 
			
		||||
    if ($this->isAjax()) {
 | 
			
		||||
      $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
 | 
			
		||||
      // @todo static::ajaxSubmit() requires data-drupal-selector to be the same
 | 
			
		||||
      //   between the various Ajax requests. A bug in
 | 
			
		||||
      //   \Drupal\Core\Form\FormBuilder prevents that from happening unless
 | 
			
		||||
      //   $form['#id'] is also the same. Normally, #id is set to a unique HTML
 | 
			
		||||
      //   ID via Html::getUniqueId(), but here we bypass that in order to work
 | 
			
		||||
      //   around the data-drupal-selector bug. This is okay so long as we
 | 
			
		||||
      //   assume that this form only ever occurs once on a page. Remove this
 | 
			
		||||
      //   workaround in https://www.drupal.org/node/2897377.
 | 
			
		||||
      $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
 | 
			
		||||
    }
 | 
			
		||||
    $target_highlight_id = $this->isUpdate ? $this->sectionUpdateHighlightId($delta) : $this->sectionAddHighlightId($delta);
 | 
			
		||||
    $form['#attributes']['data-layout-builder-target-highlight-id'] = $target_highlight_id;
 | 
			
		||||
 | 
			
		||||
    // Mark this as an administrative page for JavaScript ("Back to site" link).
 | 
			
		||||
    $form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $subform_state = SubformState::createForSubform($form['layout_settings'], $form, $form_state);
 | 
			
		||||
    $this->getPluginForm($this->layout)->validateConfigurationForm($form['layout_settings'], $subform_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    // Call the plugin submit handler.
 | 
			
		||||
    $subform_state = SubformState::createForSubform($form['layout_settings'], $form, $form_state);
 | 
			
		||||
    $this->getPluginForm($this->layout)->submitConfigurationForm($form['layout_settings'], $subform_state);
 | 
			
		||||
 | 
			
		||||
    // If this layout is context-aware, set the context mapping.
 | 
			
		||||
    if ($this->layout instanceof ContextAwarePluginInterface) {
 | 
			
		||||
      $this->layout->setContextMapping($subform_state->getValue('context_mapping', []));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $configuration = $this->layout->getConfiguration();
 | 
			
		||||
 | 
			
		||||
    $section = $this->getCurrentSection();
 | 
			
		||||
    $section->setLayoutSettings($configuration);
 | 
			
		||||
    if (!$this->isUpdate) {
 | 
			
		||||
      $this->sectionStorage->insertSection($this->delta, $section);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->layoutTempstoreRepository->set($this->sectionStorage);
 | 
			
		||||
    $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    return $this->rebuildAndClose($this->sectionStorage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the plugin form for a given layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Layout\LayoutInterface $layout
 | 
			
		||||
   *   The layout plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\PluginFormInterface
 | 
			
		||||
   *   The plugin form for the layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 | 
			
		||||
   */
 | 
			
		||||
  protected function getPluginForm(LayoutInterface $layout) {
 | 
			
		||||
    if ($layout instanceof PluginWithFormsInterface) {
 | 
			
		||||
      return $this->pluginFormFactory->createInstance($layout, 'configure');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($layout instanceof PluginFormInterface) {
 | 
			
		||||
      return $layout;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    throw new \InvalidArgumentException(sprintf('The "%s" layout does not provide a configuration form', $layout->getPluginId()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the section storage property.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   *   The section storage for the current form.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSectionStorage() {
 | 
			
		||||
    return $this->sectionStorage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the layout being modified by the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Layout\LayoutInterface|\Drupal\Core\Plugin\PluginFormInterface
 | 
			
		||||
   *   The layout for the current form.
 | 
			
		||||
   */
 | 
			
		||||
  public function getCurrentLayout(): LayoutInterface {
 | 
			
		||||
    return $this->layout;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the section being modified by the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Section
 | 
			
		||||
   *   The section for the current form.
 | 
			
		||||
   */
 | 
			
		||||
  public function getCurrentSection(): Section {
 | 
			
		||||
    if (!isset($this->section)) {
 | 
			
		||||
      if ($this->isUpdate) {
 | 
			
		||||
        $this->section = $this->sectionStorage->getSection($this->delta);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $this->section = new Section($this->pluginId);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->section;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										149
									
								
								web/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								web/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,149 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityForm;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form containing the Layout Builder UI for defaults.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class DefaultsEntityForm extends EntityForm {
 | 
			
		||||
 | 
			
		||||
  use PreviewToggleTrait;
 | 
			
		||||
  use LayoutBuilderEntityFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type bundle info service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeBundleInfo;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new DefaultsEntityForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
 | 
			
		||||
   *   The entity type bundle info service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository'),
 | 
			
		||||
      $container->get('entity_type.bundle.info')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL) {
 | 
			
		||||
    $form['#attributes']['class'][] = 'layout-builder-form';
 | 
			
		||||
    $form['layout_builder'] = [
 | 
			
		||||
      '#type' => 'layout_builder',
 | 
			
		||||
      '#section_storage' => $section_storage,
 | 
			
		||||
    ];
 | 
			
		||||
    $form['layout_builder_message'] = $this->buildMessage($section_storage->getContextValue('display'));
 | 
			
		||||
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders a message to display at the top of the layout builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $entity
 | 
			
		||||
   *   The entity view display being edited.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A renderable array containing the message.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildMessage(LayoutEntityDisplayInterface $entity) {
 | 
			
		||||
    $entity_type_id = $entity->getTargetEntityTypeId();
 | 
			
		||||
    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
 | 
			
		||||
    $bundle_info = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
 | 
			
		||||
 | 
			
		||||
    $args = [
 | 
			
		||||
      '@bundle' => $bundle_info[$entity->getTargetBundle()]['label'],
 | 
			
		||||
      '@plural_label' => $entity_type->getPluralLabel(),
 | 
			
		||||
    ];
 | 
			
		||||
    if ($entity_type->hasKey('bundle')) {
 | 
			
		||||
      $message = $this->t('You are editing the layout template for all @bundle @plural_label.', $args);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $message = $this->t('You are editing the layout template for all @plural_label.', $args);
 | 
			
		||||
    }
 | 
			
		||||
    return $this->buildMessageContainer($message, 'defaults');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildEntity(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    // \Drupal\Core\Entity\EntityForm::buildEntity() clones the entity object.
 | 
			
		||||
    // Keep it in sync with the one used by the section storage.
 | 
			
		||||
    $this->setEntity($this->sectionStorage->getContextValue('display'));
 | 
			
		||||
    $entity = parent::buildEntity($form, $form_state);
 | 
			
		||||
    $this->sectionStorage->setContextValue('display', $entity);
 | 
			
		||||
    return $entity;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
 | 
			
		||||
    $route_parameters = $route_match->getParameters()->all();
 | 
			
		||||
 | 
			
		||||
    return $this->entityTypeManager->getStorage('entity_view_display')->load($route_parameters['entity_type_id'] . '.' . $route_parameters['bundle'] . '.' . $route_parameters['view_mode_name']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function actions(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $actions = parent::actions($form, $form_state);
 | 
			
		||||
    return $this->buildActions($actions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $return = $this->sectionStorage->save();
 | 
			
		||||
    $this->saveTasks($form_state, $this->t('The layout has been saved.'));
 | 
			
		||||
    return $return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,109 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\ConfirmFormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\Core\Messenger\MessengerInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Discards any pending changes to the layout.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class DiscardLayoutChangesForm extends ConfirmFormBase implements WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The messenger service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Messenger\MessengerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $messenger;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new DiscardLayoutChangesForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
 | 
			
		||||
   *   The messenger service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
    $this->messenger = $messenger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository'),
 | 
			
		||||
      $container->get('messenger')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_discard_changes';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getQuestion() {
 | 
			
		||||
    return $this->t('Are you sure you want to discard your layout changes?');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCancelUrl() {
 | 
			
		||||
    return $this->sectionStorage->getLayoutBuilderUrl();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL) {
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    // Mark this as an administrative page for JavaScript ("Back to site" link).
 | 
			
		||||
    $form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->layoutTempstoreRepository->delete($this->sectionStorage);
 | 
			
		||||
 | 
			
		||||
    $this->messenger->addMessage($this->t('The changes to the layout have been discarded.'));
 | 
			
		||||
 | 
			
		||||
    $form_state->setRedirectUrl($this->sectionStorage->getRedirectUrl());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,114 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\ConfirmFormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\Core\Messenger\MessengerInterface;
 | 
			
		||||
use Drupal\layout_builder\DefaultsSectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Disables Layout Builder for a given default.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderDisableForm extends ConfirmFormBase implements WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\DefaultsSectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new RevertOverridesForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
 | 
			
		||||
   *   The messenger service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
    $this->setMessenger($messenger);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository'),
 | 
			
		||||
      $container->get('messenger')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_disable_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getQuestion() {
 | 
			
		||||
    return $this->t('Are you sure you want to disable Layout Builder?');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDescription() {
 | 
			
		||||
    return $this->t('All customizations will be removed. This action cannot be undone.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCancelUrl() {
 | 
			
		||||
    return $this->sectionStorage->getRedirectUrl();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL) {
 | 
			
		||||
    if (!$section_storage instanceof DefaultsSectionStorageInterface) {
 | 
			
		||||
      throw new \InvalidArgumentException(sprintf('The section storage with type "%s" and ID "%s" does not provide defaults', $section_storage->getStorageType(), $section_storage->getStorageId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    // Mark this as an administrative page for JavaScript ("Back to site" link).
 | 
			
		||||
    $form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->sectionStorage->disableLayoutBuilder()->save();
 | 
			
		||||
    $this->layoutTempstoreRepository->delete($this->sectionStorage);
 | 
			
		||||
 | 
			
		||||
    $this->messenger()->addMessage($this->t('Layout Builder has been disabled.'));
 | 
			
		||||
    $form_state->setRedirectUrl($this->getCancelUrl());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,109 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a trait for common methods used in Layout Builder entity forms.
 | 
			
		||||
 */
 | 
			
		||||
trait LayoutBuilderEntityFormTrait {
 | 
			
		||||
 | 
			
		||||
  use PreviewToggleTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getBaseFormId(): string {
 | 
			
		||||
    return $this->getEntity()->getEntityTypeId() . '_layout_builder_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Build the message container.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message
 | 
			
		||||
   *   The message to display.
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   The form type this is being attached to.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildMessageContainer(TranslatableMarkup $message, string $type): array {
 | 
			
		||||
    return [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => [
 | 
			
		||||
          'layout-builder__message',
 | 
			
		||||
          sprintf('layout-builder__message--%s', $type),
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'message' => [
 | 
			
		||||
        '#theme' => 'status_messages',
 | 
			
		||||
        '#message_list' => ['status' => [$message]],
 | 
			
		||||
        '#status_headings' => [
 | 
			
		||||
          'status' => $this->t('Status message'),
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      '#weight' => -900,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form submission handler.
 | 
			
		||||
   */
 | 
			
		||||
  public function redirectOnSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl($form_state->getTriggeringElement()['#redirect']));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the section storage object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   *   The section storage for the current form.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSectionStorage(): SectionStorageInterface {
 | 
			
		||||
    return $this->sectionStorage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the actions for the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $actions
 | 
			
		||||
   *   The actions array to modify.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The modified actions array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildActions(array $actions): array {
 | 
			
		||||
    $actions['#attributes']['role'] = 'region';
 | 
			
		||||
    $actions['#attributes']['aria-label'] = $this->t('Layout Builder tools');
 | 
			
		||||
    $actions['submit']['#value'] = $this->t('Save layout');
 | 
			
		||||
    $actions['#weight'] = -1000;
 | 
			
		||||
    $actions['discard_changes'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->t('Discard changes'),
 | 
			
		||||
      '#submit' => ['::redirectOnSubmit'],
 | 
			
		||||
      '#redirect' => 'discard_changes',
 | 
			
		||||
    ];
 | 
			
		||||
    $actions['preview_toggle'] = $this->buildContentPreviewToggle();
 | 
			
		||||
    return $actions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Performs tasks that are needed during the save process.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $formState
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message
 | 
			
		||||
   *   The message to display.
 | 
			
		||||
   */
 | 
			
		||||
  protected function saveTasks(FormStateInterface $formState, TranslatableMarkup $message): void {
 | 
			
		||||
    $this->layoutTempstoreRepository->delete($this->getSectionStorage());
 | 
			
		||||
    $this->messenger()->addStatus($message);
 | 
			
		||||
    $formState->setRedirectUrl($this->getSectionStorage()->getRedirectUrl());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,241 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Field\FieldDefinitionInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\field_ui\Form\EntityViewDisplayEditForm;
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
 | 
			
		||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Edit form for the LayoutBuilderEntityViewDisplay entity type.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity being used by this form.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entity;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage section.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\DefaultsSectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL) {
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function form(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $form = parent::form($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    // Remove the Layout Builder field from the list.
 | 
			
		||||
    $form['#fields'] = array_diff($form['#fields'], [OverridesSectionStorage::FIELD_NAME]);
 | 
			
		||||
    unset($form['fields'][OverridesSectionStorage::FIELD_NAME]);
 | 
			
		||||
 | 
			
		||||
    $is_enabled = $this->entity->isLayoutBuilderEnabled();
 | 
			
		||||
    if ($is_enabled) {
 | 
			
		||||
      // Hide the table of fields.
 | 
			
		||||
      $form['fields']['#access'] = FALSE;
 | 
			
		||||
      $form['#fields'] = [];
 | 
			
		||||
      $form['#extra'] = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $form['manage_layout'] = [
 | 
			
		||||
      '#type' => 'link',
 | 
			
		||||
      '#title' => $this->t('Manage layout'),
 | 
			
		||||
      '#weight' => -10,
 | 
			
		||||
      '#attributes' => ['class' => ['button']],
 | 
			
		||||
      '#url' => $this->sectionStorage->getLayoutBuilderUrl(),
 | 
			
		||||
      '#access' => $is_enabled,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['layout'] = [
 | 
			
		||||
      '#type' => 'details',
 | 
			
		||||
      '#open' => TRUE,
 | 
			
		||||
      '#title' => $this->t('Layout options'),
 | 
			
		||||
      '#tree' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['layout']['enabled'] = [
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
      '#title' => $this->t('Use Layout Builder'),
 | 
			
		||||
      '#default_value' => $is_enabled,
 | 
			
		||||
    ];
 | 
			
		||||
    $form['#entity_builders']['layout_builder'] = '::entityFormEntityBuild';
 | 
			
		||||
 | 
			
		||||
    // @todo Expand to work for all view modes in
 | 
			
		||||
    //   https://www.drupal.org/node/2907413.
 | 
			
		||||
    if ($this->isCanonicalMode($this->entity->getMode())) {
 | 
			
		||||
      $entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
 | 
			
		||||
      $form['layout']['allow_custom'] = [
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#title' => $this->t('Allow each @entity to have its layout customized.', [
 | 
			
		||||
          '@entity' => $entity_type->getSingularLabel(),
 | 
			
		||||
        ]),
 | 
			
		||||
        '#default_value' => $this->entity->isOverridable(),
 | 
			
		||||
        '#states' => [
 | 
			
		||||
          'disabled' => [
 | 
			
		||||
            ':input[name="layout[enabled]"]' => ['checked' => FALSE],
 | 
			
		||||
          ],
 | 
			
		||||
          'invisible' => [
 | 
			
		||||
            ':input[name="layout[enabled]"]' => ['checked' => FALSE],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
      if (!$is_enabled) {
 | 
			
		||||
        $form['layout']['allow_custom']['#attributes']['disabled'] = 'disabled';
 | 
			
		||||
      }
 | 
			
		||||
      // Prevent turning off overrides while any exist.
 | 
			
		||||
      if ($this->hasOverrides($this->entity)) {
 | 
			
		||||
        $form['layout']['enabled']['#disabled'] = TRUE;
 | 
			
		||||
        $form['layout']['enabled']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.');
 | 
			
		||||
        $form['layout']['allow_custom']['#disabled'] = TRUE;
 | 
			
		||||
        $form['layout']['allow_custom']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.');
 | 
			
		||||
        unset($form['layout']['allow_custom']['#states']);
 | 
			
		||||
        unset($form['#entity_builders']['layout_builder']);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // For non-canonical modes, the existing value should be preserved.
 | 
			
		||||
    else {
 | 
			
		||||
      $form['layout']['allow_custom'] = [
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => $this->entity->isOverridable(),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if the mode is used by the canonical route.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $mode
 | 
			
		||||
   *   The view mode.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the mode is valid, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected function isCanonicalMode($mode) {
 | 
			
		||||
    // @todo This is a convention core uses but is not a given, nor is it easily
 | 
			
		||||
    //   introspectable. Address in https://www.drupal.org/node/2907413.
 | 
			
		||||
    $canonical_mode = 'full';
 | 
			
		||||
 | 
			
		||||
    if ($mode === $canonical_mode) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The default mode is valid if the canonical mode is not enabled.
 | 
			
		||||
    if ($mode === 'default') {
 | 
			
		||||
      /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $storage */
 | 
			
		||||
      $storage = $this->entityTypeManager->getStorage($this->entity->getEntityTypeId());
 | 
			
		||||
      $query = $storage->getQuery()
 | 
			
		||||
        ->condition('targetEntityType', $this->entity->getTargetEntityTypeId())
 | 
			
		||||
        ->condition('bundle', $this->entity->getTargetBundle())
 | 
			
		||||
        ->condition('status', TRUE)
 | 
			
		||||
        ->condition('mode', $canonical_mode);
 | 
			
		||||
      return !$query->count()->execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if the defaults have any overrides.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $display
 | 
			
		||||
   *   The entity display.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if there are any overrides of this default, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected function hasOverrides(LayoutEntityDisplayInterface $display) {
 | 
			
		||||
    if (!$display->isOverridable()) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity_type = $this->entityTypeManager->getDefinition($display->getTargetEntityTypeId());
 | 
			
		||||
    $query = $this->entityTypeManager->getStorage($display->getTargetEntityTypeId())->getQuery()
 | 
			
		||||
      ->accessCheck(FALSE)
 | 
			
		||||
      ->exists(OverridesSectionStorage::FIELD_NAME);
 | 
			
		||||
    if ($bundle_key = $entity_type->getKey('bundle')) {
 | 
			
		||||
      $query->condition($bundle_key, $display->getTargetBundle());
 | 
			
		||||
    }
 | 
			
		||||
    return (bool) $query->count()->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
 | 
			
		||||
    // Do not process field values if Layout Builder is or will be enabled.
 | 
			
		||||
    $set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE);
 | 
			
		||||
    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $entity */
 | 
			
		||||
    $already_enabled = $entity->isLayoutBuilderEnabled();
 | 
			
		||||
    if ($already_enabled || $set_enabled) {
 | 
			
		||||
      $form['#fields'] = [];
 | 
			
		||||
      $form['#extra'] = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parent::copyFormValuesToEntity($entity, $form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Entity builder for layout options on the entity view display form.
 | 
			
		||||
   */
 | 
			
		||||
  public function entityFormEntityBuild($entity_type_id, LayoutEntityDisplayInterface $display, &$form, FormStateInterface &$form_state) {
 | 
			
		||||
    $set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE);
 | 
			
		||||
    $already_enabled = $display->isLayoutBuilderEnabled();
 | 
			
		||||
 | 
			
		||||
    if ($set_enabled) {
 | 
			
		||||
      $overridable = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE);
 | 
			
		||||
      $display->setOverridable($overridable);
 | 
			
		||||
 | 
			
		||||
      if (!$already_enabled) {
 | 
			
		||||
        $display->enableLayoutBuilder();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($already_enabled) {
 | 
			
		||||
      $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl('disable'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) {
 | 
			
		||||
    if ($this->entity->isLayoutBuilderEnabled()) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent::buildFieldRow($field_definition, $form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildExtraFieldRow($field_id, $extra_field) {
 | 
			
		||||
    if ($this->entity->isLayoutBuilderEnabled()) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent::buildExtraFieldRow($field_id, $extra_field);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,128 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxFormHelperTrait;
 | 
			
		||||
use Drupal\Core\Form\ConfirmFormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\layout_builder\Controller\LayoutRebuildTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a base class for confirmation forms that rebuild the Layout Builder.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
abstract class LayoutRebuildConfirmFormBase extends ConfirmFormBase implements WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxFormHelperTrait;
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
  use LayoutRebuildTrait;
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $delta;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new RemoveSectionForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCancelUrl() {
 | 
			
		||||
    return $this->sectionStorage->getLayoutBuilderUrl();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL, $delta = NULL) {
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    $this->delta = $delta;
 | 
			
		||||
 | 
			
		||||
    $form = parent::buildForm($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    if ($this->isAjax()) {
 | 
			
		||||
      $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
 | 
			
		||||
      $form['actions']['cancel']['#attributes']['class'][] = 'dialog-cancel';
 | 
			
		||||
      $target_highlight_id = !empty($this->uuid) ? $this->blockUpdateHighlightId($this->uuid) : $this->sectionUpdateHighlightId($delta);
 | 
			
		||||
      $form['#attributes']['data-layout-builder-target-highlight-id'] = $target_highlight_id;
 | 
			
		||||
      // The AJAX system automatically moves focus to the first tabbable
 | 
			
		||||
      // element after closing a dialog, sometimes scrolling to a page top.
 | 
			
		||||
      // Disable refocus on the button.
 | 
			
		||||
      $form['actions']['submit']['#ajax']['disable-refocus'] = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mark this as an administrative page for JavaScript ("Back to site" link).
 | 
			
		||||
    $form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->handleSectionStorage($this->sectionStorage, $form_state);
 | 
			
		||||
 | 
			
		||||
    $this->layoutTempstoreRepository->set($this->sectionStorage);
 | 
			
		||||
 | 
			
		||||
    $form_state->setRedirectUrl($this->getCancelUrl());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    return $this->rebuildAndClose($this->sectionStorage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Performs any actions on the section storage before saving.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current state of the form.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function handleSectionStorage(SectionStorageInterface $section_storage, FormStateInterface $form_state);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										345
									
								
								web/core/modules/layout_builder/src/Form/MoveBlockForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								web/core/modules/layout_builder/src/Form/MoveBlockForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,345 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxFormHelperTrait;
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
 | 
			
		||||
use Drupal\layout_builder\Controller\LayoutRebuildTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form for moving a block.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class MoveBlockForm extends FormBase implements WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use AjaxFormHelperTrait;
 | 
			
		||||
  use LayoutBuilderContextTrait;
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
  use LayoutRebuildTrait;
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $delta;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The region name.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $region;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The component uuid.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $uuid;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The Layout Tempstore.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstore;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new MoveBlockForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
 | 
			
		||||
    $this->layoutTempstore = $layout_tempstore_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_block_move';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the move block form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   An associative array containing the structure of the form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current state of the form.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage being configured.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The original delta of the section.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The original region of the block.
 | 
			
		||||
   * @param string $uuid
 | 
			
		||||
   *   The UUID of the block being updated.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The form array.
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $uuid = NULL) {
 | 
			
		||||
    $parameters = array_slice(func_get_args(), 2);
 | 
			
		||||
    foreach ($parameters as $parameter) {
 | 
			
		||||
      if (is_null($parameter)) {
 | 
			
		||||
        throw new \InvalidArgumentException('MoveBlockForm requires all parameters.');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    $this->delta = $delta;
 | 
			
		||||
    $this->uuid = $uuid;
 | 
			
		||||
    $this->region = $region;
 | 
			
		||||
 | 
			
		||||
    $form['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockUpdateHighlightId($uuid);
 | 
			
		||||
 | 
			
		||||
    $sections = $section_storage->getSections();
 | 
			
		||||
    $contexts = $this->getPopulatedContexts($section_storage);
 | 
			
		||||
    $region_options = [];
 | 
			
		||||
    foreach ($sections as $section_delta => $section) {
 | 
			
		||||
      $layout = $section->getLayout($contexts);
 | 
			
		||||
      $layout_definition = $layout->getPluginDefinition();
 | 
			
		||||
      if (!($section_label = $section->getLayoutSettings()['label'])) {
 | 
			
		||||
        $section_label = $this->t('Section: @delta', ['@delta' => $section_delta + 1])->render();
 | 
			
		||||
      }
 | 
			
		||||
      foreach ($layout_definition->getRegions() as $region_name => $region_info) {
 | 
			
		||||
        // Group regions by section.
 | 
			
		||||
        $region_options[$section_label]["$section_delta:$region_name"] = $this->t(
 | 
			
		||||
          '@section, Region: @region',
 | 
			
		||||
          ['@section' => $section_label, '@region' => $region_info['label']]
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // $this->region and $this->delta are where the block is currently placed.
 | 
			
		||||
    // $selected_region and $selected_delta are the values from this form
 | 
			
		||||
    // specifying where the block should be moved to.
 | 
			
		||||
    $selected_region = $this->getSelectedRegion($form_state);
 | 
			
		||||
    $selected_delta = $this->getSelectedDelta($form_state);
 | 
			
		||||
    $form['region'] = [
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#options' => $region_options,
 | 
			
		||||
      '#title' => $this->t('Region'),
 | 
			
		||||
      '#default_value' => "$selected_delta:$selected_region",
 | 
			
		||||
      '#ajax' => [
 | 
			
		||||
        'wrapper' => 'layout-builder-components-table',
 | 
			
		||||
        'callback' => '::getComponentsWrapper',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $current_section = $sections[$selected_delta];
 | 
			
		||||
 | 
			
		||||
    $aria_label = $this->t('Blocks in Section: @section, Region: @region', ['@section' => $selected_delta + 1, '@region' => $selected_region]);
 | 
			
		||||
 | 
			
		||||
    $form['components_wrapper']['components'] = [
 | 
			
		||||
      '#type' => 'table',
 | 
			
		||||
      '#header' => [
 | 
			
		||||
        $this->t('Block label'),
 | 
			
		||||
        $this->t('Weight'),
 | 
			
		||||
      ],
 | 
			
		||||
      '#tabledrag' => [
 | 
			
		||||
        [
 | 
			
		||||
          'action' => 'order',
 | 
			
		||||
          'relationship' => 'sibling',
 | 
			
		||||
          'group' => 'table-sort-weight',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      // Create a wrapping element so that the Ajax update also replaces the
 | 
			
		||||
      // 'Show block weights' link.
 | 
			
		||||
      '#theme_wrappers' => [
 | 
			
		||||
        'container' => [
 | 
			
		||||
          '#attributes' => [
 | 
			
		||||
            'id' => 'layout-builder-components-table',
 | 
			
		||||
            'class' => ['layout-builder-components-table'],
 | 
			
		||||
            'aria-label' => $aria_label,
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\layout_builder\SectionComponent[] $components */
 | 
			
		||||
    $components = $current_section->getComponentsByRegion($selected_region);
 | 
			
		||||
 | 
			
		||||
    // If the component is not in this region, add it to the listed components.
 | 
			
		||||
    if (!isset($components[$uuid])) {
 | 
			
		||||
      $components[$uuid] = $sections[$delta]->getComponent($uuid);
 | 
			
		||||
    }
 | 
			
		||||
    $state_weight_delta = round(count($components) / 2);
 | 
			
		||||
    foreach ($components as $component_uuid => $component) {
 | 
			
		||||
      /** @var \Drupal\Core\Block\BlockPluginInterface $plugin */
 | 
			
		||||
      $plugin = $component->getPlugin();
 | 
			
		||||
      $is_current_block = $component_uuid === $uuid;
 | 
			
		||||
      $row_classes = [
 | 
			
		||||
        'draggable',
 | 
			
		||||
        'layout-builder-components-table__row',
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      $label['#wrapper_attributes']['class'] = ['layout-builder-components-table__block-label'];
 | 
			
		||||
 | 
			
		||||
      if ($is_current_block) {
 | 
			
		||||
        // Highlight the current block.
 | 
			
		||||
        $label['#markup'] = $this->t('@label (current)', ['@label' => $plugin->label()]);
 | 
			
		||||
        $label['#wrapper_attributes']['class'][] = 'layout-builder-components-table__block-label--current';
 | 
			
		||||
        $row_classes[] = 'layout-builder-components-table__row--current';
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $label['#markup'] = $plugin->label();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $form['components_wrapper']['components'][$component_uuid] = [
 | 
			
		||||
        '#attributes' => ['class' => $row_classes],
 | 
			
		||||
        'label' => $label,
 | 
			
		||||
        'weight' => [
 | 
			
		||||
          '#type' => 'weight',
 | 
			
		||||
          '#default_value' => $component->getWeight(),
 | 
			
		||||
          '#title' => $this->t('Weight for @block block', ['@block' => $plugin->label()]),
 | 
			
		||||
          '#title_display' => 'invisible',
 | 
			
		||||
          '#attributes' => [
 | 
			
		||||
            'class' => ['table-sort-weight'],
 | 
			
		||||
          ],
 | 
			
		||||
          '#delta' => $state_weight_delta,
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $form['actions']['submit'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->t('Move'),
 | 
			
		||||
      '#button_type' => 'primary',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['#attributes']['data-add-layout-builder-wrapper'] = 'layout-builder--move-blocks-active';
 | 
			
		||||
 | 
			
		||||
    if ($this->isAjax()) {
 | 
			
		||||
      $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
 | 
			
		||||
    }
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $region = $this->getSelectedRegion($form_state);
 | 
			
		||||
    $delta = $this->getSelectedDelta($form_state);
 | 
			
		||||
    $original_section = $this->sectionStorage->getSection($this->delta);
 | 
			
		||||
    $component = $original_section->getComponent($this->uuid);
 | 
			
		||||
    $section = $this->sectionStorage->getSection($delta);
 | 
			
		||||
    if ($delta !== $this->delta) {
 | 
			
		||||
      // Remove component from old section and add it to the new section.
 | 
			
		||||
      $original_section->removeComponent($this->uuid);
 | 
			
		||||
      $section->insertComponent(0, $component);
 | 
			
		||||
    }
 | 
			
		||||
    $component->setRegion($region);
 | 
			
		||||
    foreach ($form_state->getValue('components') as $uuid => $component_info) {
 | 
			
		||||
      $section->getComponent($uuid)->setWeight($component_info['weight']);
 | 
			
		||||
    }
 | 
			
		||||
    $this->layoutTempstore->set($this->sectionStorage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ajax callback for the region select element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The components wrapper render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponentsWrapper(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    return $form['components_wrapper'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    return $this->rebuildAndClose($this->sectionStorage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the selected region.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The current region name.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getSelectedRegion(FormStateInterface $form_state) {
 | 
			
		||||
    if ($form_state->hasValue('region')) {
 | 
			
		||||
      return explode(':', $form_state->getValue('region'), 2)[1];
 | 
			
		||||
    }
 | 
			
		||||
    return $this->region;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the selected delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The section delta.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getSelectedDelta(FormStateInterface $form_state) {
 | 
			
		||||
    if ($form_state->hasValue('region')) {
 | 
			
		||||
      return (int) explode(':', $form_state->getValue('region'))[0];
 | 
			
		||||
    }
 | 
			
		||||
    return (int) $this->delta;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides a title callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The original delta of the section.
 | 
			
		||||
   * @param string $uuid
 | 
			
		||||
   *   The UUID of the block being updated.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The title for the move block form.
 | 
			
		||||
   */
 | 
			
		||||
  public function title(SectionStorageInterface $section_storage, $delta, $uuid) {
 | 
			
		||||
    $block_label = $section_storage
 | 
			
		||||
      ->getSection($delta)
 | 
			
		||||
      ->getComponent($uuid)
 | 
			
		||||
      ->getPlugin()
 | 
			
		||||
      ->label();
 | 
			
		||||
 | 
			
		||||
    return $this->t('Move the @block_label block', ['@block_label' => $block_label]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								web/core/modules/layout_builder/src/Form/OverridesEntityForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								web/core/modules/layout_builder/src/Form/OverridesEntityForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,181 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Entity\ContentEntityForm;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityRepositoryInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form containing the Layout Builder UI for overrides.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class OverridesEntityForm extends ContentEntityForm implements WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use PreviewToggleTrait;
 | 
			
		||||
  use LayoutBuilderEntityFormTrait;
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new OverridesEntityForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
 | 
			
		||||
   *   The entity repository service.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
 | 
			
		||||
   *   The entity type bundle service.
 | 
			
		||||
   * @param \Drupal\Component\Datetime\TimeInterface $time
 | 
			
		||||
   *   The time service.
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
 | 
			
		||||
    parent::__construct($entity_repository, $entity_type_bundle_info, $time);
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity.repository'),
 | 
			
		||||
      $container->get('entity_type.bundle.info'),
 | 
			
		||||
      $container->get('datetime.time'),
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function init(FormStateInterface $form_state) {
 | 
			
		||||
    parent::init($form_state);
 | 
			
		||||
 | 
			
		||||
    $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation(), FALSE);
 | 
			
		||||
    $form_display->setComponent(OverridesSectionStorage::FIELD_NAME, [
 | 
			
		||||
      'type' => 'layout_builder_widget',
 | 
			
		||||
      'weight' => -10,
 | 
			
		||||
      'settings' => [],
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    $this->setFormDisplay($form_display, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL) {
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    $form = parent::buildForm($form, $form_state);
 | 
			
		||||
    $form['#attributes']['class'][] = 'layout-builder-form';
 | 
			
		||||
 | 
			
		||||
    // @todo \Drupal\layout_builder\Field\LayoutSectionItemList::defaultAccess()
 | 
			
		||||
    //   restricts all access to the field, explicitly allow access here until
 | 
			
		||||
    //   https://www.drupal.org/node/2942975 is resolved.
 | 
			
		||||
    $form[OverridesSectionStorage::FIELD_NAME]['#access'] = TRUE;
 | 
			
		||||
 | 
			
		||||
    $form['layout_builder_message'] = $this->buildMessage($section_storage->getContextValue('entity'), $section_storage);
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders a message to display at the top of the layout builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity whose layout is being edited.
 | 
			
		||||
   * @param \Drupal\layout_builder\OverridesSectionStorageInterface $section_storage
 | 
			
		||||
   *   The current section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A renderable array containing the message.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildMessage(EntityInterface $entity, OverridesSectionStorageInterface $section_storage) {
 | 
			
		||||
    $entity_type = $entity->getEntityType();
 | 
			
		||||
    $bundle_info = $this->entityTypeBundleInfo->getBundleInfo($entity->getEntityTypeId());
 | 
			
		||||
 | 
			
		||||
    $variables = [
 | 
			
		||||
      '@bundle' => $bundle_info[$entity->bundle()]['label'],
 | 
			
		||||
      '@singular_label' => $entity_type->getSingularLabel(),
 | 
			
		||||
      '@plural_label' => $entity_type->getPluralLabel(),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $defaults_link = $section_storage
 | 
			
		||||
      ->getDefaultSectionStorage()
 | 
			
		||||
      ->getLayoutBuilderUrl();
 | 
			
		||||
 | 
			
		||||
    if ($defaults_link->access($this->currentUser())) {
 | 
			
		||||
      $variables[':link'] = $defaults_link->toString();
 | 
			
		||||
      if ($entity_type->hasKey('bundle')) {
 | 
			
		||||
        $message = $this->t('You are editing the layout for this @bundle @singular_label. <a href=":link">Edit the template for all @bundle @plural_label instead.</a>', $variables);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $message = $this->t('You are editing the layout for this @singular_label. <a href=":link">Edit the template for all @plural_label instead.</a>', $variables);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      if ($entity_type->hasKey('bundle')) {
 | 
			
		||||
        $message = $this->t('You are editing the layout for this @bundle @singular_label.', $variables);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $message = $this->t('You are editing the layout for this @singular_label.', $variables);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->buildMessageContainer($message, 'overrides');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $return = parent::save($form, $form_state);
 | 
			
		||||
    $this->saveTasks($form_state, $this->t('The layout override has been saved.'));
 | 
			
		||||
    return $return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function actions(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $actions = parent::actions($form, $form_state);
 | 
			
		||||
    $actions = $this->buildActions($actions);
 | 
			
		||||
    $actions['delete']['#access'] = FALSE;
 | 
			
		||||
 | 
			
		||||
    $actions['discard_changes']['#limit_validation_errors'] = [];
 | 
			
		||||
    // @todo This button should be conditionally displayed, see
 | 
			
		||||
    //   https://www.drupal.org/node/2917777.
 | 
			
		||||
    $actions['revert'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->t('Revert to defaults'),
 | 
			
		||||
      '#submit' => ['::redirectOnSubmit'],
 | 
			
		||||
      '#redirect' => 'revert',
 | 
			
		||||
    ];
 | 
			
		||||
    return $actions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a trait that provides a toggle for the content preview.
 | 
			
		||||
 */
 | 
			
		||||
trait PreviewToggleTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the content preview toggle input.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the content preview toggle.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildContentPreviewToggle() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => ['js-show'],
 | 
			
		||||
      ],
 | 
			
		||||
      'toggle_content_preview' => [
 | 
			
		||||
        '#title' => $this->t('Show content preview'),
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#value' => TRUE,
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          // Set attribute used by local storage to get content preview status.
 | 
			
		||||
          'data-content-preview-id' => "Drupal.layout_builder.content_preview.{$this->currentUser()->id()}",
 | 
			
		||||
        ],
 | 
			
		||||
        '#id' => 'layout-builder-content-preview',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the current user.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Session\AccountInterface
 | 
			
		||||
   *   The current user.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function currentUser();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								web/core/modules/layout_builder/src/Form/RemoveBlockForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								web/core/modules/layout_builder/src/Form/RemoveBlockForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,73 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form to confirm the removal of a block.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class RemoveBlockForm extends LayoutRebuildConfirmFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current region.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $region;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The UUID of the block being removed.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $uuid;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getQuestion() {
 | 
			
		||||
    $label = $this->sectionStorage
 | 
			
		||||
      ->getSection($this->delta)
 | 
			
		||||
      ->getComponent($this->uuid)
 | 
			
		||||
      ->getPlugin()
 | 
			
		||||
      ->label();
 | 
			
		||||
 | 
			
		||||
    return $this->t('Are you sure you want to remove the %label block?', ['%label' => $label]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getConfirmText() {
 | 
			
		||||
    return $this->t('Remove');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_remove_block';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $uuid = NULL) {
 | 
			
		||||
    $this->region = $region;
 | 
			
		||||
    $this->uuid = $uuid;
 | 
			
		||||
    return parent::buildForm($form, $form_state, $section_storage, $delta);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function handleSectionStorage(SectionStorageInterface $section_storage, FormStateInterface $form_state) {
 | 
			
		||||
    $section_storage->getSection($this->delta)->removeComponent($this->uuid);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,50 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form to confirm the removal of a section.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class RemoveSectionForm extends LayoutRebuildConfirmFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_remove_section';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getQuestion() {
 | 
			
		||||
    $configuration = $this->sectionStorage->getSection($this->delta)->getLayoutSettings();
 | 
			
		||||
    // Layouts may choose to use a class that might not have a label
 | 
			
		||||
    // configuration.
 | 
			
		||||
    if (!empty($configuration['label'])) {
 | 
			
		||||
      return $this->t('Are you sure you want to remove @section?', ['@section' => $configuration['label']]);
 | 
			
		||||
    }
 | 
			
		||||
    return $this->t('Are you sure you want to remove section @section?', ['@section' => $this->delta + 1]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getConfirmText() {
 | 
			
		||||
    return $this->t('Remove');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function handleSectionStorage(SectionStorageInterface $section_storage, FormStateInterface $form_state) {
 | 
			
		||||
    $section_storage->removeSection($this->delta);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										124
									
								
								web/core/modules/layout_builder/src/Form/RevertOverridesForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								web/core/modules/layout_builder/src/Form/RevertOverridesForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\ConfirmFormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceDynamicSafeFormInterface;
 | 
			
		||||
use Drupal\Core\Messenger\MessengerInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reverts the overridden layout to the defaults.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class RevertOverridesForm extends ConfirmFormBase implements WorkspaceDynamicSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  use WorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The messenger service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Messenger\MessengerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $messenger;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new RevertOverridesForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
 | 
			
		||||
   *   The messenger service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
    $this->messenger = $messenger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('layout_builder.tempstore_repository'),
 | 
			
		||||
      $container->get('messenger')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_revert_overrides';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getQuestion() {
 | 
			
		||||
    return $this->t('Are you sure you want to revert this to defaults?');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getConfirmText() {
 | 
			
		||||
    return $this->t('Revert');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCancelUrl() {
 | 
			
		||||
    return $this->sectionStorage->getLayoutBuilderUrl();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL) {
 | 
			
		||||
    if (!$section_storage instanceof OverridesSectionStorageInterface) {
 | 
			
		||||
      throw new \InvalidArgumentException(sprintf('The section storage with type "%s" and ID "%s" does not provide overrides', $section_storage->getStorageType(), $section_storage->getStorageId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->sectionStorage = $section_storage;
 | 
			
		||||
    // Mark this as an administrative page for JavaScript ("Back to site" link).
 | 
			
		||||
    $form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    // Remove all sections.
 | 
			
		||||
    $this->sectionStorage
 | 
			
		||||
      ->removeAllSections()
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->layoutTempstoreRepository->delete($this->sectionStorage);
 | 
			
		||||
 | 
			
		||||
    $this->messenger->addMessage($this->t('The layout has been reverted back to defaults.'));
 | 
			
		||||
    $form_state->setRedirectUrl($this->sectionStorage->getRedirectUrl());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								web/core/modules/layout_builder/src/Form/UpdateBlockForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								web/core/modules/layout_builder/src/Form/UpdateBlockForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a form to update a block.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class UpdateBlockForm extends ConfigureBlockFormBase {
 | 
			
		||||
 | 
			
		||||
  use LayoutBuilderHighlightTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'layout_builder_update_block';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the block form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   An associative array containing the structure of the form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current state of the form.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage being configured.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region of the block.
 | 
			
		||||
   * @param string $uuid
 | 
			
		||||
   *   The UUID of the block being updated.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The form array.
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state, ?SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $uuid = NULL) {
 | 
			
		||||
    $component = $section_storage->getSection($delta)->getComponent($uuid);
 | 
			
		||||
    $form['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockUpdateHighlightId($uuid);
 | 
			
		||||
    return $this->doBuildForm($form, $form_state, $section_storage, $delta, $component);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function submitLabel() {
 | 
			
		||||
    return $this->t('Update');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,63 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\Form\WorkspaceSafeFormTrait as EntityWorkspaceSafeFormTrait;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a trait that marks Layout Builder forms as workspace-safe.
 | 
			
		||||
 */
 | 
			
		||||
trait WorkspaceSafeFormTrait {
 | 
			
		||||
 | 
			
		||||
  use EntityWorkspaceSafeFormTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines whether the current form is safe to be submitted in a workspace.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   An associative array containing the structure of the form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current state of the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the form is workspace-safe, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function isWorkspaceSafeForm(array $form, FormStateInterface $form_state): bool {
 | 
			
		||||
    $section_storage = $this->sectionStorage ?: $this->getSectionStorageFromFormState($form_state);
 | 
			
		||||
    if ($section_storage) {
 | 
			
		||||
      $context_definitions = $section_storage->getContextDefinitions();
 | 
			
		||||
      if (!empty($context_definitions['entity'])) {
 | 
			
		||||
        /** @var \Drupal\Core\Entity\EntityInterface $entity */
 | 
			
		||||
        $entity = $section_storage->getContextValue('entity');
 | 
			
		||||
 | 
			
		||||
        return $this->isWorkspaceSafeEntity($entity);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the section storage from a form state object, if it exists.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface|null
 | 
			
		||||
   *   The section storage or NULL if it doesn't exist.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getSectionStorageFromFormState(FormStateInterface $form_state): ?SectionStorageInterface {
 | 
			
		||||
    foreach ($form_state->getBuildInfo()['args'] as $argument) {
 | 
			
		||||
      if ($argument instanceof SectionStorageInterface) {
 | 
			
		||||
        return $argument;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										407
									
								
								web/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										407
									
								
								web/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,407 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResultInterface;
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Link;
 | 
			
		||||
use Drupal\Core\Breadcrumb\Breadcrumb;
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\layout_builder\InlineBlockEntityOperations;
 | 
			
		||||
use Drupal\layout_builder\Plugin\Block\ExtraFieldBlock;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\field\FieldConfigInterface;
 | 
			
		||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\layout_builder\Form\OverridesEntityForm;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm;
 | 
			
		||||
use Drupal\layout_builder\Form\DefaultsEntityForm;
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage;
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
use Drupal\Core\Hook\Order\Order;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for layout_builder.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderHooks {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_help().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('help')]
 | 
			
		||||
  public function help($route_name, RouteMatchInterface $route_match): ?string {
 | 
			
		||||
    // Add help text to the Layout Builder UI.
 | 
			
		||||
    if ($route_match->getRouteObject()->getOption('_layout_builder')) {
 | 
			
		||||
      $output = '<p>' . $this->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>' . $this->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>' . $this->t('To manage other areas of the page, use the block administration page.') . '</p>';
 | 
			
		||||
      }
 | 
			
		||||
      $output .= '<p>' . $this->t('Forms and links inside the content of the layout builder tool have been disabled.') . '</p>';
 | 
			
		||||
      return $output;
 | 
			
		||||
    }
 | 
			
		||||
    switch ($route_name) {
 | 
			
		||||
      case 'help.page.layout_builder':
 | 
			
		||||
        $output = '<h2>' . $this->t('About') . '</h2>';
 | 
			
		||||
        $output .= '<p>' . $this->t('Layout Builder allows you to use layouts to customize how content, content 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>' . $this->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 .= '<h2>' . $this->t('Uses') . '</h2>';
 | 
			
		||||
        $output .= '<dl>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Default layouts') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->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>' . $this->t('Overridden layouts') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->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 .= '<dt>' . $this->t('User permissions') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('The Layout Builder module makes a number of permissions available, which can be set by role on the <a href=":permissions">permissions page</a>. For more information, see the <a href=":layout-builder-permissions">Configuring Layout Builder permissions</a> online documentation.', [
 | 
			
		||||
          ':permissions' => Url::fromRoute('user.admin_permissions.module', [
 | 
			
		||||
            'modules' => 'layout_builder',
 | 
			
		||||
          ])->toString(),
 | 
			
		||||
          ':layout-builder-permissions' => 'https://www.drupal.org/docs/8/core/modules/layout-builder/configuring-layout-builder-permissions',
 | 
			
		||||
        ]) . '</dd>';
 | 
			
		||||
        $output .= '</dl>';
 | 
			
		||||
        return $output;
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_entity_type_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_type_alter')]
 | 
			
		||||
  public function entityTypeAlter(array &$entity_types) : void {
 | 
			
		||||
    /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
 | 
			
		||||
    $entity_types['entity_view_display']->setClass(LayoutBuilderEntityViewDisplay::class)->setStorageClass(LayoutBuilderEntityViewDisplayStorage::class)->setFormClass('layout_builder', DefaultsEntityForm::class)->setFormClass('edit', LayoutBuilderEntityViewDisplayForm::class);
 | 
			
		||||
    // 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);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_form_FORM_ID_alter() for \Drupal\field_ui\Form\EntityFormDisplayEditForm.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('form_entity_form_display_edit_form_alter')]
 | 
			
		||||
  public function formEntityFormDisplayEditFormAlter(&$form, FormStateInterface $form_state) : void {
 | 
			
		||||
    // Hides the Layout Builder field. It is rendered directly in
 | 
			
		||||
    // \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::buildMultiple().
 | 
			
		||||
    unset($form['fields'][OverridesSectionStorage::FIELD_NAME]);
 | 
			
		||||
    $key = array_search(OverridesSectionStorage::FIELD_NAME, $form['#fields']);
 | 
			
		||||
    if ($key !== FALSE) {
 | 
			
		||||
      unset($form['#fields'][$key]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_insert().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('field_config_insert')]
 | 
			
		||||
  public function fieldConfigInsert(FieldConfigInterface $field_config): void {
 | 
			
		||||
    // Clear the sample entity for this entity type and bundle.
 | 
			
		||||
    $sample_entity_generator = \Drupal::service('layout_builder.sample_entity_generator');
 | 
			
		||||
    $sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
 | 
			
		||||
    \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_delete().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('field_config_delete')]
 | 
			
		||||
  public function fieldConfigDelete(FieldConfigInterface $field_config): void {
 | 
			
		||||
    // Clear the sample entity for this entity type and bundle.
 | 
			
		||||
    $sample_entity_generator = \Drupal::service('layout_builder.sample_entity_generator');
 | 
			
		||||
    $sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
 | 
			
		||||
    \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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()
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_view_alter', order: Order::Last)]
 | 
			
		||||
  public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
 | 
			
		||||
    // 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'])) {
 | 
			
		||||
      /** @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 = $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]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $route_name = \Drupal::routeMatch()->getRouteName();
 | 
			
		||||
    // If the entity is displayed within a Layout Builder block and the current
 | 
			
		||||
    // route is in the Layout Builder UI, then remove all contextual link
 | 
			
		||||
    // placeholders.
 | 
			
		||||
    if ($route_name && $display instanceof LayoutBuilderEntityViewDisplay && str_starts_with($route_name, 'layout_builder.')) {
 | 
			
		||||
      unset($build['#contextual_links']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_entity_build_defaults_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_build_defaults_alter')]
 | 
			
		||||
  public function entityBuildDefaultsAlter(array &$build, EntityInterface $entity, $view_mode): void {
 | 
			
		||||
    // Contextual links are removed for entities viewed in Layout Builder's UI.
 | 
			
		||||
    // The route.name.is_layout_builder_ui cache context accounts for this
 | 
			
		||||
    // difference.
 | 
			
		||||
    // @see layout_builder_entity_view_alter()
 | 
			
		||||
    // @see \Drupal\layout_builder\Cache\LayoutBuilderUiCacheContext
 | 
			
		||||
    $build['#cache']['contexts'][] = 'route.name.is_layout_builder_ui';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_entity_presave().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_presave')]
 | 
			
		||||
  public function entityPresave(EntityInterface $entity): void {
 | 
			
		||||
    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().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_delete')]
 | 
			
		||||
  public function entityDelete(EntityInterface $entity): void {
 | 
			
		||||
    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().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('cron')]
 | 
			
		||||
  public function cron(): void {
 | 
			
		||||
    if (\Drupal::moduleHandler()->moduleExists('block_content')) {
 | 
			
		||||
      /** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */
 | 
			
		||||
      $entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class);
 | 
			
		||||
      $entity_operations->removeUnused();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_plugin_filter_TYPE__CONSUMER_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('plugin_filter_block__layout_builder_alter')]
 | 
			
		||||
  public function pluginFilterBlockLayoutBuilderAlter(array &$definitions, array $extra): void {
 | 
			
		||||
    // Remove blocks that are not useful within Layout Builder.
 | 
			
		||||
    unset($definitions['system_messages_block']);
 | 
			
		||||
    unset($definitions['help_block']);
 | 
			
		||||
    unset($definitions['local_tasks_block']);
 | 
			
		||||
    unset($definitions['local_actions_block']);
 | 
			
		||||
    // Remove blocks that are non-functional within Layout Builder.
 | 
			
		||||
    unset($definitions['system_main_block']);
 | 
			
		||||
    // @todo Restore the page title block in https://www.drupal.org/node/2938129.
 | 
			
		||||
    unset($definitions['page_title_block']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_plugin_filter_TYPE_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('plugin_filter_block_alter')]
 | 
			
		||||
  public function pluginFilterBlockAlter(array &$definitions, array $extra, $consumer): void {
 | 
			
		||||
    // @todo Determine the 'inline_block' blocks should be allowed outside
 | 
			
		||||
    //   of layout_builder https://www.drupal.org/node/2979142.
 | 
			
		||||
    if ($consumer !== 'layout_builder' || !isset($extra['list']) || $extra['list'] !== 'inline_blocks') {
 | 
			
		||||
      foreach ($definitions as $id => $definition) {
 | 
			
		||||
        if ($definition['id'] === 'inline_block') {
 | 
			
		||||
          unset($definitions[$id]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_access().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('block_content_access')]
 | 
			
		||||
  public function blockContentAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
 | 
			
		||||
    /** @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('create and edit custom blocks')) {
 | 
			
		||||
      return AccessResult::allowed();
 | 
			
		||||
    }
 | 
			
		||||
    return AccessResult::forbidden();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_plugin_filter_TYPE__CONSUMER_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('plugin_filter_block__block_ui_alter')]
 | 
			
		||||
  public function pluginFilterBlockBlockUiAlter(array &$definitions, array $extra): void {
 | 
			
		||||
    foreach ($definitions as $id => $definition) {
 | 
			
		||||
      // 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()) {
 | 
			
		||||
            unset($definitions[$id]);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_plugin_filter_TYPE__CONSUMER_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('plugin_filter_layout__layout_builder_alter')]
 | 
			
		||||
  public function pluginFilterLayoutLayoutBuilderAlter(array &$definitions, array $extra): void {
 | 
			
		||||
    // 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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_plugin_filter_TYPE_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('plugin_filter_layout_alter')]
 | 
			
		||||
  public function pluginFilterLayoutAlter(array &$definitions, array $extra, $consumer): void {
 | 
			
		||||
    // Hide the blank layout plugin from listings.
 | 
			
		||||
    unset($definitions['layout_builder_blank']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_system_breadcrumb_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('system_breadcrumb_alter')]
 | 
			
		||||
  public function systemBreadcrumbAlter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void {
 | 
			
		||||
    // Remove the extra 'Manage display' breadcrumb for Layout Builder defaults.
 | 
			
		||||
    if ($route_match->getRouteObject() && $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');
 | 
			
		||||
        if (!$link->getUrl()->isRouted()) {
 | 
			
		||||
                return TRUE;
 | 
			
		||||
        }
 | 
			
		||||
          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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_entity_translation_create().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_translation_create')]
 | 
			
		||||
  public function entityTranslationCreate(EntityInterface $translation): void {
 | 
			
		||||
    /** @var \Drupal\Core\Entity\FieldableEntityInterface $translation */
 | 
			
		||||
    if ($translation->hasField(OverridesSectionStorage::FIELD_NAME) && $translation->getFieldDefinition(OverridesSectionStorage::FIELD_NAME)->isTranslatable()) {
 | 
			
		||||
      // When creating a new translation do not copy untranslated sections
 | 
			
		||||
      // because per-language layouts are not supported.
 | 
			
		||||
      $translation->set(OverridesSectionStorage::FIELD_NAME, []);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_theme_registry_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('theme_registry_alter')]
 | 
			
		||||
  public function themeRegistryAlter(&$theme_registry): void {
 | 
			
		||||
    // Move our preprocess to run after
 | 
			
		||||
    // content_translation_preprocess_language_content_settings_table().
 | 
			
		||||
    if (!empty($theme_registry['language_content_settings_table']['preprocess functions'])) {
 | 
			
		||||
      $preprocess_functions =& $theme_registry['language_content_settings_table']['preprocess functions'];
 | 
			
		||||
      $index = array_search('layout_builder_preprocess_language_content_settings_table', $preprocess_functions);
 | 
			
		||||
      if ($index !== FALSE) {
 | 
			
		||||
        unset($preprocess_functions[$index]);
 | 
			
		||||
        $preprocess_functions[] = 'layout_builder_preprocess_language_content_settings_table';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_theme_suggestions_HOOK_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('theme_suggestions_field_alter')]
 | 
			
		||||
  public function themeSuggestionsFieldAlter(&$suggestions, array $variables): void {
 | 
			
		||||
    $element = $variables['element'];
 | 
			
		||||
    if (isset($element['#third_party_settings']['layout_builder']['view_mode'])) {
 | 
			
		||||
      // See system_theme_suggestions_field().
 | 
			
		||||
      $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'] . '__' . $element['#third_party_settings']['layout_builder']['view_mode'];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,239 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\RevisionableInterface;
 | 
			
		||||
use Drupal\Core\Entity\SynchronizableInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a class for reacting to entity events related to Inline Blocks.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This is an internal utility class wrapping hook implementations.
 | 
			
		||||
 */
 | 
			
		||||
class InlineBlockEntityOperations implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  use LayoutEntityHelperTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Inline block usage tracking service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\InlineBlockUsageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $usage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block content storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $blockContentStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new EntityOperations object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
 | 
			
		||||
   *   The entity type manager service.
 | 
			
		||||
   * @param \Drupal\layout_builder\InlineBlockUsageInterface $usage
 | 
			
		||||
   *   Inline block usage tracking service.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
 | 
			
		||||
   *   The section storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsageInterface $usage, SectionStorageManagerInterface $section_storage_manager) {
 | 
			
		||||
    $this->entityTypeManager = $entityTypeManager;
 | 
			
		||||
    $this->blockContentStorage = $entityTypeManager->getStorage('block_content');
 | 
			
		||||
    $this->usage = $usage;
 | 
			
		||||
    $this->sectionStorageManager = $section_storage_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('inline_block.usage'),
 | 
			
		||||
      $container->get('plugin.manager.layout_builder.section_storage')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove all unused inline blocks on save.
 | 
			
		||||
   *
 | 
			
		||||
   * Entities that were used in prevision revisions will be removed if not
 | 
			
		||||
   * saving a new revision.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The parent entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function removeUnusedForEntityOnSave(EntityInterface $entity) {
 | 
			
		||||
    // If the entity is new or there is no original entity then there will
 | 
			
		||||
    // not be any unused inline blocks to remove.
 | 
			
		||||
    // If this is a revisionable entity then do not remove inline blocks. They
 | 
			
		||||
    // could be referenced in previous revisions even if this is not a new
 | 
			
		||||
    // revision.
 | 
			
		||||
    if ($entity->isNew() || !$entity->getOriginal() || $entity instanceof RevisionableInterface) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // If the original entity used the default storage then we cannot remove
 | 
			
		||||
    // unused inline blocks because they will still be referenced in the
 | 
			
		||||
    // defaults.
 | 
			
		||||
    if ($this->originalEntityUsesDefaultStorage($entity)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Delete and remove the usage for inline blocks that were removed.
 | 
			
		||||
    if ($removed_block_ids = $this->getRemovedBlockIds($entity)) {
 | 
			
		||||
      $this->deleteBlocksAndUsage($removed_block_ids);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the IDs of the inline blocks that were removed.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The layout entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int[]
 | 
			
		||||
   *   The block content IDs that were removed.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getRemovedBlockIds(EntityInterface $entity) {
 | 
			
		||||
    $original_sections = $this->getEntitySections($entity->getOriginal());
 | 
			
		||||
    $current_sections = $this->getEntitySections($entity);
 | 
			
		||||
    // Avoid un-needed conversion from revision IDs to block content IDs by
 | 
			
		||||
    // first determining if there are any revisions in the original that are not
 | 
			
		||||
    // also in the current sections.
 | 
			
		||||
    $current_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($current_sections);
 | 
			
		||||
    $original_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($original_sections);
 | 
			
		||||
    if ($unused_original_revision_ids = array_diff($original_block_content_revision_ids, $current_block_content_revision_ids)) {
 | 
			
		||||
      // If there are any revisions in the original that aren't in the current
 | 
			
		||||
      // there may some blocks that need to be removed.
 | 
			
		||||
      $current_block_content_ids = $this->getBlockIdsForRevisionIds($current_block_content_revision_ids);
 | 
			
		||||
      $unused_original_block_content_ids = $this->getBlockIdsForRevisionIds($unused_original_revision_ids);
 | 
			
		||||
      return array_diff($unused_original_block_content_ids, $current_block_content_ids);
 | 
			
		||||
    }
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handles entity tracking on deleting a parent entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The parent entity.
 | 
			
		||||
   */
 | 
			
		||||
  public function handleEntityDelete(EntityInterface $entity) {
 | 
			
		||||
    $this->usage->removeByLayoutEntity($entity);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handles saving a parent entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The parent entity.
 | 
			
		||||
   */
 | 
			
		||||
  public function handlePreSave(EntityInterface $entity) {
 | 
			
		||||
    if (($entity instanceof SynchronizableInterface && $entity->isSyncing())
 | 
			
		||||
      || !$this->isLayoutCompatibleEntity($entity)
 | 
			
		||||
    ) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $duplicate_blocks = FALSE;
 | 
			
		||||
    if ($sections = $this->getEntitySections($entity)) {
 | 
			
		||||
      if ($this->originalEntityUsesDefaultStorage($entity)) {
 | 
			
		||||
        // This is a new override from a default and the blocks need to be
 | 
			
		||||
        // duplicated.
 | 
			
		||||
        $duplicate_blocks = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      // Since multiple parent entity revisions may reference common block
 | 
			
		||||
      // revisions, when a block is modified, it must always result in the
 | 
			
		||||
      // creation of a new block revision.
 | 
			
		||||
      $new_revision = $entity instanceof RevisionableInterface;
 | 
			
		||||
      foreach ($this->getInlineBlockComponents($sections) as $component) {
 | 
			
		||||
        $this->saveInlineBlockComponent($entity, $component, $new_revision, $duplicate_blocks);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->removeUnusedForEntityOnSave($entity);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete the inline blocks and the usage records.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int[] $block_content_ids
 | 
			
		||||
   *   The block content entity IDs.
 | 
			
		||||
   */
 | 
			
		||||
  protected function deleteBlocksAndUsage(array $block_content_ids) {
 | 
			
		||||
    foreach ($block_content_ids as $block_content_id) {
 | 
			
		||||
      if ($block = $this->blockContentStorage->load($block_content_id)) {
 | 
			
		||||
        $block->delete();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->usage->deleteUsage($block_content_ids);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes unused inline blocks.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $limit
 | 
			
		||||
   *   The maximum number of inline blocks to remove.
 | 
			
		||||
   */
 | 
			
		||||
  public function removeUnused($limit = 100) {
 | 
			
		||||
    $this->deleteBlocksAndUsage($this->usage->getUnused($limit));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets blocks IDs for an array of revision IDs.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int[] $revision_ids
 | 
			
		||||
   *   The revision IDs.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int[]
 | 
			
		||||
   *   The block IDs.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getBlockIdsForRevisionIds(array $revision_ids) {
 | 
			
		||||
    if ($revision_ids) {
 | 
			
		||||
      $query = $this->blockContentStorage->getQuery()->accessCheck(FALSE);
 | 
			
		||||
      $query->condition('revision_id', $revision_ids, 'IN');
 | 
			
		||||
      $block_ids = $query->execute();
 | 
			
		||||
      return $block_ids;
 | 
			
		||||
    }
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves an inline block component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity with the layout.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent $component
 | 
			
		||||
   *   The section component with an inline block.
 | 
			
		||||
   * @param bool $new_revision
 | 
			
		||||
   *   Whether a new revision of the block should be created when modified.
 | 
			
		||||
   * @param bool $duplicate_blocks
 | 
			
		||||
   *   Whether the blocks should be duplicated.
 | 
			
		||||
   */
 | 
			
		||||
  protected function saveInlineBlockComponent(EntityInterface $entity, SectionComponent $component, $new_revision, $duplicate_blocks) {
 | 
			
		||||
    /** @var \Drupal\layout_builder\Plugin\Block\InlineBlock $plugin */
 | 
			
		||||
    $plugin = $component->getPlugin();
 | 
			
		||||
    $pre_save_configuration = $plugin->getConfiguration();
 | 
			
		||||
    $plugin->saveBlockContent($new_revision, $duplicate_blocks);
 | 
			
		||||
    $post_save_configuration = $plugin->getConfiguration();
 | 
			
		||||
    if ($duplicate_blocks || (empty($pre_save_configuration['block_revision_id']) && !empty($post_save_configuration['block_revision_id']))) {
 | 
			
		||||
      $this->usage->addUsage($post_save_configuration['block_id'], $entity);
 | 
			
		||||
    }
 | 
			
		||||
    $component->setConfiguration($post_save_configuration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								web/core/modules/layout_builder/src/InlineBlockUsage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								web/core/modules/layout_builder/src/InlineBlockUsage.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Database\Connection;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service class to track inline block usage.
 | 
			
		||||
 */
 | 
			
		||||
class InlineBlockUsage implements InlineBlockUsageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The database connection.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Database\Connection
 | 
			
		||||
   */
 | 
			
		||||
  protected $database;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates an InlineBlockUsage object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Database\Connection $database
 | 
			
		||||
   *   The database connection.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(Connection $database) {
 | 
			
		||||
    $this->database = $database;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function addUsage($block_content_id, EntityInterface $entity) {
 | 
			
		||||
    $this->database->merge('inline_block_usage')
 | 
			
		||||
      ->keys([
 | 
			
		||||
        'block_content_id' => $block_content_id,
 | 
			
		||||
        'layout_entity_id' => $entity->id(),
 | 
			
		||||
        'layout_entity_type' => $entity->getEntityTypeId(),
 | 
			
		||||
      ])->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getUnused($limit = 100) {
 | 
			
		||||
    $query = $this->database->select('inline_block_usage', 't');
 | 
			
		||||
    $query->fields('t', ['block_content_id']);
 | 
			
		||||
    $query->isNull('layout_entity_id');
 | 
			
		||||
    $query->isNull('layout_entity_type');
 | 
			
		||||
    return $query->range(0, $limit)->execute()->fetchCol();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function removeByLayoutEntity(EntityInterface $entity) {
 | 
			
		||||
    $query = $this->database->update('inline_block_usage')
 | 
			
		||||
      ->fields([
 | 
			
		||||
        'layout_entity_type' => NULL,
 | 
			
		||||
        'layout_entity_id' => NULL,
 | 
			
		||||
      ]);
 | 
			
		||||
    $query->condition('layout_entity_type', $entity->getEntityTypeId());
 | 
			
		||||
    $query->condition('layout_entity_id', $entity->id());
 | 
			
		||||
    $query->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteUsage(array $block_content_ids) {
 | 
			
		||||
    if (!empty($block_content_ids)) {
 | 
			
		||||
      $query = $this->database->delete('inline_block_usage')->condition('block_content_id', $block_content_ids, 'IN');
 | 
			
		||||
      $query->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getUsage($block_content_id) {
 | 
			
		||||
    $query = $this->database->select('inline_block_usage');
 | 
			
		||||
    $query->condition('block_content_id', $block_content_id);
 | 
			
		||||
    $query->fields('inline_block_usage', ['layout_entity_id', 'layout_entity_type']);
 | 
			
		||||
    $query->range(0, 1);
 | 
			
		||||
    return $query->execute()->fetchObject();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an interface for tracking inline block usage.
 | 
			
		||||
 */
 | 
			
		||||
interface InlineBlockUsageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a usage record.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $block_content_id
 | 
			
		||||
   *   The block content ID.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The layout entity.
 | 
			
		||||
   */
 | 
			
		||||
  public function addUsage($block_content_id, EntityInterface $entity);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets unused inline block IDs.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $limit
 | 
			
		||||
   *   The maximum number of block content entity IDs to return.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int[]
 | 
			
		||||
   *   The entity IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function getUnused($limit = 100);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove usage record by layout entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The layout entity.
 | 
			
		||||
   */
 | 
			
		||||
  public function removeByLayoutEntity(EntityInterface $entity);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete the inline blocks' the usage records.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int[] $block_content_ids
 | 
			
		||||
   *   The block content entity IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteUsage(array $block_content_ids);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets usage record for inline block by ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $block_content_id
 | 
			
		||||
   *   The block content entity ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @return object|false
 | 
			
		||||
   *   The usage record with properties layout_entity_id and layout_entity_type
 | 
			
		||||
   *   or FALSE if there is no usage.
 | 
			
		||||
   */
 | 
			
		||||
  public function getUsage($block_content_id);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides methods for enabling and disabling Layout Builder.
 | 
			
		||||
 */
 | 
			
		||||
interface LayoutBuilderEnabledInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if Layout Builder is enabled.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if Layout Builder is enabled, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function isLayoutBuilderEnabled();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Enables the Layout Builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function enableLayoutBuilder();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Disables the Layout Builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function disableLayoutBuilder();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								web/core/modules/layout_builder/src/LayoutBuilderEvents.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								web/core/modules/layout_builder/src/LayoutBuilderEvents.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines events for the layout_builder module.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent
 | 
			
		||||
 */
 | 
			
		||||
final class LayoutBuilderEvents {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the event fired when a component's render array is built.
 | 
			
		||||
   *
 | 
			
		||||
   * This event allows modules to collaborate on creating the render array of
 | 
			
		||||
   * the SectionComponent object. The event listener method receives a
 | 
			
		||||
   * \Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent
 | 
			
		||||
   * instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @Event
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent
 | 
			
		||||
   * @see \Drupal\layout_builder\SectionComponent::toRenderArray()
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  const SECTION_COMPONENT_BUILD_RENDER_ARRAY = 'section_component.build.render_array';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the event fired in when preparing a layout builder element.
 | 
			
		||||
   *
 | 
			
		||||
   * This event allows modules to collaborate on creating the sections used in
 | 
			
		||||
   * \Drupal\layout_builder\Element\LayoutBuilder during #pre_render.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\Event\PrepareLayoutEvent
 | 
			
		||||
   * @see \Drupal\layout_builder\Element\LayoutBuilder
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  const PREPARE_LAYOUT = 'prepare_layout';
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A trait for generating IDs used to highlight active UI elements.
 | 
			
		||||
 */
 | 
			
		||||
trait LayoutBuilderHighlightTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the ID used to highlight the active Layout Builder UI element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The section the block is in.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The section region in which the block is placed.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The highlight ID of the block.
 | 
			
		||||
   */
 | 
			
		||||
  protected function blockAddHighlightId($delta, $region) {
 | 
			
		||||
    return "block-$delta-$region";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the ID used to highlight the active Layout Builder UI element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $uuid
 | 
			
		||||
   *   The uuid of the block.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The highlight ID of the block.
 | 
			
		||||
   */
 | 
			
		||||
  protected function blockUpdateHighlightId($uuid) {
 | 
			
		||||
    return $uuid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the ID used to highlight the active Layout Builder UI element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The location of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The highlight ID of the section.
 | 
			
		||||
   */
 | 
			
		||||
  protected function sectionAddHighlightId($delta) {
 | 
			
		||||
    return "section-$delta";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the ID used to highlight the active Layout Builder UI element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The location of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The highlight ID of the section.
 | 
			
		||||
   */
 | 
			
		||||
  protected function sectionUpdateHighlightId($delta) {
 | 
			
		||||
    return "section-update-$delta";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides an interface for displays that could be overridable.
 | 
			
		||||
 */
 | 
			
		||||
interface LayoutBuilderOverridableInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if the display allows custom overrides.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if custom overrides are allowed, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function isOverridable();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the display to allow or disallow overrides.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $overridable
 | 
			
		||||
   *   TRUE if the display should allow overrides, FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setOverridable($overridable = TRUE);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,115 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides dynamic permissions for Layout Builder overrides.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::access()
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Dynamic permission callbacks are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderOverridesPermissions implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type bundle info service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $bundleInfo;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * LayoutBuilderOverridesPermissions constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
 | 
			
		||||
   *   The bundle info service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->bundleInfo = $bundle_info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('entity_type.bundle.info')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of permissions.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[][]
 | 
			
		||||
   *   An array whose keys are permission names and whose corresponding values
 | 
			
		||||
   *   are defined in \Drupal\user\PermissionHandlerInterface::getPermissions().
 | 
			
		||||
   */
 | 
			
		||||
  public function permissions() {
 | 
			
		||||
    $permissions = [];
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $entity_displays */
 | 
			
		||||
    $entity_displays = $this->entityTypeManager->getStorage('entity_view_display')->loadByProperties(['third_party_settings.layout_builder.allow_custom' => TRUE]);
 | 
			
		||||
    foreach ($entity_displays as $entity_display) {
 | 
			
		||||
      $entity_type_id = $entity_display->getTargetEntityTypeId();
 | 
			
		||||
      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
 | 
			
		||||
      $bundle = $entity_display->getTargetBundle();
 | 
			
		||||
      $args = [
 | 
			
		||||
        '%entity_type' => $entity_type->getCollectionLabel(),
 | 
			
		||||
        '@entity_type_singular' => $entity_type->getSingularLabel(),
 | 
			
		||||
        '@entity_type_plural' => $entity_type->getPluralLabel(),
 | 
			
		||||
        '%bundle' => $this->bundleInfo->getBundleInfo($entity_type_id)[$bundle]['label'],
 | 
			
		||||
      ];
 | 
			
		||||
      // These permissions are generated on behalf of $entity_display entity
 | 
			
		||||
      // display, therefore add this entity display as a config dependency.
 | 
			
		||||
      $dependencies = [
 | 
			
		||||
        $entity_display->getConfigDependencyKey() => [
 | 
			
		||||
          $entity_display->getConfigDependencyName(),
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
      if ($entity_type->hasKey('bundle')) {
 | 
			
		||||
        $permissions["configure all $bundle $entity_type_id layout overrides"] = [
 | 
			
		||||
          'title' => $this->t('%entity_type - %bundle: Configure all layout overrides', $args),
 | 
			
		||||
          'warning' => $this->t('Warning: Allows configuring the layout even if the user cannot edit the @entity_type_singular itself.', $args),
 | 
			
		||||
          'dependencies' => $dependencies,
 | 
			
		||||
        ];
 | 
			
		||||
        $permissions["configure editable $bundle $entity_type_id layout overrides"] = [
 | 
			
		||||
          'title' => $this->t('%entity_type - %bundle: Configure layout overrides for @entity_type_plural that the user can edit', $args),
 | 
			
		||||
          'dependencies' => $dependencies,
 | 
			
		||||
        ];
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $permissions["configure all $bundle $entity_type_id layout overrides"] = [
 | 
			
		||||
          'title' => $this->t('%entity_type: Configure all layout overrides', $args),
 | 
			
		||||
          'warning' => $this->t('Warning: Allows configuring the layout even if the user cannot edit the @entity_type_singular itself.', $args),
 | 
			
		||||
          'dependencies' => $dependencies,
 | 
			
		||||
        ];
 | 
			
		||||
        $permissions["configure editable $bundle $entity_type_id layout overrides"] = [
 | 
			
		||||
          'title' => $this->t('%entity_type: Configure layout overrides for @entity_type_plural that the user can edit', $args),
 | 
			
		||||
          'dependencies' => $dependencies,
 | 
			
		||||
        ];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $permissions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
 | 
			
		||||
use Drupal\layout_builder\EventSubscriber\SetInlineBlockDependency;
 | 
			
		||||
use Drupal\layout_builder\Normalizer\LayoutEntityDisplayNormalizer;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ChildDefinition;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Definition;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Reference;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sets the layout_builder.get_block_dependency_subscriber service definition.
 | 
			
		||||
 *
 | 
			
		||||
 * This service is dependent on the block_content module so it must be provided
 | 
			
		||||
 * dynamically.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Service providers are internal.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\EventSubscriber\SetInlineBlockDependency
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderServiceProvider implements ServiceProviderInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container) {
 | 
			
		||||
    $modules = $container->getParameter('container.modules');
 | 
			
		||||
    if (isset($modules['block_content'])) {
 | 
			
		||||
      $definition = new Definition(SetInlineBlockDependency::class);
 | 
			
		||||
      $definition->setArguments([
 | 
			
		||||
        new Reference('entity_type.manager'),
 | 
			
		||||
        new Reference('database'),
 | 
			
		||||
        new Reference('inline_block.usage'),
 | 
			
		||||
        new Reference('plugin.manager.layout_builder.section_storage'),
 | 
			
		||||
      ]);
 | 
			
		||||
      $definition->addTag('event_subscriber');
 | 
			
		||||
      $definition->setPublic(TRUE);
 | 
			
		||||
      $container->setDefinition('layout_builder.get_block_dependency_subscriber', $definition);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($modules['serialization'])) {
 | 
			
		||||
      $definition = (new ChildDefinition('serializer.normalizer.config_entity'))
 | 
			
		||||
        ->setClass(LayoutEntityDisplayNormalizer::class)
 | 
			
		||||
        // Ensure that this normalizer takes precedence for Layout Builder data
 | 
			
		||||
        // over the generic serializer.normalizer.config_entity.
 | 
			
		||||
        ->addTag('normalizer', ['priority' => 5]);
 | 
			
		||||
      $container->setDefinition('layout_builder.normalizer.layout_entity_display', $definition);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										158
									
								
								web/core/modules/layout_builder/src/LayoutEntityHelperTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								web/core/modules/layout_builder/src/LayoutEntityHelperTrait.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\Context;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\Context\EntityContext;
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Methods to help with entities using the layout builder.
 | 
			
		||||
 */
 | 
			
		||||
trait LayoutEntityHelperTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if an entity can have a layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity to check.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the entity can have a layout otherwise FALSE.
 | 
			
		||||
   */
 | 
			
		||||
  protected function isLayoutCompatibleEntity(EntityInterface $entity) {
 | 
			
		||||
    return $this->getSectionStorageForEntity($entity) !== NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets revision IDs for layout sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Section[] $sections
 | 
			
		||||
   *   The layout sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int[]
 | 
			
		||||
   *   The revision IDs.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getInlineBlockRevisionIdsInSections(array $sections) {
 | 
			
		||||
    $revision_ids = [];
 | 
			
		||||
    foreach ($this->getInlineBlockComponents($sections) as $component) {
 | 
			
		||||
      $configuration = $component->getPlugin()->getConfiguration();
 | 
			
		||||
      if (!empty($configuration['block_revision_id'])) {
 | 
			
		||||
        $revision_ids[] = $configuration['block_revision_id'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $revision_ids;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the sections for an entity if any.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Section[]
 | 
			
		||||
   *   The entity layout sections if available.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntitySections(EntityInterface $entity) {
 | 
			
		||||
    $section_storage = $this->getSectionStorageForEntity($entity);
 | 
			
		||||
    return $section_storage ? $section_storage->getSections() : [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets components that have Inline Block plugins.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Section[] $sections
 | 
			
		||||
   *   The layout sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionComponent[]
 | 
			
		||||
   *   The components that contain Inline Block plugins.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getInlineBlockComponents(array $sections) {
 | 
			
		||||
    $inline_block_components = [];
 | 
			
		||||
    foreach ($sections as $section) {
 | 
			
		||||
      foreach ($section->getComponents() as $component) {
 | 
			
		||||
        $plugin = $component->getPlugin();
 | 
			
		||||
        if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'inline_block') {
 | 
			
		||||
          $inline_block_components[] = $component;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $inline_block_components;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the section storage for an entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface|null
 | 
			
		||||
   *   The section storage if found otherwise NULL.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getSectionStorageForEntity(EntityInterface $entity) {
 | 
			
		||||
    // @todo Take into account other view modes in
 | 
			
		||||
    //   https://www.drupal.org/node/3008924.
 | 
			
		||||
    $view_mode = 'full';
 | 
			
		||||
    if ($entity instanceof LayoutEntityDisplayInterface) {
 | 
			
		||||
      $contexts['display'] = EntityContext::fromEntity($entity);
 | 
			
		||||
      $contexts['view_mode'] = new Context(new ContextDefinition('string'), $entity->getMode());
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $contexts['entity'] = EntityContext::fromEntity($entity);
 | 
			
		||||
      if ($entity instanceof FieldableEntityInterface) {
 | 
			
		||||
        $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
 | 
			
		||||
        if ($display instanceof LayoutEntityDisplayInterface) {
 | 
			
		||||
          $contexts['display'] = EntityContext::fromEntity($display);
 | 
			
		||||
        }
 | 
			
		||||
        $contexts['view_mode'] = new Context(new ContextDefinition('string'), $view_mode);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->sectionStorageManager()->findByContext($contexts, new CacheableMetadata());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if the original entity used the default section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * This method can be used during the entity save process to determine whether
 | 
			
		||||
   * the original entity is set and used the default section storage plugin as
 | 
			
		||||
   * determined by ::getSectionStorageForEntity().
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the original entity used the default storage.
 | 
			
		||||
   */
 | 
			
		||||
  protected function originalEntityUsesDefaultStorage(EntityInterface $entity) {
 | 
			
		||||
    $section_storage = $this->getSectionStorageForEntity($entity);
 | 
			
		||||
    if ($section_storage instanceof OverridesSectionStorageInterface && !$entity->isNew() && $entity->getOriginal()) {
 | 
			
		||||
      $original_section_storage = $this->getSectionStorageForEntity($entity->getOriginal());
 | 
			
		||||
      return $original_section_storage instanceof DefaultsSectionStorageInterface;
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the section storage manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
 | 
			
		||||
   *   The section storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  private function sectionStorageManager() {
 | 
			
		||||
    return $this->sectionStorageManager ?: \Drupal::service('plugin.manager.layout_builder.section_storage');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,130 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\TempStore\SharedTempStoreFactory;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a mechanism for loading layouts from tempstore.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutTempstoreRepository implements LayoutTempstoreRepositoryInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The shared tempstore factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
 | 
			
		||||
   */
 | 
			
		||||
  protected $tempStoreFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The static cache of loaded values.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorageInterface[]
 | 
			
		||||
   */
 | 
			
		||||
  protected array $cache = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * LayoutTempstoreRepository constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
 | 
			
		||||
   *   The shared tempstore factory.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SharedTempStoreFactory $temp_store_factory) {
 | 
			
		||||
    $this->tempStoreFactory = $temp_store_factory;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function get(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $key = $this->getKey($section_storage);
 | 
			
		||||
 | 
			
		||||
    // Check if the storage is present in the static cache.
 | 
			
		||||
    if (isset($this->cache[$key])) {
 | 
			
		||||
      return $this->cache[$key];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $tempstore = $this->getTempstore($section_storage)->get($key);
 | 
			
		||||
    if (!empty($tempstore['section_storage'])) {
 | 
			
		||||
      $storage_type = $section_storage->getStorageType();
 | 
			
		||||
      $section_storage = $tempstore['section_storage'];
 | 
			
		||||
 | 
			
		||||
      if (!($section_storage instanceof SectionStorageInterface)) {
 | 
			
		||||
        throw new \UnexpectedValueException(sprintf('The entry with storage type "%s" and ID "%s" is invalid', $storage_type, $key));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Set the storage in the static cache.
 | 
			
		||||
      $this->cache[$key] = $section_storage;
 | 
			
		||||
    }
 | 
			
		||||
    return $section_storage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function has(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $key = $this->getKey($section_storage);
 | 
			
		||||
 | 
			
		||||
    // Check if the storage is present in the static cache.
 | 
			
		||||
    if (isset($this->cache[$key])) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $tempstore = $this->getTempstore($section_storage)->get($key);
 | 
			
		||||
    return !empty($tempstore['section_storage']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function set(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $key = $this->getKey($section_storage);
 | 
			
		||||
    $this->getTempstore($section_storage)->set($key, ['section_storage' => $section_storage]);
 | 
			
		||||
    // Update the storage in the static cache.
 | 
			
		||||
    $this->cache[$key] = $section_storage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function delete(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $key = $this->getKey($section_storage);
 | 
			
		||||
    $this->getTempstore($section_storage)->delete($key);
 | 
			
		||||
    // Remove the storage from the static cache.
 | 
			
		||||
    unset($this->cache[$key]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the shared tempstore.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\TempStore\SharedTempStore
 | 
			
		||||
   *   The tempstore.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getTempstore(SectionStorageInterface $section_storage) {
 | 
			
		||||
    $collection = 'layout_builder.section_storage.' . $section_storage->getStorageType();
 | 
			
		||||
    return $this->tempStoreFactory->get($collection);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the string to use as the tempstore key.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A unique string representing the section storage. This should include as
 | 
			
		||||
   *   much identifying information as possible about this particular storage,
 | 
			
		||||
   *   including information like the current language.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getKey(SectionStorageInterface $section_storage) {
 | 
			
		||||
    if ($section_storage instanceof TempStoreIdentifierInterface) {
 | 
			
		||||
      return $section_storage->getTempstoreKey();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $section_storage->getStorageId();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides an interface for loading layouts from tempstore.
 | 
			
		||||
 */
 | 
			
		||||
interface LayoutTempstoreRepositoryInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the tempstore version of a section storage, if it exists.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage to check for in tempstore.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   *   Either the version of this section storage from tempstore, or the passed
 | 
			
		||||
   *   section storage if none exists.
 | 
			
		||||
   *
 | 
			
		||||
   * @throw \UnexpectedValueException
 | 
			
		||||
   *   Thrown if a value exists, but is not a section storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function get(SectionStorageInterface $section_storage);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stores this section storage in tempstore.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage to set in tempstore.
 | 
			
		||||
   */
 | 
			
		||||
  public function set(SectionStorageInterface $section_storage);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks for the existence of a tempstore version of a section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage to check for in tempstore.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if there is a tempstore version of this section storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function has(SectionStorageInterface $section_storage);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes the tempstore version of a section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
 | 
			
		||||
   *   The section storage to remove from tempstore.
 | 
			
		||||
   */
 | 
			
		||||
  public function delete(SectionStorageInterface $section_storage);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
 | 
			
		||||
use Drupal\serialization\Normalizer\ConfigEntityNormalizer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Normalizes/denormalizes LayoutEntityDisplay objects into an array structure.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutEntityDisplayNormalizer extends ConfigEntityNormalizer {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static function getDataWithoutInternals(array $data) {
 | 
			
		||||
    $data = parent::getDataWithoutInternals($data);
 | 
			
		||||
    // Do not expose the actual layout sections in normalization.
 | 
			
		||||
    // @todo Determine what to expose here in
 | 
			
		||||
    //   https://www.drupal.org/node/2942975.
 | 
			
		||||
    unset($data['third_party_settings']['layout_builder']['sections']);
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSupportedTypes(?string $format): array {
 | 
			
		||||
    return [
 | 
			
		||||
      LayoutEntityDisplayInterface::class => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an interface for an object that stores layout sections for overrides.
 | 
			
		||||
 */
 | 
			
		||||
interface OverridesSectionStorageInterface extends SectionStorageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the corresponding defaults section storage for this override.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\DefaultsSectionStorageInterface
 | 
			
		||||
   *   The defaults section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Determine if this method needs a parameter in
 | 
			
		||||
   *   https://www.drupal.org/project/drupal/issues/2907413.
 | 
			
		||||
   */
 | 
			
		||||
  public function getDefaultSectionStorage();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates if overrides are in use.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if this overrides section storage is in use, otherwise FALSE.
 | 
			
		||||
   */
 | 
			
		||||
  public function isOverridden();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,188 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Block;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Block\Attribute\Block;
 | 
			
		||||
use Drupal\Core\Block\BlockBase;
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\layout_builder\Plugin\Derivative\ExtraFieldBlockDeriver;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a block that renders an extra field from an entity.
 | 
			
		||||
 *
 | 
			
		||||
 * This block handles fields that are provided by implementations of
 | 
			
		||||
 * hook_entity_extra_field_info().
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Plugin\Block\FieldBlock
 | 
			
		||||
 *   This block plugin handles all other field entities not provided by
 | 
			
		||||
 *   hook_entity_extra_field_info().
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[Block(
 | 
			
		||||
  id: "extra_field_block",
 | 
			
		||||
  deriver: ExtraFieldBlockDeriver::class
 | 
			
		||||
)]
 | 
			
		||||
class ExtraFieldBlock extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity field manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityFieldManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field name.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $fieldName;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new ExtraFieldBlock.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param mixed $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
 | 
			
		||||
   *   The entity field manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->entityFieldManager = $entity_field_manager;
 | 
			
		||||
    // Get field name from the plugin ID.
 | 
			
		||||
    [, , , $field_name] = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4);
 | 
			
		||||
    assert(!empty($field_name));
 | 
			
		||||
    $this->fieldName = $field_name;
 | 
			
		||||
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function defaultConfiguration() {
 | 
			
		||||
    return [
 | 
			
		||||
      'label_display' => FALSE,
 | 
			
		||||
      'formatter' => [
 | 
			
		||||
        'settings' => [],
 | 
			
		||||
        'third_party_settings' => [],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('entity_field.manager')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the entity that has the field.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\FieldableEntityInterface
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntity() {
 | 
			
		||||
    return $this->getContextValue('entity');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function build() {
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    // Add a placeholder to replace after the entity view is built.
 | 
			
		||||
    // @see layout_builder_entity_view_alter().
 | 
			
		||||
    $extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
 | 
			
		||||
    if (!isset($extra_fields['display'][$this->fieldName])) {
 | 
			
		||||
      $build = [];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $build = [
 | 
			
		||||
        '#extra_field_placeholder_field_name' => $this->fieldName,
 | 
			
		||||
        // Always provide a placeholder. The Layout Builder will NOT invoke
 | 
			
		||||
        // hook_entity_view_alter() so extra fields will not be added to the
 | 
			
		||||
        // render array. If the hook is invoked the placeholder will be
 | 
			
		||||
        // replaced.
 | 
			
		||||
        // @see ::replaceFieldPlaceholder()
 | 
			
		||||
        '#markup' => $this->t('Placeholder for the @preview_fallback', ['@preview_fallback' => $this->getPreviewFallbackString()]),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    CacheableMetadata::createFromObject($this)->applyTo($build);
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getPreviewFallbackString() {
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    $extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
 | 
			
		||||
    return new TranslatableMarkup('"@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Replaces all placeholders for a given field.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $build
 | 
			
		||||
   *   The built render array for the elements.
 | 
			
		||||
   * @param array $built_field
 | 
			
		||||
   *   The render array to replace the placeholder.
 | 
			
		||||
   * @param string $field_name
 | 
			
		||||
   *   The field name.
 | 
			
		||||
   *
 | 
			
		||||
   * @see ::build()
 | 
			
		||||
   */
 | 
			
		||||
  public static function replaceFieldPlaceholder(array &$build, array $built_field, $field_name) {
 | 
			
		||||
    foreach (Element::children($build) as $child) {
 | 
			
		||||
      if (isset($build[$child]['#extra_field_placeholder_field_name']) && $build[$child]['#extra_field_placeholder_field_name'] === $field_name) {
 | 
			
		||||
        $placeholder_cache = CacheableMetadata::createFromRenderArray($build[$child]);
 | 
			
		||||
        $built_cache = CacheableMetadata::createFromRenderArray($built_field);
 | 
			
		||||
        $merged_cache = $placeholder_cache->merge($built_cache);
 | 
			
		||||
        $build[$child] = $built_field;
 | 
			
		||||
        $merged_cache->applyTo($build);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        static::replaceFieldPlaceholder($build[$child], $built_field, $field_name);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function blockAccess(AccountInterface $account) {
 | 
			
		||||
    return $this->getEntity()->access('view', $account, TRUE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										441
									
								
								web/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										441
									
								
								web/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,441 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Block;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Block\Attribute\Block;
 | 
			
		||||
use Drupal\Core\Block\BlockBase;
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Entity\EntityDisplayBase;
 | 
			
		||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Field\FieldDefinitionInterface;
 | 
			
		||||
use Drupal\Core\Field\FormatterInterface;
 | 
			
		||||
use Drupal\Core\Field\FormatterPluginManager;
 | 
			
		||||
use Drupal\Core\Form\EnforcedResponseException;
 | 
			
		||||
use Drupal\Core\Form\FormHelper;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\field\FieldConfigInterface;
 | 
			
		||||
use Drupal\layout_builder\Plugin\Derivative\FieldBlockDeriver;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Drupal\field\FieldLabelOptionsTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a block that renders a field from an entity.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[Block(
 | 
			
		||||
  id: "field_block",
 | 
			
		||||
  deriver: FieldBlockDeriver::class
 | 
			
		||||
)]
 | 
			
		||||
class FieldBlock extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
 | 
			
		||||
 | 
			
		||||
  use FieldLabelOptionsTrait;
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity field manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityFieldManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The formatter manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Field\FormatterPluginManager
 | 
			
		||||
   */
 | 
			
		||||
  protected $formatterManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeId;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The bundle ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $bundle;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field name.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $fieldName;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field definition.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Field\FieldDefinitionInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fieldDefinition;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The module handler.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $moduleHandler;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The logger.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Psr\Log\LoggerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $logger;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new FieldBlock.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param mixed $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
 | 
			
		||||
   *   The entity field manager.
 | 
			
		||||
   * @param \Drupal\Core\Field\FormatterPluginManager $formatter_manager
 | 
			
		||||
   *   The formatter manager.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
 | 
			
		||||
   *   The module handler.
 | 
			
		||||
   * @param \Psr\Log\LoggerInterface $logger
 | 
			
		||||
   *   The logger.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FormatterPluginManager $formatter_manager, ModuleHandlerInterface $module_handler, LoggerInterface $logger) {
 | 
			
		||||
    $this->entityFieldManager = $entity_field_manager;
 | 
			
		||||
    $this->formatterManager = $formatter_manager;
 | 
			
		||||
    $this->moduleHandler = $module_handler;
 | 
			
		||||
    $this->logger = $logger;
 | 
			
		||||
 | 
			
		||||
    // Get the entity type and field name from the plugin ID.
 | 
			
		||||
    [, $entity_type_id, $bundle, $field_name] = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4);
 | 
			
		||||
    $this->entityTypeId = $entity_type_id;
 | 
			
		||||
    $this->bundle = $bundle;
 | 
			
		||||
    $this->fieldName = $field_name;
 | 
			
		||||
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('entity_field.manager'),
 | 
			
		||||
      $container->get('plugin.manager.field.formatter'),
 | 
			
		||||
      $container->get('module_handler'),
 | 
			
		||||
      $container->get('logger.channel.layout_builder')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the entity that has the field.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\FieldableEntityInterface
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntity() {
 | 
			
		||||
    return $this->getContextValue('entity');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function build() {
 | 
			
		||||
    $display_settings = $this->getConfiguration()['formatter'];
 | 
			
		||||
    $display_settings['third_party_settings']['layout_builder']['view_mode'] = $this->getContextValue('view_mode');
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    try {
 | 
			
		||||
      $build = [];
 | 
			
		||||
      $view = $entity->get($this->fieldName)->view($display_settings);
 | 
			
		||||
      if ($view) {
 | 
			
		||||
        $build = [$view];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // @todo Remove in https://www.drupal.org/project/drupal/issues/2367555.
 | 
			
		||||
    catch (EnforcedResponseException $e) {
 | 
			
		||||
      throw $e;
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $e) {
 | 
			
		||||
      $build = [];
 | 
			
		||||
      $this->logger->warning('The field "%field" failed to render with the error of "%error".', ['%field' => $this->fieldName, '%error' => $e->getMessage()]);
 | 
			
		||||
    }
 | 
			
		||||
    CacheableMetadata::createFromRenderArray($build)->addCacheableDependency($this)->applyTo($build);
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getPreviewFallbackString() {
 | 
			
		||||
    return new TranslatableMarkup('"@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function blockAccess(AccountInterface $account) {
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
 | 
			
		||||
    // First consult the entity.
 | 
			
		||||
    $access = $entity->access('view', $account, TRUE);
 | 
			
		||||
    if (!$access->isAllowed()) {
 | 
			
		||||
      return $access;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check that the entity in question has this field.
 | 
			
		||||
    if (!$entity instanceof FieldableEntityInterface || !$entity->hasField($this->fieldName)) {
 | 
			
		||||
      return $access->andIf(AccessResult::forbidden());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check field access.
 | 
			
		||||
    $field = $entity->get($this->fieldName);
 | 
			
		||||
    $access = $access->andIf($field->access('view', $account, TRUE));
 | 
			
		||||
    if (!$access->isAllowed()) {
 | 
			
		||||
      return $access;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check to see if the field has any values or a default value.
 | 
			
		||||
    if ($field->isEmpty() && !$this->entityFieldHasDefaultValue()) {
 | 
			
		||||
      return $access->andIf(AccessResult::forbidden());
 | 
			
		||||
    }
 | 
			
		||||
    return $access;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function defaultConfiguration() {
 | 
			
		||||
    return [
 | 
			
		||||
      'label_display' => FALSE,
 | 
			
		||||
      'formatter' => [
 | 
			
		||||
        'label' => 'above',
 | 
			
		||||
        'type' => $this->pluginDefinition['default_formatter'],
 | 
			
		||||
        'settings' => [],
 | 
			
		||||
        'third_party_settings' => [],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function blockForm($form, FormStateInterface $form_state) {
 | 
			
		||||
    $config = $this->getConfiguration();
 | 
			
		||||
 | 
			
		||||
    $form['formatter'] = [
 | 
			
		||||
      '#tree' => TRUE,
 | 
			
		||||
      '#process' => [
 | 
			
		||||
        [$this, 'formatterSettingsProcessCallback'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $form['formatter']['label'] = [
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#title' => $this->t('Label'),
 | 
			
		||||
      '#options' => $this->getFieldLabelOptions(),
 | 
			
		||||
      '#default_value' => $config['formatter']['label'],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['formatter']['type'] = [
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#title' => $this->t('Formatter'),
 | 
			
		||||
      '#options' => $this->getApplicablePluginOptions($this->getFieldDefinition()),
 | 
			
		||||
      '#required' => TRUE,
 | 
			
		||||
      '#default_value' => $config['formatter']['type'],
 | 
			
		||||
      '#ajax' => [
 | 
			
		||||
        'callback' => [static::class, 'formatterSettingsAjaxCallback'],
 | 
			
		||||
        'wrapper' => 'formatter-settings-wrapper',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Add the formatter settings to the form via AJAX.
 | 
			
		||||
    $form['formatter']['settings_wrapper'] = [
 | 
			
		||||
      '#prefix' => '<div id="formatter-settings-wrapper">',
 | 
			
		||||
      '#suffix' => '</div>',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API callback: builds the formatter settings elements.
 | 
			
		||||
   */
 | 
			
		||||
  public function formatterSettingsProcessCallback(array &$element, FormStateInterface $form_state, array &$complete_form) {
 | 
			
		||||
    if ($formatter = $this->getFormatter($element['#parents'], $form_state)) {
 | 
			
		||||
      $element['settings_wrapper']['settings'] = $formatter->settingsForm($complete_form, $form_state);
 | 
			
		||||
      $element['settings_wrapper']['settings']['#parents'] = array_merge($element['#parents'], ['settings']);
 | 
			
		||||
      $element['settings_wrapper']['third_party_settings'] = $this->thirdPartySettingsForm($formatter, $this->getFieldDefinition(), $complete_form, $form_state);
 | 
			
		||||
      $element['settings_wrapper']['third_party_settings']['#parents'] = array_merge($element['#parents'], ['third_party_settings']);
 | 
			
		||||
      FormHelper::rewriteStatesSelector($element['settings_wrapper'], "fields[$this->fieldName][settings_edit_form]", 'settings[formatter]');
 | 
			
		||||
 | 
			
		||||
      // Store the array parents for our element so that we can retrieve the
 | 
			
		||||
      // formatter settings in our AJAX callback.
 | 
			
		||||
      $form_state->set('field_block_array_parents', $element['#array_parents']);
 | 
			
		||||
    }
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds the formatter third party settings forms.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Field\FormatterInterface $plugin
 | 
			
		||||
   *   The formatter.
 | 
			
		||||
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
 | 
			
		||||
   *   The field definition.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The (entire) configuration form array.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The formatter third party settings form.
 | 
			
		||||
   */
 | 
			
		||||
  protected function thirdPartySettingsForm(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $settings_form = [];
 | 
			
		||||
    // Invoke hook_field_formatter_third_party_settings_form(), keying resulting
 | 
			
		||||
    // subforms by module name.
 | 
			
		||||
    $this->moduleHandler->invokeAllWith(
 | 
			
		||||
      'field_formatter_third_party_settings_form',
 | 
			
		||||
      function (callable $hook, string $module) use (&$settings_form, $plugin, $field_definition, $form, $form_state) {
 | 
			
		||||
        $settings_form[$module] = $hook(
 | 
			
		||||
          $plugin,
 | 
			
		||||
          $field_definition,
 | 
			
		||||
          EntityDisplayBase::CUSTOM_MODE,
 | 
			
		||||
          $form,
 | 
			
		||||
          $form_state,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
    return $settings_form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API callback: gets the layout settings elements.
 | 
			
		||||
   */
 | 
			
		||||
  public static function formatterSettingsAjaxCallback(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $formatter_array_parents = $form_state->get('field_block_array_parents');
 | 
			
		||||
    return NestedArray::getValue($form, array_merge($formatter_array_parents, ['settings_wrapper']));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function blockSubmit($form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->configuration['formatter'] = $form_state->getValue('formatter');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the field definition.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Field\FieldDefinitionInterface
 | 
			
		||||
   *   The field definition.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFieldDefinition() {
 | 
			
		||||
    if (empty($this->fieldDefinition)) {
 | 
			
		||||
      $field_definitions = $this->entityFieldManager->getFieldDefinitions($this->entityTypeId, $this->bundle);
 | 
			
		||||
      $this->fieldDefinition = $field_definitions[$this->fieldName];
 | 
			
		||||
    }
 | 
			
		||||
    return $this->fieldDefinition;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of applicable formatter options for a field.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
 | 
			
		||||
   *   The field definition.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of applicable formatter options.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\field_ui\Form\EntityDisplayFormBase::getApplicablePluginOptions()
 | 
			
		||||
   */
 | 
			
		||||
  protected function getApplicablePluginOptions(FieldDefinitionInterface $field_definition) {
 | 
			
		||||
    $options = $this->formatterManager->getOptions($field_definition->getType());
 | 
			
		||||
    $applicable_options = [];
 | 
			
		||||
    foreach ($options as $option => $label) {
 | 
			
		||||
      $plugin_class = DefaultFactory::getPluginClass($option, $this->formatterManager->getDefinition($option));
 | 
			
		||||
      if ($plugin_class::isApplicable($field_definition)) {
 | 
			
		||||
        $applicable_options[$option] = $label;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $applicable_options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the formatter object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $parents
 | 
			
		||||
   *   The #parents of the element representing the formatter.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current state of the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Field\FormatterInterface
 | 
			
		||||
   *   The formatter object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFormatter(array $parents, FormStateInterface $form_state) {
 | 
			
		||||
    // Use the processed values, if available.
 | 
			
		||||
    $configuration = NestedArray::getValue($form_state->getValues(), $parents);
 | 
			
		||||
    if (!$configuration) {
 | 
			
		||||
      // Next check the raw user input.
 | 
			
		||||
      $configuration = NestedArray::getValue($form_state->getUserInput(), $parents);
 | 
			
		||||
      if (!$configuration) {
 | 
			
		||||
        // If no user input exists, use the default values.
 | 
			
		||||
        $configuration = $this->getConfiguration()['formatter'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->formatterManager->getInstance([
 | 
			
		||||
      'configuration' => $configuration,
 | 
			
		||||
      'field_definition' => $this->getFieldDefinition(),
 | 
			
		||||
      'view_mode' => EntityDisplayBase::CUSTOM_MODE,
 | 
			
		||||
      'prepare' => TRUE,
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether there is a default value set on the field.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if default value set, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected function entityFieldHasDefaultValue(): bool {
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    $field = $entity->get($this->fieldName);
 | 
			
		||||
    $definition = $field->getFieldDefinition();
 | 
			
		||||
    if ($definition->getDefaultValue($entity)) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @todo Remove special handling of image fields after
 | 
			
		||||
    // https://www.drupal.org/project/drupal/issues/3005528.
 | 
			
		||||
    if ($definition->getType() !== 'image') {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $default_image = $definition->getSetting('default_image');
 | 
			
		||||
    // If we are dealing with a configurable field, look in both instance-level
 | 
			
		||||
    // and field-level settings.
 | 
			
		||||
    if (empty($default_image['uuid']) && ($definition instanceof FieldConfigInterface)) {
 | 
			
		||||
      $default_image = $definition->getFieldStorageDefinition()->getSetting('default_image');
 | 
			
		||||
    }
 | 
			
		||||
    return !empty($default_image['uuid']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										301
									
								
								web/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								web/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,301 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Block;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Access\RefinableDependentAccessInterface;
 | 
			
		||||
use Drupal\Core\Access\RefinableDependentAccessTrait;
 | 
			
		||||
use Drupal\Core\Block\Attribute\Block;
 | 
			
		||||
use Drupal\Core\Block\BlockBase;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
 | 
			
		||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\SubformStateInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\layout_builder\Plugin\Derivative\InlineBlockDeriver;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an inline block plugin type.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[Block(
 | 
			
		||||
   id: 'inline_block',
 | 
			
		||||
   admin_label: new TranslatableMarkup('Inline block'),
 | 
			
		||||
   category: new TranslatableMarkup('Inline blocks'),
 | 
			
		||||
   deriver: InlineBlockDeriver::class,
 | 
			
		||||
)]
 | 
			
		||||
class InlineBlock extends BlockBase implements ContainerFactoryPluginInterface, RefinableDependentAccessInterface {
 | 
			
		||||
 | 
			
		||||
  use RefinableDependentAccessTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block content entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\block_content\BlockContentInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $blockContent;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity display repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityDisplayRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether a new block is being created.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $isNew = TRUE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current user.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Session\AccountInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $currentUser;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new InlineBlock.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param mixed $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager service.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
 | 
			
		||||
   *   The entity display repository.
 | 
			
		||||
   * @param \Drupal\Core\Session\AccountInterface $current_user
 | 
			
		||||
   *   The current user.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository, AccountInterface $current_user) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->entityDisplayRepository = $entity_display_repository;
 | 
			
		||||
    $this->currentUser = $current_user;
 | 
			
		||||
    if (!empty($this->configuration['block_revision_id']) || !empty($this->configuration['block_serialized'])) {
 | 
			
		||||
      $this->isNew = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('entity_display.repository'),
 | 
			
		||||
      $container->get('current_user')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function defaultConfiguration() {
 | 
			
		||||
    return [
 | 
			
		||||
      'view_mode' => 'full',
 | 
			
		||||
      'block_id' => NULL,
 | 
			
		||||
      'block_revision_id' => NULL,
 | 
			
		||||
      'block_serialized' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function blockForm($form, FormStateInterface $form_state) {
 | 
			
		||||
    $block = $this->getEntity();
 | 
			
		||||
 | 
			
		||||
    // Add the entity form display in a process callback so that #parents can
 | 
			
		||||
    // be successfully propagated to field widgets.
 | 
			
		||||
    $form['block_form'] = [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#process' => [[static::class, 'processBlockForm']],
 | 
			
		||||
      '#block' => $block,
 | 
			
		||||
      '#access' => $this->currentUser->hasPermission('create and edit custom blocks'),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $options = $this->entityDisplayRepository->getViewModeOptionsByBundle('block_content', $block->bundle());
 | 
			
		||||
 | 
			
		||||
    $form['view_mode'] = [
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#options' => $options,
 | 
			
		||||
      '#title' => $this->t('View mode'),
 | 
			
		||||
      '#description' => $this->t('The view mode in which to render the block.'),
 | 
			
		||||
      '#default_value' => $this->configuration['view_mode'],
 | 
			
		||||
      '#access' => count($options) > 1,
 | 
			
		||||
    ];
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Process callback to insert a Content Block form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $element
 | 
			
		||||
   *   The containing element.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The containing element, with the Content Block form inserted.
 | 
			
		||||
   */
 | 
			
		||||
  public static function processBlockForm(array $element, FormStateInterface $form_state) {
 | 
			
		||||
    /** @var \Drupal\block_content\BlockContentInterface $block */
 | 
			
		||||
    $block = $element['#block'];
 | 
			
		||||
    EntityFormDisplay::collectRenderDisplay($block, 'edit')->buildForm($block, $element, $form_state);
 | 
			
		||||
    $element['revision_log']['#access'] = FALSE;
 | 
			
		||||
    $element['info']['#access'] = FALSE;
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function blockValidate($form, FormStateInterface $form_state) {
 | 
			
		||||
    $block_form = $form['block_form'];
 | 
			
		||||
    /** @var \Drupal\block_content\BlockContentInterface $block */
 | 
			
		||||
    $block = $block_form['#block'];
 | 
			
		||||
    $form_display = EntityFormDisplay::collectRenderDisplay($block, 'edit');
 | 
			
		||||
    $complete_form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
 | 
			
		||||
    $form_display->extractFormValues($block, $block_form, $complete_form_state);
 | 
			
		||||
    $form_display->validateFormValues($block, $block_form, $complete_form_state);
 | 
			
		||||
    // @todo Remove when https://www.drupal.org/project/drupal/issues/2948549 is closed.
 | 
			
		||||
    $form_state->setTemporaryValue('block_form_parents', $block_form['#parents']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function blockSubmit($form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->configuration['view_mode'] = $form_state->getValue('view_mode');
 | 
			
		||||
 | 
			
		||||
    // @todo Remove when https://www.drupal.org/project/drupal/issues/2948549 is closed.
 | 
			
		||||
    $block_form = NestedArray::getValue($form, $form_state->getTemporaryValue('block_form_parents'));
 | 
			
		||||
    /** @var \Drupal\block_content\BlockContentInterface $block */
 | 
			
		||||
    $block = $block_form['#block'];
 | 
			
		||||
    $form_display = EntityFormDisplay::collectRenderDisplay($block, 'edit');
 | 
			
		||||
    $complete_form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
 | 
			
		||||
    $form_display->extractFormValues($block, $block_form, $complete_form_state);
 | 
			
		||||
    $block->setInfo($this->configuration['label']);
 | 
			
		||||
    $this->configuration['block_serialized'] = serialize($block);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function blockAccess(AccountInterface $account) {
 | 
			
		||||
    if ($entity = $this->getEntity()) {
 | 
			
		||||
      return $entity->access('view', $account, TRUE);
 | 
			
		||||
    }
 | 
			
		||||
    return AccessResult::forbidden();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function build() {
 | 
			
		||||
    $block = $this->getEntity();
 | 
			
		||||
    return $this->entityTypeManager->getViewBuilder($block->getEntityTypeId())->view($block, $this->configuration['view_mode']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads or creates the block content entity of the block.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\block_content\BlockContentInterface
 | 
			
		||||
   *   The block content entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntity() {
 | 
			
		||||
    if (!isset($this->blockContent)) {
 | 
			
		||||
      if (!empty($this->configuration['block_serialized'])) {
 | 
			
		||||
        $this->blockContent = unserialize($this->configuration['block_serialized']);
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!empty($this->configuration['block_revision_id'])) {
 | 
			
		||||
        $entity = $this->entityTypeManager->getStorage('block_content')->loadRevision($this->configuration['block_revision_id']);
 | 
			
		||||
        $this->blockContent = $entity;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $this->blockContent = $this->entityTypeManager->getStorage('block_content')->create([
 | 
			
		||||
          'type' => $this->getDerivativeId(),
 | 
			
		||||
          'reusable' => FALSE,
 | 
			
		||||
        ]);
 | 
			
		||||
      }
 | 
			
		||||
      if ($this->blockContent instanceof RefinableDependentAccessInterface && $dependee = $this->getAccessDependency()) {
 | 
			
		||||
        $this->blockContent->setAccessDependency($dependee);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->blockContent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $form = parent::buildConfigurationForm($form, $form_state);
 | 
			
		||||
    if ($this->isNew) {
 | 
			
		||||
      // If the Content Block is new then don't provide a default label.
 | 
			
		||||
      unset($form['label']['#default_value']);
 | 
			
		||||
    }
 | 
			
		||||
    $form['label']['#description'] = $this->t('The title of the block as shown to the user.');
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves the block_content entity for this plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $new_revision
 | 
			
		||||
   *   Whether to create new revision, if the block was modified.
 | 
			
		||||
   * @param bool $duplicate_block
 | 
			
		||||
   *   Whether to duplicate the "block_content" entity.
 | 
			
		||||
   */
 | 
			
		||||
  public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE) {
 | 
			
		||||
    /** @var \Drupal\block_content\BlockContentInterface $block */
 | 
			
		||||
    $block = NULL;
 | 
			
		||||
    if (!empty($this->configuration['block_serialized'])) {
 | 
			
		||||
      $block = unserialize($this->configuration['block_serialized']);
 | 
			
		||||
    }
 | 
			
		||||
    if ($duplicate_block) {
 | 
			
		||||
      if (empty($block) && !empty($this->configuration['block_revision_id'])) {
 | 
			
		||||
        $block = $this->entityTypeManager->getStorage('block_content')->loadRevision($this->configuration['block_revision_id']);
 | 
			
		||||
      }
 | 
			
		||||
      if ($block) {
 | 
			
		||||
        $block = $block->createDuplicate();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($block) {
 | 
			
		||||
      // Since the content block is only set if it was unserialized, the flag
 | 
			
		||||
      // will only effect blocks which were modified or serialized originally.
 | 
			
		||||
      if ($new_revision) {
 | 
			
		||||
        $block->setNewRevision();
 | 
			
		||||
      }
 | 
			
		||||
      $block->save();
 | 
			
		||||
      $this->configuration['block_id'] = $block->id();
 | 
			
		||||
      $this->configuration['block_revision_id'] = $block->getRevisionId();
 | 
			
		||||
      $this->configuration['block_serialized'] = NULL;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,156 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\ConfigAction;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Uuid\UuidInterface;
 | 
			
		||||
use Drupal\Core\Config\Action\Attribute\ConfigAction;
 | 
			
		||||
use Drupal\Core\Config\Action\ConfigActionException;
 | 
			
		||||
use Drupal\Core\Config\Action\ConfigActionPluginInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\layout_builder\Plugin\ConfigAction\Deriver\AddComponentDeriver;
 | 
			
		||||
use Drupal\Core\Config\ConfigManagerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionComponent;
 | 
			
		||||
use Drupal\layout_builder\SectionListInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds a component to a layout builder section.
 | 
			
		||||
 *
 | 
			
		||||
 * An example of using this in a recipe's config actions would be:
 | 
			
		||||
 * @code
 | 
			
		||||
 * dashboard.dashboard.welcome:
 | 
			
		||||
 *   addComponentToLayout:
 | 
			
		||||
 *     section: 0
 | 
			
		||||
 *     position: 4
 | 
			
		||||
 *     component:
 | 
			
		||||
 *       region:
 | 
			
		||||
 *         layout_twocol_section: 'second'
 | 
			
		||||
 *       default_region: content
 | 
			
		||||
 *       configuration:
 | 
			
		||||
 *         id: dashboard_text_block
 | 
			
		||||
 *         label: 'My new dashboard block'
 | 
			
		||||
 *         label_display: 'visible'
 | 
			
		||||
 *         provider: 'dashboard'
 | 
			
		||||
 *         context_mapping: { }
 | 
			
		||||
 *         text:
 | 
			
		||||
 *           value: '<p>My new block text</p>'
 | 
			
		||||
 *           format: 'basic_html'
 | 
			
		||||
 * @endcode
 | 
			
		||||
 * This will add a component to a layout region, given by the `section` index.
 | 
			
		||||
 * The `position` will determine where it will be inserted, starting at 0. If is
 | 
			
		||||
 * higher than the actual number of components in the region, it will be placed
 | 
			
		||||
 * last.
 | 
			
		||||
 * The `component` defines the actual component we are adding to the layout.
 | 
			
		||||
 * Sections can have multiple regions. A `region` mapping will determine which
 | 
			
		||||
 * region to use based on the id of the layout. If no matching is found, it will
 | 
			
		||||
 * use the `default_region`.
 | 
			
		||||
 * The `configuration` array will include the plugin configuration, including a
 | 
			
		||||
 * mandatory `id` for the plugin ID. It should validate against the config
 | 
			
		||||
 * schema of the plugin.
 | 
			
		||||
 * The `additional` array will be copied as is, as that is ignored by config
 | 
			
		||||
 * schema.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This API is experimental.
 | 
			
		||||
 */
 | 
			
		||||
#[ConfigAction(
 | 
			
		||||
  id: 'add_layout_component',
 | 
			
		||||
  admin_label: new TranslatableMarkup('Add component(s) to layout'),
 | 
			
		||||
  deriver: AddComponentDeriver::class,
 | 
			
		||||
)]
 | 
			
		||||
final class AddComponent implements ConfigActionPluginInterface, ContainerFactoryPluginInterface {
 | 
			
		||||
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    private readonly ConfigManagerInterface $configManager,
 | 
			
		||||
    private readonly UuidInterface $uuidGenerator,
 | 
			
		||||
    private readonly string $pluginId,
 | 
			
		||||
    private readonly bool $multiple,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
 | 
			
		||||
    assert(is_array($plugin_definition));
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get(ConfigManagerInterface::class),
 | 
			
		||||
      $container->get(UuidInterface::class),
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition['multiple'],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function apply(string $configName, mixed $values): void {
 | 
			
		||||
    assert(is_array($values));
 | 
			
		||||
 | 
			
		||||
    if ($this->multiple) {
 | 
			
		||||
      assert(array_is_list($values));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $values = [$values];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity = $this->configManager->loadConfigEntityByName($configName);
 | 
			
		||||
    if (!$entity instanceof SectionListInterface) {
 | 
			
		||||
      throw new ConfigActionException("No entity found for applying the addComponentToLayout action.");
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($values as $value) {
 | 
			
		||||
      $this->applySingle($entity, $value);
 | 
			
		||||
    }
 | 
			
		||||
    $entity->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a single component to the layout.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionListInterface $entity
 | 
			
		||||
   *   The entity with a layout.
 | 
			
		||||
   * @param array $value
 | 
			
		||||
   *   The data for the config action.
 | 
			
		||||
   */
 | 
			
		||||
  private function applySingle(SectionListInterface $entity, array $value): void {
 | 
			
		||||
    $section_delta = $value['section'];
 | 
			
		||||
    $position = $value['position'];
 | 
			
		||||
 | 
			
		||||
    assert(is_int($section_delta));
 | 
			
		||||
    assert(is_int($position));
 | 
			
		||||
 | 
			
		||||
    $section = $entity->getSection($section_delta);
 | 
			
		||||
    $component = $value['component'];
 | 
			
		||||
    $region = $component['default_region'] ?? NULL;
 | 
			
		||||
    if (array_key_exists('region', $component) && is_array($component['region'])) {
 | 
			
		||||
      // Since the recipe author might not know ahead of time what layout the
 | 
			
		||||
      // section is using, they should supply a map whose keys are layout IDs
 | 
			
		||||
      // and values are region names, so we know where to place this component.
 | 
			
		||||
      // If the section layout ID is not in the map, they should supply the
 | 
			
		||||
      // name of a fallback region. If all that fails, give up with an
 | 
			
		||||
      // exception.
 | 
			
		||||
      $region = $component['region'][$section->getLayoutId()] ??
 | 
			
		||||
        $component['default_region'] ??
 | 
			
		||||
        throw new ConfigActionException("Cannot determine which region of the section to place this component into, because no default region was provided.");
 | 
			
		||||
    }
 | 
			
		||||
    if ($region === NULL) {
 | 
			
		||||
      throw new ConfigActionException("Cannot determine which region of the section to place this component into, because no region was provided.");
 | 
			
		||||
    }
 | 
			
		||||
    if (!isset($value['component']['configuration']) || !isset($value['component']['configuration']['id'])) {
 | 
			
		||||
      throw new ConfigActionException("Cannot determine the component configuration, or misses a plugin ID.");
 | 
			
		||||
    }
 | 
			
		||||
    // If no weight were set, there would be a warning. So we set a
 | 
			
		||||
    // default, which will be overridden in insertComponent anyway.
 | 
			
		||||
    // We also need to generate the UUID here, or it could be null.
 | 
			
		||||
    $uuid = $component['uuid'] ?? $this->uuidGenerator->generate();
 | 
			
		||||
    $component = new SectionComponent($uuid, $region, $component['configuration'], $component['additional'] ?? []);
 | 
			
		||||
    // If the position is higher than the number of components, just put it last
 | 
			
		||||
    // instead of failing.
 | 
			
		||||
    $position = min($position, count($section->getComponentsByRegion($region)));
 | 
			
		||||
    $section->insertComponent($position, $component);
 | 
			
		||||
    $entity->setSection($section_delta, $section);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\ConfigAction\Deriver;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionListInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This API is experimental.
 | 
			
		||||
 */
 | 
			
		||||
final class AddComponentDeriver extends DeriverBase implements ContainerDeriverInterface {
 | 
			
		||||
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    private readonly EntityTypeManagerInterface $entityTypeManager,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, $base_plugin_id): static {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get(EntityTypeManagerInterface::class),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDerivativeDefinitions($base_plugin_definition): array {
 | 
			
		||||
    $entity_types = [];
 | 
			
		||||
    foreach ($this->entityTypeManager->getDefinitions() as $entity_type) {
 | 
			
		||||
      if ($entity_type->entityClassImplements(ConfigEntityInterface::class) && $entity_type->entityClassImplements(SectionListInterface::class)) {
 | 
			
		||||
        $entity_types[] = $entity_type->id();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $base_plugin_definition['entity_types'] = $entity_types;
 | 
			
		||||
    $this->derivatives['addComponentToLayout'] = $base_plugin_definition + [
 | 
			
		||||
      'multiple' => FALSE,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->derivatives['addComponentsToLayout'] = $base_plugin_definition + [
 | 
			
		||||
      'multiple' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
    return $this->derivatives;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\DataType;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\Core\TypedData\Attribute\DataType;
 | 
			
		||||
use Drupal\Core\TypedData\TypedData;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a data type wrapping \Drupal\layout_builder\Section.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[DataType(
 | 
			
		||||
  id: "layout_section",
 | 
			
		||||
  label: new TranslatableMarkup("Layout Section"),
 | 
			
		||||
  description: new TranslatableMarkup("A layout section"),
 | 
			
		||||
)]
 | 
			
		||||
class SectionData extends TypedData {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section object.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\Section
 | 
			
		||||
   */
 | 
			
		||||
  protected $value;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setValue($value, $notify = TRUE) {
 | 
			
		||||
    if (is_array($value)) {
 | 
			
		||||
      $value = Section::fromArray($value);
 | 
			
		||||
    }
 | 
			
		||||
    if ($value && !$value instanceof Section) {
 | 
			
		||||
      throw new \InvalidArgumentException(sprintf('Value assigned to "%s" is not a valid section', $this->getName()));
 | 
			
		||||
    }
 | 
			
		||||
    parent::setValue($value, $notify);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,167 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Derivative;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
 | 
			
		||||
use Drupal\Component\Plugin\PluginBase;
 | 
			
		||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeRepositoryInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides entity field block definitions for every field.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin derivers are internal.
 | 
			
		||||
 */
 | 
			
		||||
class ExtraFieldBlockDeriver extends DeriverBase implements ContainerDeriverInterface {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity field manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityFieldManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type bundle info.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeBundleInfo;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs new FieldBlockDeriver.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
 | 
			
		||||
   *   The entity field manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
 | 
			
		||||
   *   The entity type bundle info.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository
 | 
			
		||||
   *   The entity type repository.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
 | 
			
		||||
   *   The module handler.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    EntityFieldManagerInterface $entity_field_manager,
 | 
			
		||||
    EntityTypeManagerInterface $entity_type_manager,
 | 
			
		||||
    EntityTypeBundleInfoInterface $entity_type_bundle_info,
 | 
			
		||||
    EntityTypeRepositoryInterface $entity_type_repository,
 | 
			
		||||
    protected ModuleHandlerInterface $moduleHandler,
 | 
			
		||||
  ) {
 | 
			
		||||
    $this->entityFieldManager = $entity_field_manager;
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
 | 
			
		||||
    $this->entityTypeRepository = $entity_type_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, $base_plugin_id) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_field.manager'),
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('entity_type.bundle.info'),
 | 
			
		||||
      $container->get('entity_type.repository'),
 | 
			
		||||
      $container->get('module_handler')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDerivativeDefinitions($base_plugin_definition) {
 | 
			
		||||
    $entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels();
 | 
			
		||||
    $enabled_bundle_ids = $this->bundleIdsWithLayoutBuilderDisplays();
 | 
			
		||||
    $expose_all_fields = $this->moduleHandler->moduleExists('layout_builder_expose_all_field_blocks');
 | 
			
		||||
    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
 | 
			
		||||
      // Only process fieldable entity types.
 | 
			
		||||
      if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // If not loading everything, skip entity types that aren't included.
 | 
			
		||||
      if (!$expose_all_fields && !isset($enabled_bundle_ids[$entity_type_id])) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
 | 
			
		||||
      foreach ($bundles as $bundle_id => $bundle) {
 | 
			
		||||
        // If not loading everything, skip bundle types that aren't included.
 | 
			
		||||
        if (!$expose_all_fields && !isset($enabled_bundle_ids[$entity_type_id][$bundle_id])) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle_id);
 | 
			
		||||
        // Skip bundles without any extra fields.
 | 
			
		||||
        if (empty($extra_fields['display'])) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($extra_fields['display'] as $extra_field_id => $extra_field) {
 | 
			
		||||
          $derivative = $base_plugin_definition;
 | 
			
		||||
 | 
			
		||||
          $derivative['category'] = $this->t('@entity fields', ['@entity' => $entity_type_labels[$entity_type_id]]);
 | 
			
		||||
 | 
			
		||||
          $derivative['admin_label'] = $extra_field['label'];
 | 
			
		||||
 | 
			
		||||
          $context_definition = EntityContextDefinition::fromEntityType($entity_type)
 | 
			
		||||
            ->addConstraint('Bundle', [$bundle_id]);
 | 
			
		||||
          $derivative['context_definitions'] = [
 | 
			
		||||
            'entity' => $context_definition,
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
          $derivative_id = $entity_type_id . PluginBase::DERIVATIVE_SEPARATOR . $bundle_id . PluginBase::DERIVATIVE_SEPARATOR . $extra_field_id;
 | 
			
		||||
          $this->derivatives[$derivative_id] = $derivative;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->derivatives;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the list of entity type and bundle tuples with layout builder enabled.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A structured array with entity type as first key, bundle as second.
 | 
			
		||||
   */
 | 
			
		||||
  protected function bundleIdsWithLayoutBuilderDisplays(): array {
 | 
			
		||||
    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */
 | 
			
		||||
    $displays = $this->entityTypeManager->getStorage('entity_view_display')->loadByProperties([
 | 
			
		||||
      'third_party_settings.layout_builder.enabled' => TRUE,
 | 
			
		||||
    ]);
 | 
			
		||||
    $layout_bundles = [];
 | 
			
		||||
    foreach ($displays as $display) {
 | 
			
		||||
      $bundle = $display->getTargetBundle();
 | 
			
		||||
      $layout_bundles[$display->getTargetEntityTypeId()][$bundle] = $bundle;
 | 
			
		||||
    }
 | 
			
		||||
    return $layout_bundles;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,206 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Derivative;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
 | 
			
		||||
use Drupal\Component\Plugin\PluginBase;
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeRepositoryInterface;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Field\FieldConfigInterface;
 | 
			
		||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
 | 
			
		||||
use Drupal\Core\Field\FormatterPluginManager;
 | 
			
		||||
use Drupal\Core\Logger\LoggerChannelTrait;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides entity field block definitions for every field.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin derivers are internal.
 | 
			
		||||
 */
 | 
			
		||||
class FieldBlockDeriver extends DeriverBase implements ContainerDeriverInterface {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
  use LoggerChannelTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity field manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityFieldManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fieldTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The formatter manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Field\FormatterPluginManager
 | 
			
		||||
   */
 | 
			
		||||
  protected $formatterManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs new FieldBlockDeriver.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository
 | 
			
		||||
   *   The entity type repository.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
 | 
			
		||||
   *   The entity field manager.
 | 
			
		||||
   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
 | 
			
		||||
   *   The field type manager.
 | 
			
		||||
   * @param \Drupal\Core\Field\FormatterPluginManager $formatter_manager
 | 
			
		||||
   *   The formatter manager.
 | 
			
		||||
   * @param \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entityViewDisplayStorage
 | 
			
		||||
   *   The entity view display storage.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
 | 
			
		||||
   *   The module handler.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    EntityTypeRepositoryInterface $entity_type_repository,
 | 
			
		||||
    EntityFieldManagerInterface $entity_field_manager,
 | 
			
		||||
    FieldTypePluginManagerInterface $field_type_manager,
 | 
			
		||||
    FormatterPluginManager $formatter_manager,
 | 
			
		||||
    protected ConfigEntityStorageInterface $entityViewDisplayStorage,
 | 
			
		||||
    protected ModuleHandlerInterface $moduleHandler,
 | 
			
		||||
  ) {
 | 
			
		||||
    $this->entityTypeRepository = $entity_type_repository;
 | 
			
		||||
    $this->entityFieldManager = $entity_field_manager;
 | 
			
		||||
    $this->fieldTypeManager = $field_type_manager;
 | 
			
		||||
    $this->formatterManager = $formatter_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, $base_plugin_id) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.repository'),
 | 
			
		||||
      $container->get('entity_field.manager'),
 | 
			
		||||
      $container->get('plugin.manager.field.field_type'),
 | 
			
		||||
      $container->get('plugin.manager.field.formatter'),
 | 
			
		||||
      $container->get('entity_type.manager')->getStorage('entity_view_display'),
 | 
			
		||||
      $container->get('module_handler')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDerivativeDefinitions($base_plugin_definition) {
 | 
			
		||||
    $entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels();
 | 
			
		||||
    foreach ($this->getFieldMap() as $entity_type_id => $entity_field_map) {
 | 
			
		||||
      foreach ($entity_field_map as $field_name => $field_info) {
 | 
			
		||||
        // Skip fields without any formatters.
 | 
			
		||||
        $options = $this->formatterManager->getOptions($field_info['type']);
 | 
			
		||||
        if (empty($options)) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($field_info['bundles'] as $bundle) {
 | 
			
		||||
          $derivative = $base_plugin_definition;
 | 
			
		||||
          $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
 | 
			
		||||
          if (empty($field_definitions[$field_name])) {
 | 
			
		||||
            $this->getLogger('field')->error('Field %field_name exists but is missing a corresponding field definition and may be misconfigured.', ['%field_name' => "$entity_type_id.$bundle.$field_name"]);
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
          $field_definition = $field_definitions[$field_name];
 | 
			
		||||
 | 
			
		||||
          // Store the default formatter on the definition.
 | 
			
		||||
          $derivative['default_formatter'] = '';
 | 
			
		||||
          $field_type_definition = $this->fieldTypeManager->getDefinition($field_info['type']);
 | 
			
		||||
          if (isset($field_type_definition['default_formatter'])) {
 | 
			
		||||
            $derivative['default_formatter'] = $field_type_definition['default_formatter'];
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          $derivative['category'] = $this->t('@entity fields', ['@entity' => $entity_type_labels[$entity_type_id]]);
 | 
			
		||||
 | 
			
		||||
          $derivative['admin_label'] = $field_definition->getLabel();
 | 
			
		||||
 | 
			
		||||
          // Add a dependency on the field if it is configurable.
 | 
			
		||||
          if ($field_definition instanceof FieldConfigInterface) {
 | 
			
		||||
            $derivative['config_dependencies'][$field_definition->getConfigDependencyKey()][] = $field_definition->getConfigDependencyName();
 | 
			
		||||
          }
 | 
			
		||||
          // For any field that is not display configurable, mark it as
 | 
			
		||||
          // unavailable to place in the block UI.
 | 
			
		||||
          $derivative['_block_ui_hidden'] = !$field_definition->isDisplayConfigurable('view');
 | 
			
		||||
 | 
			
		||||
          $context_definition = EntityContextDefinition::fromEntityTypeId($entity_type_id)->setLabel($entity_type_labels[$entity_type_id]);
 | 
			
		||||
          $context_definition->addConstraint('Bundle', [$bundle]);
 | 
			
		||||
          $derivative['context_definitions'] = [
 | 
			
		||||
            'entity' => $context_definition,
 | 
			
		||||
            'view_mode' => new ContextDefinition('string'),
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
          $derivative_id = $entity_type_id . PluginBase::DERIVATIVE_SEPARATOR . $bundle . PluginBase::DERIVATIVE_SEPARATOR . $field_name;
 | 
			
		||||
          $this->derivatives[$derivative_id] = $derivative;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->derivatives;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the entity field map for deriving block definitions.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The entity field map.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Entity\EntityFieldManagerInterface::getFieldMap()
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFieldMap(): array {
 | 
			
		||||
    $field_map = $this->entityFieldManager->getFieldMap();
 | 
			
		||||
 | 
			
		||||
    // If all fields are exposed as field blocks, just return the field map
 | 
			
		||||
    // without any further processing.
 | 
			
		||||
    if ($this->moduleHandler->moduleExists('layout_builder_expose_all_field_blocks')) {
 | 
			
		||||
      return $field_map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Load all entity view displays which are using Layout Builder.
 | 
			
		||||
    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */
 | 
			
		||||
    $displays = $this->entityViewDisplayStorage->loadByProperties([
 | 
			
		||||
      'third_party_settings.layout_builder.enabled' => TRUE,
 | 
			
		||||
    ]);
 | 
			
		||||
    $layout_bundles = [];
 | 
			
		||||
    foreach ($displays as $display) {
 | 
			
		||||
      $bundle = $display->getTargetBundle();
 | 
			
		||||
      $layout_bundles[$display->getTargetEntityTypeId()][$bundle] = $bundle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Process $field_map, removing any entity types which are not using Layout
 | 
			
		||||
    // Builder.
 | 
			
		||||
    $field_map = array_intersect_key($field_map, $layout_bundles);
 | 
			
		||||
 | 
			
		||||
    foreach ($field_map as $entity_type_id => $fields) {
 | 
			
		||||
      foreach ($fields as $field_name => $field_info) {
 | 
			
		||||
        $field_map[$entity_type_id][$field_name]['bundles'] = array_intersect($field_info['bundles'], $layout_bundles[$entity_type_id]);
 | 
			
		||||
 | 
			
		||||
        // If no bundles are using Layout Builder, remove this field from the
 | 
			
		||||
        // field map.
 | 
			
		||||
        if (empty($field_info['bundles'])) {
 | 
			
		||||
          unset($field_map[$entity_type_id][$field_name]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $field_map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Derivative;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides inline block plugin definitions for all block types.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin derivers are internal.
 | 
			
		||||
 */
 | 
			
		||||
class InlineBlockDeriver extends DeriverBase implements ContainerDeriverInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a BlockContentDeriver object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, $base_plugin_id) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDerivativeDefinitions($base_plugin_definition) {
 | 
			
		||||
    $this->derivatives = [];
 | 
			
		||||
    if ($this->entityTypeManager->hasDefinition('block_content_type')) {
 | 
			
		||||
      $block_content_types = $this->entityTypeManager->getStorage('block_content_type')->loadMultiple();
 | 
			
		||||
      foreach ($block_content_types as $id => $type) {
 | 
			
		||||
        $this->derivatives[$id] = $base_plugin_definition;
 | 
			
		||||
        $this->derivatives[$id]['admin_label'] = $type->label();
 | 
			
		||||
        $this->derivatives[$id]['config_dependencies'][$type->getConfigDependencyKey()][] = $type->getConfigDependencyName();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return parent::getDerivativeDefinitions($base_plugin_definition);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Derivative;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\layout_builder\Plugin\SectionStorage\SectionStorageLocalTaskProviderInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides local task definitions for the layout builder user interface.
 | 
			
		||||
 *
 | 
			
		||||
 * @todo Remove this in https://www.drupal.org/project/drupal/issues/2936655.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin derivers are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderLocalTaskDeriver extends DeriverBase implements ContainerDeriverInterface {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LayoutBuilderLocalTaskDeriver.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
 | 
			
		||||
   *   The section storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager, SectionStorageManagerInterface $section_storage_manager) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->sectionStorageManager = $section_storage_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, $base_plugin_id) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('plugin.manager.layout_builder.section_storage')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDerivativeDefinitions($base_plugin_definition) {
 | 
			
		||||
    foreach ($this->sectionStorageManager->getDefinitions() as $plugin_id => $definition) {
 | 
			
		||||
      $section_storage = $this->sectionStorageManager->loadEmpty($plugin_id);
 | 
			
		||||
      if ($section_storage instanceof SectionStorageLocalTaskProviderInterface) {
 | 
			
		||||
        $this->derivatives += $section_storage->buildLocalTasks($base_plugin_definition);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->derivatives;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,96 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Field\FieldType;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Field\Attribute\FieldType;
 | 
			
		||||
use Drupal\Core\Field\FieldDefinitionInterface;
 | 
			
		||||
use Drupal\Core\Field\FieldItemBase;
 | 
			
		||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\Core\TypedData\DataDefinition;
 | 
			
		||||
use Drupal\layout_builder\Field\LayoutSectionItemList;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin implementation of the 'layout_section' field type.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 *
 | 
			
		||||
 * @property \Drupal\layout_builder\Section $section
 | 
			
		||||
 */
 | 
			
		||||
#[FieldType(
 | 
			
		||||
  id: "layout_section",
 | 
			
		||||
  label: new TranslatableMarkup("Layout Section"),
 | 
			
		||||
  description: new TranslatableMarkup("Layout Section"),
 | 
			
		||||
  no_ui: TRUE,
 | 
			
		||||
  list_class: LayoutSectionItemList::class,
 | 
			
		||||
  cardinality: FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
 | 
			
		||||
)]
 | 
			
		||||
class LayoutSectionItem extends FieldItemBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
 | 
			
		||||
    $properties['section'] = DataDefinition::create('layout_section')
 | 
			
		||||
      ->setLabel(new TranslatableMarkup('Layout Section'))
 | 
			
		||||
      ->setRequired(FALSE);
 | 
			
		||||
 | 
			
		||||
    return $properties;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __get($name) {
 | 
			
		||||
    // @todo \Drupal\Core\Field\FieldItemBase::__get() does not return default
 | 
			
		||||
    //   values for un-instantiated properties. This will forcibly instantiate
 | 
			
		||||
    //   all properties with the side-effect of a performance hit, resolve
 | 
			
		||||
    //   properly in https://www.drupal.org/node/2413471.
 | 
			
		||||
    $this->getProperties();
 | 
			
		||||
 | 
			
		||||
    return parent::__get($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function mainPropertyName() {
 | 
			
		||||
    return 'section';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
 | 
			
		||||
    $schema = [
 | 
			
		||||
      'columns' => [
 | 
			
		||||
        'section' => [
 | 
			
		||||
          'type' => 'blob',
 | 
			
		||||
          'size' => 'normal',
 | 
			
		||||
          'serialize' => TRUE,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $schema;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
 | 
			
		||||
    // @todo Expand this in https://www.drupal.org/node/2912331.
 | 
			
		||||
    $values['section'] = new Section('layout_onecol');
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isEmpty() {
 | 
			
		||||
    return empty($this->section);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,63 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Field\FieldWidget;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Field\Attribute\FieldWidget;
 | 
			
		||||
use Drupal\Core\Field\FieldItemListInterface;
 | 
			
		||||
use Drupal\Core\Field\WidgetBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A widget to display the layout form.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[FieldWidget(
 | 
			
		||||
  id: 'layout_builder_widget',
 | 
			
		||||
  label: new TranslatableMarkup('Layout Builder Widget'),
 | 
			
		||||
  description: new TranslatableMarkup('A field widget for Layout Builder.'),
 | 
			
		||||
  field_types: ['layout_section'],
 | 
			
		||||
  multiple_values: TRUE,
 | 
			
		||||
)]
 | 
			
		||||
class LayoutBuilderWidget extends WidgetBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $element += [
 | 
			
		||||
      '#type' => 'layout_builder',
 | 
			
		||||
      '#section_storage' => $this->getSectionStorage($form_state),
 | 
			
		||||
    ];
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
 | 
			
		||||
    // @todo This isn't resilient to being set twice, during validation and
 | 
			
		||||
    //   save https://www.drupal.org/project/drupal/issues/2833682.
 | 
			
		||||
    if (!$form_state->isValidationComplete()) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $items->setValue($this->getSectionStorage($form_state)->getSections());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   *   The section storage loaded from the tempstore.
 | 
			
		||||
   */
 | 
			
		||||
  private function getSectionStorage(FormStateInterface $form_state) {
 | 
			
		||||
    return $form_state->getFormObject()->getSectionStorage();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Layout;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Layout\Attribute\Layout;
 | 
			
		||||
use Drupal\Core\Layout\LayoutDefault;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a layout plugin that produces no output.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Field\LayoutSectionItemList::removeSection()
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionListTrait::addBlankSection()
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionListTrait::hasBlankSection()
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This layout plugin is intended for internal use by Layout Builder only.
 | 
			
		||||
 */
 | 
			
		||||
#[Layout(
 | 
			
		||||
  id: 'layout_builder_blank',
 | 
			
		||||
  label: new TranslatableMarkup('Blank Layout'),
 | 
			
		||||
  category: new TranslatableMarkup('Blank Layout'),
 | 
			
		||||
)]
 | 
			
		||||
class BlankLayout extends LayoutDefault {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function build(array $regions) {
 | 
			
		||||
    // Return no output.
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,83 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Layout;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Layout\LayoutDefault;
 | 
			
		||||
use Drupal\Core\Plugin\PluginFormInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class of layouts with configurable widths.
 | 
			
		||||
 */
 | 
			
		||||
abstract class MultiWidthLayoutBase extends LayoutDefault implements PluginFormInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function defaultConfiguration() {
 | 
			
		||||
    $configuration = parent::defaultConfiguration();
 | 
			
		||||
    return $configuration + [
 | 
			
		||||
      'column_widths' => $this->getDefaultWidth(),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $form['column_widths'] = [
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#title' => $this->t('Column widths'),
 | 
			
		||||
      '#default_value' => $this->configuration['column_widths'],
 | 
			
		||||
      '#options' => $this->getWidthOptions(),
 | 
			
		||||
      '#description' => $this->t('Choose the column widths for this layout.'),
 | 
			
		||||
    ];
 | 
			
		||||
    return parent::buildConfigurationForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    parent::submitConfigurationForm($form, $form_state);
 | 
			
		||||
    $this->configuration['column_widths'] = $form_state->getValue('column_widths');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function build(array $regions) {
 | 
			
		||||
    $build = parent::build($regions);
 | 
			
		||||
    $build['#attributes']['class'] = [
 | 
			
		||||
      'layout',
 | 
			
		||||
      $this->getPluginDefinition()->getTemplate(),
 | 
			
		||||
      $this->getPluginDefinition()->getTemplate() . '--' . $this->configuration['column_widths'],
 | 
			
		||||
    ];
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the width options for the configuration form.
 | 
			
		||||
   *
 | 
			
		||||
   * The first option will be used as the default 'column_widths' configuration
 | 
			
		||||
   * value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   The width options array where the keys are strings that will be added to
 | 
			
		||||
   *   the CSS classes and the values are the human readable labels.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function getWidthOptions();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides a default value for the width options.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A key from the array returned by ::getWidthOptions().
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDefaultWidth() {
 | 
			
		||||
    // Return the first available key from the list of options.
 | 
			
		||||
    $width_classes = array_keys($this->getWidthOptions());
 | 
			
		||||
    return array_shift($width_classes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Layout;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configurable three column layout plugin class.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class ThreeColumnLayout extends MultiWidthLayoutBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getWidthOptions() {
 | 
			
		||||
    return [
 | 
			
		||||
      '25-50-25' => '25%/50%/25%',
 | 
			
		||||
      '33-34-33' => '33%/34%/33%',
 | 
			
		||||
      '25-25-50' => '25%/25%/50%',
 | 
			
		||||
      '50-25-25' => '50%/25%/25%',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDefaultWidth() {
 | 
			
		||||
    return '33-34-33';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,33 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\Layout;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configurable two column layout plugin class.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class TwoColumnLayout extends MultiWidthLayoutBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getWidthOptions() {
 | 
			
		||||
    return [
 | 
			
		||||
      '50-50' => '50%/50%',
 | 
			
		||||
      '33-67' => '33%/67%',
 | 
			
		||||
      '67-33' => '67%/33%',
 | 
			
		||||
      '25-75' => '25%/75%',
 | 
			
		||||
      '75-25' => '75%/25%',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDefaultWidth() {
 | 
			
		||||
    return '50-50';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,416 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\SectionStorage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface;
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\Context\EntityContext;
 | 
			
		||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\layout_builder\Attribute\SectionStorage;
 | 
			
		||||
use Drupal\layout_builder\DefaultsSectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\Entity\SampleEntityGeneratorInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the 'defaults' section storage type.
 | 
			
		||||
 *
 | 
			
		||||
 * DefaultsSectionStorage uses a positive weight because:
 | 
			
		||||
 * - It must be picked after
 | 
			
		||||
 *   \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage.
 | 
			
		||||
 * - The default weight is 0, so other custom implementations will also take
 | 
			
		||||
 *   precedence unless otherwise specified.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[SectionStorage(id: "defaults", weight: 20, context_definitions: [
 | 
			
		||||
  "display" => new EntityContextDefinition(
 | 
			
		||||
    data_type: "entity_view_display",
 | 
			
		||||
    label: new TranslatableMarkup("Entity view display"),
 | 
			
		||||
  ),
 | 
			
		||||
  'view_mode' => new ContextDefinition(
 | 
			
		||||
    data_type: 'string',
 | 
			
		||||
    label: new TranslatableMarkup("View mode"),
 | 
			
		||||
    default_value: "default",
 | 
			
		||||
  ),
 | 
			
		||||
])]
 | 
			
		||||
class DefaultsSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, DefaultsSectionStorageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type bundle info.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeBundleInfo;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The sample entity generator.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\Entity\SampleEntityGeneratorInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sampleEntityGenerator;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, SampleEntityGeneratorInterface $sample_entity_generator) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
 | 
			
		||||
    $this->sampleEntityGenerator = $sample_entity_generator;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('entity_type.bundle.info'),
 | 
			
		||||
      $container->get('layout_builder.sample_entity_generator')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getSectionList() {
 | 
			
		||||
    return $this->getContextValue('display');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the entity storing the defaults.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface
 | 
			
		||||
   *   The entity storing the defaults.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDisplay() {
 | 
			
		||||
    return $this->getSectionList();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorageId() {
 | 
			
		||||
    return $this->getDisplay()->id();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getRedirectUrl() {
 | 
			
		||||
    return Url::fromRoute("entity.entity_view_display.{$this->getDisplay()->getTargetEntityTypeId()}.view_mode", $this->getRouteParameters());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getLayoutBuilderUrl($rel = 'view') {
 | 
			
		||||
    return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.$rel", $this->getRouteParameters());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the route parameters needed to generate a URL for this object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed[]
 | 
			
		||||
   *   An associative array of parameter names and values.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getRouteParameters() {
 | 
			
		||||
    $display = $this->getDisplay();
 | 
			
		||||
    $entity_type = $this->entityTypeManager->getDefinition($display->getTargetEntityTypeId());
 | 
			
		||||
    $bundle_parameter_key = $entity_type->getBundleEntityType() ?: 'bundle';
 | 
			
		||||
    return [
 | 
			
		||||
      $bundle_parameter_key => $display->getTargetBundle(),
 | 
			
		||||
      'view_mode_name' => $display->getMode(),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildRoutes(RouteCollection $collection) {
 | 
			
		||||
    if (!\Drupal::moduleHandler()->moduleExists('field_ui')) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
 | 
			
		||||
      // Try to get the route from the current collection.
 | 
			
		||||
      if (!$entity_route = $collection->get($entity_type->get('field_ui_base_route'))) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $path = $entity_route->getPath() . '/display/{view_mode_name}/layout';
 | 
			
		||||
 | 
			
		||||
      $defaults = [];
 | 
			
		||||
      $defaults['entity_type_id'] = $entity_type_id;
 | 
			
		||||
      // If the entity type has no bundles and it doesn't use {bundle} in its
 | 
			
		||||
      // admin path, use the entity type.
 | 
			
		||||
      if (!str_contains($path, '{bundle}')) {
 | 
			
		||||
        if (!$entity_type->hasKey('bundle')) {
 | 
			
		||||
          $defaults['bundle'] = $entity_type_id;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $defaults['bundle_key'] = $entity_type->getBundleEntityType();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $requirements = [];
 | 
			
		||||
      $requirements['_field_ui_view_mode_access'] = 'administer ' . $entity_type_id . ' display';
 | 
			
		||||
 | 
			
		||||
      $options = $entity_route->getOptions();
 | 
			
		||||
      $options['_admin_route'] = FALSE;
 | 
			
		||||
 | 
			
		||||
      $this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $path, $defaults, $requirements, $options, $entity_type_id, 'entity_view_display');
 | 
			
		||||
 | 
			
		||||
      // Set field_ui.route_enhancer to run on the manage layout form.
 | 
			
		||||
      if (isset($defaults['bundle_key'])) {
 | 
			
		||||
        $collection->get("layout_builder.defaults.$entity_type_id.view")
 | 
			
		||||
          ->setOption('_field_ui', TRUE)
 | 
			
		||||
          ->setDefault('bundle', '');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $route_names = [
 | 
			
		||||
        "entity.entity_view_display.{$entity_type_id}.default",
 | 
			
		||||
        "entity.entity_view_display.{$entity_type_id}.view_mode",
 | 
			
		||||
      ];
 | 
			
		||||
      foreach ($route_names as $route_name) {
 | 
			
		||||
        if (!$route = $collection->get($route_name)) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $route->addDefaults([
 | 
			
		||||
          'section_storage_type' => $this->getStorageType(),
 | 
			
		||||
          'section_storage' => '',
 | 
			
		||||
        ] + $defaults);
 | 
			
		||||
        $parameters['section_storage']['layout_builder_tempstore'] = TRUE;
 | 
			
		||||
        $parameters = NestedArray::mergeDeep($parameters, $route->getOption('parameters') ?: []);
 | 
			
		||||
        $route->setOption('parameters', $parameters);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of relevant entity types.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\EntityTypeInterface[]
 | 
			
		||||
   *   An array of entity types.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntityTypes() {
 | 
			
		||||
    return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
 | 
			
		||||
      return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasHandlerClass('form', 'layout_builder') && $entity_type->hasViewBuilderClass() && $entity_type->get('field_ui_base_route');
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getContextsDuringPreview() {
 | 
			
		||||
    $contexts = parent::getContextsDuringPreview();
 | 
			
		||||
 | 
			
		||||
    // During preview add a sample entity for the target entity type and bundle.
 | 
			
		||||
    $display = $this->getDisplay();
 | 
			
		||||
    $entity = $this->sampleEntityGenerator->get($display->getTargetEntityTypeId(), $display->getTargetBundle());
 | 
			
		||||
 | 
			
		||||
    $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity);
 | 
			
		||||
    return $contexts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deriveContextsFromRoute($value, $definition, $name, array $defaults) {
 | 
			
		||||
    $contexts = [];
 | 
			
		||||
 | 
			
		||||
    if ($entity = $this->extractEntityFromRoute($value, $defaults)) {
 | 
			
		||||
      $contexts['display'] = EntityContext::fromEntity($entity);
 | 
			
		||||
    }
 | 
			
		||||
    return $contexts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extracts an entity from the route values.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $value
 | 
			
		||||
   *   The raw value from the route.
 | 
			
		||||
   * @param array $defaults
 | 
			
		||||
   *   The route defaults array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\EntityInterface|null
 | 
			
		||||
   *   The entity for the route, or NULL if none exist.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\SectionStorageInterface::deriveContextsFromRoute()
 | 
			
		||||
   * @see \Drupal\Core\ParamConverter\ParamConverterInterface::convert()
 | 
			
		||||
   */
 | 
			
		||||
  private function extractEntityFromRoute($value, array $defaults) {
 | 
			
		||||
    // If a bundle is not provided but a value corresponding to the bundle key
 | 
			
		||||
    // is, use that for the bundle value.
 | 
			
		||||
    if (empty($defaults['bundle']) && isset($defaults['bundle_key']) && !empty($defaults[$defaults['bundle_key']])) {
 | 
			
		||||
      $defaults['bundle'] = $defaults[$defaults['bundle_key']];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is_string($value) && str_contains($value, '.')) {
 | 
			
		||||
      [$entity_type_id, $bundle, $view_mode] = explode('.', $value, 3);
 | 
			
		||||
    }
 | 
			
		||||
    elseif (!empty($defaults['entity_type_id']) && !empty($defaults['bundle']) && !empty($defaults['view_mode_name'])) {
 | 
			
		||||
      $entity_type_id = $defaults['entity_type_id'];
 | 
			
		||||
      $bundle = $defaults['bundle'];
 | 
			
		||||
      $view_mode = $defaults['view_mode_name'];
 | 
			
		||||
      $value = "$entity_type_id.$bundle.$view_mode";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $storage = $this->entityTypeManager->getStorage('entity_view_display');
 | 
			
		||||
    // If the display does not exist, create a new one.
 | 
			
		||||
    if (!$display = $storage->load($value)) {
 | 
			
		||||
      $display = $storage->create([
 | 
			
		||||
        'targetEntityType' => $entity_type_id,
 | 
			
		||||
        'bundle' => $bundle,
 | 
			
		||||
        'mode' => $view_mode,
 | 
			
		||||
        'status' => TRUE,
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
    return $display;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function label() {
 | 
			
		||||
    return $this->getDisplay()->label();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save() {
 | 
			
		||||
    return $this->getDisplay()->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isOverridable() {
 | 
			
		||||
    return $this->getDisplay()->isOverridable();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setOverridable($overridable = TRUE) {
 | 
			
		||||
    $this->getDisplay()->setOverridable($overridable);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setThirdPartySetting($module, $key, $value) {
 | 
			
		||||
    $this->getDisplay()->setThirdPartySetting($module, $key, $value);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isLayoutBuilderEnabled() {
 | 
			
		||||
    return $this->getDisplay()->isLayoutBuilderEnabled();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function enableLayoutBuilder() {
 | 
			
		||||
    $this->getDisplay()->enableLayoutBuilder();
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function disableLayoutBuilder() {
 | 
			
		||||
    $this->getDisplay()->disableLayoutBuilder();
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getThirdPartySetting($module, $key, $default = NULL) {
 | 
			
		||||
    return $this->getDisplay()->getThirdPartySetting($module, $key, $default);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getThirdPartySettings($module) {
 | 
			
		||||
    return $this->getDisplay()->getThirdPartySettings($module);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function unsetThirdPartySetting($module, $key) {
 | 
			
		||||
    $this->getDisplay()->unsetThirdPartySetting($module, $key);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getThirdPartyProviders() {
 | 
			
		||||
    return $this->getDisplay()->getThirdPartyProviders();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
 | 
			
		||||
    $result = AccessResult::allowedIf($this->isLayoutBuilderEnabled())->addCacheableDependency($this);
 | 
			
		||||
    return $return_as_object ? $result : $result->isAllowed();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
 | 
			
		||||
    $cacheability->addCacheableDependency($this);
 | 
			
		||||
    return $this->isLayoutBuilderEnabled();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setContext($name, ComponentContextInterface $context) {
 | 
			
		||||
    // Set the view mode context based on the display context.
 | 
			
		||||
    if ($name === 'display') {
 | 
			
		||||
      $this->setContextValue('view_mode', $context->getContextValue()->getMode());
 | 
			
		||||
    }
 | 
			
		||||
    parent::setContext($name, $context);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,417 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\SectionStorage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityRepositoryInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\TranslatableInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\Context;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\Context\EntityContext;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\layout_builder\Attribute\SectionStorage;
 | 
			
		||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
 | 
			
		||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the 'overrides' section storage type.
 | 
			
		||||
 *
 | 
			
		||||
 * OverridesSectionStorage uses a negative weight because:
 | 
			
		||||
 * - It must be picked before
 | 
			
		||||
 *   \Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage.
 | 
			
		||||
 * - The default weight is 0, so custom implementations will not take
 | 
			
		||||
 *   precedence unless otherwise specified.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[SectionStorage(
 | 
			
		||||
  id: "overrides",
 | 
			
		||||
  weight: -20,
 | 
			
		||||
  context_definitions: [
 | 
			
		||||
    'entity' => new ContextDefinition(
 | 
			
		||||
      data_type: 'entity',
 | 
			
		||||
      label: new TranslatableMarkup("Entity"),
 | 
			
		||||
      constraints: [
 | 
			
		||||
        "EntityHasField" => OverridesSectionStorage::FIELD_NAME,
 | 
			
		||||
      ],
 | 
			
		||||
    ),
 | 
			
		||||
    'view_mode' => new ContextDefinition(
 | 
			
		||||
      data_type: 'string',
 | 
			
		||||
      label: new TranslatableMarkup("View mode"),
 | 
			
		||||
      default_value: "default",
 | 
			
		||||
    ),
 | 
			
		||||
  ],
 | 
			
		||||
  handles_permission_check: TRUE,
 | 
			
		||||
)]
 | 
			
		||||
class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, OverridesSectionStorageInterface, SectionStorageLocalTaskProviderInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The field name used by this storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  const FIELD_NAME = 'layout_builder__layout';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity field manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityFieldManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current user.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Session\AccountInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $currentUser;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, SectionStorageManagerInterface $section_storage_manager, EntityRepositoryInterface $entity_repository, AccountInterface $current_user) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->entityFieldManager = $entity_field_manager;
 | 
			
		||||
    $this->sectionStorageManager = $section_storage_manager;
 | 
			
		||||
    $this->entityRepository = $entity_repository;
 | 
			
		||||
    $this->currentUser = $current_user;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('entity_field.manager'),
 | 
			
		||||
      $container->get('plugin.manager.layout_builder.section_storage'),
 | 
			
		||||
      $container->get('entity.repository'),
 | 
			
		||||
      $container->get('current_user')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getSectionList() {
 | 
			
		||||
    return $this->getEntity()->get(static::FIELD_NAME);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the entity storing the overrides.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\FieldableEntityInterface
 | 
			
		||||
   *   The entity storing the overrides.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntity() {
 | 
			
		||||
    return $this->getContextValue('entity');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorageId() {
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    return $entity->getEntityTypeId() . '.' . $entity->id();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getTempstoreKey() {
 | 
			
		||||
    $key = parent::getTempstoreKey();
 | 
			
		||||
    $key .= '.' . $this->getContextValue('view_mode');
 | 
			
		||||
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    // @todo Allow entities to provide this contextual information in
 | 
			
		||||
    //   https://www.drupal.org/project/drupal/issues/3026957.
 | 
			
		||||
    if ($entity instanceof TranslatableInterface) {
 | 
			
		||||
      $key .= '.' . $entity->language()->getId();
 | 
			
		||||
    }
 | 
			
		||||
    return $key;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deriveContextsFromRoute($value, $definition, $name, array $defaults) {
 | 
			
		||||
    $contexts = [];
 | 
			
		||||
 | 
			
		||||
    if ($entity = $this->extractEntityFromRoute($value, $defaults)) {
 | 
			
		||||
      $contexts['entity'] = EntityContext::fromEntity($entity);
 | 
			
		||||
      // @todo Expand to work for all view modes in
 | 
			
		||||
      //   https://www.drupal.org/node/2907413.
 | 
			
		||||
      $view_mode = 'full';
 | 
			
		||||
      // Retrieve the actual view mode from the returned view display as the
 | 
			
		||||
      // requested view mode may not exist and a fallback will be used.
 | 
			
		||||
      $view_mode = LayoutBuilderEntityViewDisplay::collectRenderDisplay($entity, $view_mode)->getMode();
 | 
			
		||||
      $contexts['view_mode'] = new Context(new ContextDefinition('string'), $view_mode);
 | 
			
		||||
    }
 | 
			
		||||
    return $contexts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extracts an entity from the route values.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $value
 | 
			
		||||
   *   The raw value from the route.
 | 
			
		||||
   * @param array $defaults
 | 
			
		||||
   *   The route defaults array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\EntityInterface|null
 | 
			
		||||
   *   The entity for the route, or NULL if none exist. The entity is not
 | 
			
		||||
   *   guaranteed to be fieldable, or contain the necessary field for this
 | 
			
		||||
   *   section storage plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\SectionStorageInterface::deriveContextsFromRoute()
 | 
			
		||||
   * @see \Drupal\Core\ParamConverter\ParamConverterInterface::convert()
 | 
			
		||||
   */
 | 
			
		||||
  private function extractEntityFromRoute($value, array $defaults) {
 | 
			
		||||
    if (str_contains($value, '.')) {
 | 
			
		||||
      [$entity_type_id, $entity_id] = explode('.', $value, 2);
 | 
			
		||||
    }
 | 
			
		||||
    elseif (isset($defaults['entity_type_id']) && !empty($defaults[$defaults['entity_type_id']])) {
 | 
			
		||||
      $entity_type_id = $defaults['entity_type_id'];
 | 
			
		||||
      $entity_id = $defaults[$entity_type_id];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity = $this->entityRepository->getActive($entity_type_id, $entity_id);
 | 
			
		||||
    return ($entity instanceof FieldableEntityInterface) ? $entity : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildRoutes(RouteCollection $collection) {
 | 
			
		||||
    foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
 | 
			
		||||
      // If the canonical route does not exist, do not provide any Layout
 | 
			
		||||
      // Builder UI routes for this entity type.
 | 
			
		||||
      if (!$collection->get("entity.$entity_type_id.canonical")) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $defaults = [];
 | 
			
		||||
      $defaults['entity_type_id'] = $entity_type_id;
 | 
			
		||||
 | 
			
		||||
      // Retrieve the requirements from the canonical route.
 | 
			
		||||
      $requirements = $collection->get("entity.$entity_type_id.canonical")->getRequirements();
 | 
			
		||||
 | 
			
		||||
      $options = [];
 | 
			
		||||
      // Ensure that upcasting is run in the correct order.
 | 
			
		||||
      $options['parameters']['section_storage'] = [];
 | 
			
		||||
      $options['parameters'][$entity_type_id]['type'] = 'entity:' . $entity_type_id;
 | 
			
		||||
      $options['_admin_route'] = FALSE;
 | 
			
		||||
 | 
			
		||||
      $template = $entity_type->getLinkTemplate('canonical') . '/layout';
 | 
			
		||||
      $this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $template, $defaults, $requirements, $options, $entity_type_id, $entity_type_id);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildLocalTasks($base_plugin_definition) {
 | 
			
		||||
    $local_tasks = [];
 | 
			
		||||
    foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
 | 
			
		||||
      $local_tasks["layout_builder.overrides.$entity_type_id.view"] = $base_plugin_definition + [
 | 
			
		||||
        'route_name' => "layout_builder.overrides.$entity_type_id.view",
 | 
			
		||||
        'weight' => 15,
 | 
			
		||||
        'title' => $this->t('Layout'),
 | 
			
		||||
        'base_route' => "entity.$entity_type_id.canonical",
 | 
			
		||||
        'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    return $local_tasks;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if this entity type's ID is stored as an integer.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
 | 
			
		||||
   *   An entity type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if this entity type's ID key is always an integer, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected function hasIntegerId(EntityTypeInterface $entity_type) {
 | 
			
		||||
    $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type->id());
 | 
			
		||||
    return $field_storage_definitions[$entity_type->getKey('id')]->getType() === 'integer';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of relevant entity types.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Entity\EntityTypeInterface[]
 | 
			
		||||
   *   An array of entity types.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntityTypes() {
 | 
			
		||||
    return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
 | 
			
		||||
      return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasHandlerClass('form', 'layout_builder') && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDefaultSectionStorage() {
 | 
			
		||||
    $display = LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), $this->getContextValue('view_mode'));
 | 
			
		||||
    return $this->sectionStorageManager->load('defaults', ['display' => EntityContext::fromEntity($display)]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getRedirectUrl() {
 | 
			
		||||
    return $this->getEntity()->toUrl('canonical');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getLayoutBuilderUrl($rel = 'view') {
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    $route_parameters[$entity->getEntityTypeId()] = $entity->id();
 | 
			
		||||
    return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.$rel", $route_parameters);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getContextsDuringPreview() {
 | 
			
		||||
    $contexts = parent::getContextsDuringPreview();
 | 
			
		||||
 | 
			
		||||
    if (isset($contexts['entity'])) {
 | 
			
		||||
      $contexts['layout_builder.entity'] = $contexts['entity'];
 | 
			
		||||
      unset($contexts['entity']);
 | 
			
		||||
    }
 | 
			
		||||
    return $contexts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function label() {
 | 
			
		||||
    return $this->getEntity()->label();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save() {
 | 
			
		||||
    return $this->getEntity()->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
 | 
			
		||||
    if ($account === NULL) {
 | 
			
		||||
      $account = $this->currentUser;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
 | 
			
		||||
    // Create an access result that will allow access to the layout if one of
 | 
			
		||||
    // these conditions applies:
 | 
			
		||||
    // 1. The user can configure any layouts.
 | 
			
		||||
    $any_access = AccessResult::allowedIfHasPermission($account, 'configure any layout');
 | 
			
		||||
    // 2. The user can configure layouts on all items of the bundle type.
 | 
			
		||||
    $bundle_access = AccessResult::allowedIfHasPermission($account, "configure all {$entity->bundle()} {$entity->getEntityTypeId()} layout overrides");
 | 
			
		||||
    // 3. The user can configure layouts items of this bundle type they can edit
 | 
			
		||||
    //    AND the user has access to edit this entity.
 | 
			
		||||
    $edit_only_bundle_access = AccessResult::allowedIfHasPermission($account, "configure editable {$entity->bundle()} {$entity->getEntityTypeId()} layout overrides");
 | 
			
		||||
    $edit_only_bundle_access = $edit_only_bundle_access->andIf($entity->access('update', $account, TRUE));
 | 
			
		||||
 | 
			
		||||
    $result = $any_access
 | 
			
		||||
      ->orIf($bundle_access)
 | 
			
		||||
      ->orIf($edit_only_bundle_access);
 | 
			
		||||
 | 
			
		||||
    // Access also depends on the default being enabled.
 | 
			
		||||
    $result = $result->andIf($this->getDefaultSectionStorage()->access($operation, $account, TRUE));
 | 
			
		||||
    // Access also depends on the default layout being overridable.
 | 
			
		||||
    $result = $result->andIf(AccessResult::allowedIf($this->getDefaultSectionStorage()->isOverridable())->addCacheableDependency($this->getDefaultSectionStorage()));
 | 
			
		||||
    $result = $this->handleTranslationAccess($result, $operation, $account);
 | 
			
		||||
    return $return_as_object ? $result : $result->isAllowed();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handles access checks related to translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Access\AccessResult $result
 | 
			
		||||
   *   The access result.
 | 
			
		||||
   * @param string $operation
 | 
			
		||||
   *   The operation to be performed.
 | 
			
		||||
   * @param \Drupal\Core\Session\AccountInterface $account
 | 
			
		||||
   *   The user for which to check access.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Access\AccessResultInterface
 | 
			
		||||
   *   The access result.
 | 
			
		||||
   */
 | 
			
		||||
  protected function handleTranslationAccess(AccessResult $result, $operation, AccountInterface $account) {
 | 
			
		||||
    $entity = $this->getEntity();
 | 
			
		||||
    // Access is always denied on non-default translations.
 | 
			
		||||
    return $result->andIf(AccessResult::allowedIf(!($entity instanceof TranslatableInterface && !$entity->isDefaultTranslation())))->addCacheableDependency($entity);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
 | 
			
		||||
    $default_section_storage = $this->getDefaultSectionStorage();
 | 
			
		||||
    $cacheability->addCacheableDependency($default_section_storage)->addCacheableDependency($this);
 | 
			
		||||
    // Check that overrides are enabled and have at least one section.
 | 
			
		||||
    return $default_section_storage->isOverridable() && $this->isOverridden();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isOverridden() {
 | 
			
		||||
    // If there are any sections at all, including a blank one, this section
 | 
			
		||||
    // storage has been overridden. Do not use count() as it does not include
 | 
			
		||||
    // blank sections.
 | 
			
		||||
    return !empty($this->getSections());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,114 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\SectionStorage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\Context;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextDefinition;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginTrait;
 | 
			
		||||
use Drupal\Core\Plugin\PluginBase;
 | 
			
		||||
use Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait;
 | 
			
		||||
use Drupal\layout_builder\Section;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\TempStoreIdentifierInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a base class for Section Storage types.
 | 
			
		||||
 */
 | 
			
		||||
abstract class SectionStorageBase extends PluginBase implements SectionStorageInterface, TempStoreIdentifierInterface, CacheableDependencyInterface {
 | 
			
		||||
 | 
			
		||||
  use ContextAwarePluginTrait;
 | 
			
		||||
  use LayoutBuilderRoutesTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the section list.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionListInterface
 | 
			
		||||
   *   The section list.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function getSectionList();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorageType() {
 | 
			
		||||
    return $this->getPluginId();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function count(): int {
 | 
			
		||||
    return $this->getSectionList()->count();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSections() {
 | 
			
		||||
    return $this->getSectionList()->getSections();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSection($delta) {
 | 
			
		||||
    return $this->getSectionList()->getSection($delta);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function appendSection(Section $section) {
 | 
			
		||||
    $this->getSectionList()->appendSection($section);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function insertSection($delta, Section $section) {
 | 
			
		||||
    $this->getSectionList()->insertSection($delta, $section);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function removeSection($delta) {
 | 
			
		||||
    $this->getSectionList()->removeSection($delta);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function removeAllSections($set_blank = FALSE) {
 | 
			
		||||
    $this->getSectionList()->removeAllSections($set_blank);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getContextsDuringPreview() {
 | 
			
		||||
    $contexts = $this->getContexts();
 | 
			
		||||
 | 
			
		||||
    // view_mode is a required context, but SectionStorage plugins are not
 | 
			
		||||
    // required to return it (for example, the layout_library plugin provided
 | 
			
		||||
    // in the Layout Library module. In these instances, explicitly create a
 | 
			
		||||
    // view_mode context with the value "default".
 | 
			
		||||
    if (!isset($contexts['view_mode']) || $contexts['view_mode']->validate()->count() || !$contexts['view_mode']->getContextValue()) {
 | 
			
		||||
      $contexts['view_mode'] = new Context(new ContextDefinition('string'), 'default');
 | 
			
		||||
    }
 | 
			
		||||
    return $contexts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getTempstoreKey() {
 | 
			
		||||
    return $this->getStorageId();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Plugin\SectionStorage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Allows section storage plugins to provide local tasks.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Plugin\Derivative\LayoutBuilderLocalTaskDeriver
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
 */
 | 
			
		||||
interface SectionStorageLocalTaskProviderInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the local tasks dynamically for Layout Builder plugins.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $base_plugin_definition
 | 
			
		||||
   *   The definition of the base plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of full derivative definitions keyed on derivative ID.
 | 
			
		||||
   */
 | 
			
		||||
  public function buildLocalTasks($base_plugin_definition);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,57 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Routing;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Routing\RouteBuildEvent;
 | 
			
		||||
use Drupal\Core\Routing\RoutingEvents;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides routes for the Layout Builder UI.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutBuilderRoutes implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LayoutBuilderRoutes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
 | 
			
		||||
   *   The section storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SectionStorageManagerInterface $section_storage_manager) {
 | 
			
		||||
    $this->sectionStorageManager = $section_storage_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alters existing routes for a specific collection.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteBuildEvent $event
 | 
			
		||||
   *   The route build event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onAlterRoutes(RouteBuildEvent $event) {
 | 
			
		||||
    $collection = $event->getRouteCollection();
 | 
			
		||||
    foreach ($this->sectionStorageManager->getDefinitions() as $plugin_id => $definition) {
 | 
			
		||||
      $this->sectionStorageManager->loadEmpty($plugin_id)->buildRoutes($collection);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    // Run after \Drupal\field_ui\Routing\RouteSubscriber.
 | 
			
		||||
    $events[RoutingEvents::ALTER] = ['onAlterRoutes', -110];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,103 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Routing;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\layout_builder\DefaultsSectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
 | 
			
		||||
use Symfony\Component\Routing\Route;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a trait for building routes for a Layout Builder UI.
 | 
			
		||||
 */
 | 
			
		||||
trait LayoutBuilderRoutesTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the layout routes for the given values.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\Routing\RouteCollection $collection
 | 
			
		||||
   *   The route collection.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorage\SectionStorageDefinition $definition
 | 
			
		||||
   *   The definition of the section storage.
 | 
			
		||||
   * @param string $path
 | 
			
		||||
   *   The path patten for the routes.
 | 
			
		||||
   * @param array $defaults
 | 
			
		||||
   *   (optional) An array of default parameter values.
 | 
			
		||||
   * @param array $requirements
 | 
			
		||||
   *   (optional) An array of requirements for parameters.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   (optional) An array of options.
 | 
			
		||||
   * @param string $route_name_prefix
 | 
			
		||||
   *   (optional) The prefix to use for the route name.
 | 
			
		||||
   * @param string $entity_type_id
 | 
			
		||||
   *   (optional) The entity type ID, if available.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildLayoutRoutes(RouteCollection $collection, SectionStorageDefinition $definition, $path, array $defaults = [], array $requirements = [], array $options = [], $route_name_prefix = '', $entity_type_id = '') {
 | 
			
		||||
    $type = $definition->id();
 | 
			
		||||
    $defaults['section_storage_type'] = $type;
 | 
			
		||||
    // Provide an empty value to allow the section storage to be upcast.
 | 
			
		||||
    $defaults['section_storage'] = '';
 | 
			
		||||
    // Trigger the layout builder access check.
 | 
			
		||||
    $requirements['_layout_builder_access'] = 'view';
 | 
			
		||||
    // Trigger the layout builder RouteEnhancer.
 | 
			
		||||
    $options['_layout_builder'] = TRUE;
 | 
			
		||||
    // Trigger the layout builder param converter.
 | 
			
		||||
    $parameters['section_storage']['layout_builder_tempstore'] = TRUE;
 | 
			
		||||
    // Merge the passed in options in after Layout Builder's parameters.
 | 
			
		||||
    $options = NestedArray::mergeDeep(['parameters' => $parameters], $options);
 | 
			
		||||
 | 
			
		||||
    if ($route_name_prefix) {
 | 
			
		||||
      $route_name_prefix = "layout_builder.$type.$route_name_prefix";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $route_name_prefix = "layout_builder.$type";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $main_defaults = $defaults;
 | 
			
		||||
    $main_options = $options;
 | 
			
		||||
    if ($entity_type_id) {
 | 
			
		||||
      $main_defaults['_entity_form'] = "$entity_type_id.layout_builder";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $main_defaults['_controller'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::layout';
 | 
			
		||||
    }
 | 
			
		||||
    $main_defaults['_title_callback'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::title';
 | 
			
		||||
    $route = (new Route($path))
 | 
			
		||||
      ->setDefaults($main_defaults)
 | 
			
		||||
      ->setRequirements($requirements)
 | 
			
		||||
      ->setOptions($main_options);
 | 
			
		||||
    $collection->add("$route_name_prefix.view", $route);
 | 
			
		||||
 | 
			
		||||
    $discard_changes_defaults = $defaults;
 | 
			
		||||
    $discard_changes_defaults['_form'] = '\Drupal\layout_builder\Form\DiscardLayoutChangesForm';
 | 
			
		||||
    $route = (new Route("$path/discard-changes"))
 | 
			
		||||
      ->setDefaults($discard_changes_defaults)
 | 
			
		||||
      ->setRequirements($requirements)
 | 
			
		||||
      ->setOptions($options);
 | 
			
		||||
    $collection->add("$route_name_prefix.discard_changes", $route);
 | 
			
		||||
 | 
			
		||||
    if (is_subclass_of($definition->getClass(), OverridesSectionStorageInterface::class)) {
 | 
			
		||||
      $revert_defaults = $defaults;
 | 
			
		||||
      $revert_defaults['_form'] = '\Drupal\layout_builder\Form\RevertOverridesForm';
 | 
			
		||||
      $route = (new Route("$path/revert"))
 | 
			
		||||
        ->setDefaults($revert_defaults)
 | 
			
		||||
        ->setRequirements($requirements)
 | 
			
		||||
        ->setOptions($options);
 | 
			
		||||
      $collection->add("$route_name_prefix.revert", $route);
 | 
			
		||||
    }
 | 
			
		||||
    elseif (is_subclass_of($definition->getClass(), DefaultsSectionStorageInterface::class)) {
 | 
			
		||||
      $disable_defaults = $defaults;
 | 
			
		||||
      $disable_defaults['_form'] = '\Drupal\layout_builder\Form\LayoutBuilderDisableForm';
 | 
			
		||||
      $disable_options = $options;
 | 
			
		||||
      unset($disable_options['_admin_route'], $disable_options['_layout_builder']);
 | 
			
		||||
      $route = (new Route("$path/disable"))
 | 
			
		||||
        ->setDefaults($disable_defaults)
 | 
			
		||||
        ->setRequirements($requirements)
 | 
			
		||||
        ->setOptions($disable_options);
 | 
			
		||||
      $collection->add("$route_name_prefix.disable", $route);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,57 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Routing;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\ParamConverter\ParamConverterInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
 | 
			
		||||
use Symfony\Component\Routing\Route;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loads the section storage from the routing defaults.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutSectionStorageParamConverter implements ParamConverterInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The section storage manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sectionStorageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LayoutSectionStorageParamConverter.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
 | 
			
		||||
   *   The section storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SectionStorageManagerInterface $section_storage_manager) {
 | 
			
		||||
    $this->sectionStorageManager = $section_storage_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function convert($value, $definition, $name, array $defaults) {
 | 
			
		||||
    // If no section storage type is specified or if it is invalid, return.
 | 
			
		||||
    if (!isset($defaults['section_storage_type']) || !$this->sectionStorageManager->hasDefinition($defaults['section_storage_type'])) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $type = $defaults['section_storage_type'];
 | 
			
		||||
    // Load an empty instance and derive the available contexts.
 | 
			
		||||
    $contexts = $this->sectionStorageManager->loadEmpty($type)->deriveContextsFromRoute($value, $definition, $name, $defaults);
 | 
			
		||||
    // Attempt to load a full instance based on the context.
 | 
			
		||||
    return $this->sectionStorageManager->load($type, $contexts);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function applies($definition, $name, Route $route) {
 | 
			
		||||
    return !empty($definition['layout_builder_section_storage']) || !empty($definition['layout_builder_tempstore']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\Routing;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Routing\EnhancerInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteObjectInterface;
 | 
			
		||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loads the section storage from the layout tempstore.
 | 
			
		||||
 */
 | 
			
		||||
class LayoutTempstoreRouteEnhancer implements EnhancerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout tempstore repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutTempstoreRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LayoutTempstoreRouteEnhancer.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
 | 
			
		||||
   *   The layout tempstore repository.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
 | 
			
		||||
    $this->layoutTempstoreRepository = $layout_tempstore_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function enhance(array $defaults, Request $request) {
 | 
			
		||||
    $parameters = $defaults[RouteObjectInterface::ROUTE_OBJECT]->getOption('parameters');
 | 
			
		||||
    if (isset($parameters['section_storage']['layout_builder_tempstore']) && isset($defaults['section_storage']) && $defaults['section_storage'] instanceof SectionStorageInterface) {
 | 
			
		||||
      $defaults['section_storage'] = $this->layoutTempstoreRepository->get($defaults['section_storage']);
 | 
			
		||||
    }
 | 
			
		||||
    return $defaults;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										454
									
								
								web/core/modules/layout_builder/src/Section.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										454
									
								
								web/core/modules/layout_builder/src/Section.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,454 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PreviewAwarePluginInterface;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a domain object for layout sections.
 | 
			
		||||
 *
 | 
			
		||||
 * A section consists of three parts:
 | 
			
		||||
 * - The layout plugin ID for the layout applied to the section (for example,
 | 
			
		||||
 *   'layout_onecol').
 | 
			
		||||
 * - An array of settings for the layout plugin.
 | 
			
		||||
 * - An array of components that can be rendered in the section.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\Core\Layout\LayoutDefinition
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionComponent
 | 
			
		||||
 */
 | 
			
		||||
class Section implements ThirdPartySettingsInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout plugin ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutId;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The layout plugin settings.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $layoutSettings = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array of components, keyed by UUID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\layout_builder\SectionComponent[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $components = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Third party settings.
 | 
			
		||||
   *
 | 
			
		||||
   * An array of key/value pairs keyed by provider.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $thirdPartySettings = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new Section.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $layout_id
 | 
			
		||||
   *   The layout plugin ID.
 | 
			
		||||
   * @param array $layout_settings
 | 
			
		||||
   *   (optional) The layout plugin settings.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent[] $components
 | 
			
		||||
   *   (optional) The components.
 | 
			
		||||
   * @param array[] $third_party_settings
 | 
			
		||||
   *   (optional) Any third party settings.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($layout_id, array $layout_settings = [], array $components = [], array $third_party_settings = []) {
 | 
			
		||||
    $this->layoutId = $layout_id;
 | 
			
		||||
    $this->layoutSettings = $layout_settings;
 | 
			
		||||
    foreach ($components as $component) {
 | 
			
		||||
      $this->setComponent($component);
 | 
			
		||||
    }
 | 
			
		||||
    $this->thirdPartySettings = $third_party_settings;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the renderable array for this section.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
 | 
			
		||||
   *   An array of available contexts.
 | 
			
		||||
   * @param bool $in_preview
 | 
			
		||||
   *   TRUE if the section is being previewed, FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A renderable array representing the content of the section.
 | 
			
		||||
   */
 | 
			
		||||
  public function toRenderArray(array $contexts = [], $in_preview = FALSE) {
 | 
			
		||||
    $regions = [];
 | 
			
		||||
    foreach ($this->getComponents() as $component) {
 | 
			
		||||
      if ($output = $component->toRenderArray($contexts, $in_preview)) {
 | 
			
		||||
        $regions[$component->getRegion()][$component->getUuid()] = $output;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $layout = $this->getLayout($contexts);
 | 
			
		||||
    if ($layout instanceof PreviewAwarePluginInterface) {
 | 
			
		||||
      $layout->setInPreview($in_preview);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $build = $layout->build($regions);
 | 
			
		||||
    // If an entity was used to build the layout, store it on the build.
 | 
			
		||||
    if (!Element::isEmpty($build) && isset($contexts['layout_builder.entity'])) {
 | 
			
		||||
      $build['#entity'] = $contexts['layout_builder.entity']->getContextValue();
 | 
			
		||||
    }
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the layout plugin for this section.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
 | 
			
		||||
   *   An array of available contexts.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Layout\LayoutInterface
 | 
			
		||||
   *   The layout plugin.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLayout(array $contexts = []) {
 | 
			
		||||
    $layout = $this->layoutPluginManager()->createInstance($this->getLayoutId(), $this->layoutSettings);
 | 
			
		||||
    if ($contexts) {
 | 
			
		||||
      $this->contextHandler()->applyContextMapping($layout, $contexts);
 | 
			
		||||
    }
 | 
			
		||||
    return $layout;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the layout plugin ID for this section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The layout plugin ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   *   This method should only be used by code responsible for storing the data.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLayoutId() {
 | 
			
		||||
    return $this->layoutId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the layout plugin settings for this section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed[]
 | 
			
		||||
   *   The layout plugin settings.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   *   This method should only be used by code responsible for storing the data.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLayoutSettings() {
 | 
			
		||||
    return $this->getLayout()->getConfiguration();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the layout plugin settings for this section.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed[] $layout_settings
 | 
			
		||||
   *   The layout plugin settings.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setLayoutSettings(array $layout_settings) {
 | 
			
		||||
    $this->layoutSettings = $layout_settings;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the default region.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The machine-readable name of the default region.
 | 
			
		||||
   */
 | 
			
		||||
  public function getDefaultRegion() {
 | 
			
		||||
    return $this->layoutPluginManager()->getDefinition($this->getLayoutId())->getDefaultRegion();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the components of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionComponent[]
 | 
			
		||||
   *   An array of components, keyed by the component UUID.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponents() {
 | 
			
		||||
    return $this->components;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the component for a given UUID.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $uuid
 | 
			
		||||
   *   The UUID of the component to retrieve.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionComponent
 | 
			
		||||
   *   The component.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   Thrown when the expected UUID does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponent($uuid) {
 | 
			
		||||
    if (!isset($this->components[$uuid])) {
 | 
			
		||||
      throw new \InvalidArgumentException(sprintf('Invalid UUID "%s"', $uuid));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->components[$uuid];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method to set a component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent $component
 | 
			
		||||
   *   The component.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  protected function setComponent(SectionComponent $component) {
 | 
			
		||||
    $this->components[$component->getUuid()] = $component;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes a given component from a region.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $uuid
 | 
			
		||||
   *   The UUID of the component to remove.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function removeComponent($uuid) {
 | 
			
		||||
    unset($this->components[$uuid]);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Appends a component to the end of a region.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent $component
 | 
			
		||||
   *   The component being appended.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function appendComponent(SectionComponent $component) {
 | 
			
		||||
    $component->setWeight($this->getNextHighestWeight($component->getRegion()));
 | 
			
		||||
    $this->setComponent($component);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the next highest weight of the component in a region.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   A number higher than the highest weight of the component in the region.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getNextHighestWeight($region) {
 | 
			
		||||
    $components = $this->getComponentsByRegion($region);
 | 
			
		||||
    $weights = array_map(function (SectionComponent $component) {
 | 
			
		||||
      return $component->getWeight();
 | 
			
		||||
    }, $components);
 | 
			
		||||
    return $weights ? max($weights) + 1 : 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the components for a specific region.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionComponent[]
 | 
			
		||||
   *   An array of components in the specified region, sorted by weight.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponentsByRegion($region) {
 | 
			
		||||
    $components = array_filter($this->getComponents(), function (SectionComponent $component) use ($region) {
 | 
			
		||||
      return $component->getRegion() === $region;
 | 
			
		||||
    });
 | 
			
		||||
    uasort($components, function (SectionComponent $a, SectionComponent $b) {
 | 
			
		||||
      return $a->getWeight() <=> $b->getWeight();
 | 
			
		||||
    });
 | 
			
		||||
    return $components;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Inserts a component after a specified existing component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $preceding_uuid
 | 
			
		||||
   *   The UUID of the existing component to insert after.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent $component
 | 
			
		||||
   *   The component being inserted.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   Thrown when the expected UUID does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function insertAfterComponent($preceding_uuid, SectionComponent $component) {
 | 
			
		||||
    // Find the delta of the specified UUID.
 | 
			
		||||
    $uuids = array_keys($this->getComponentsByRegion($component->getRegion()));
 | 
			
		||||
    $delta = array_search($preceding_uuid, $uuids, TRUE);
 | 
			
		||||
    if ($delta === FALSE) {
 | 
			
		||||
      throw new \InvalidArgumentException(sprintf('Invalid preceding UUID "%s"', $preceding_uuid));
 | 
			
		||||
    }
 | 
			
		||||
    return $this->insertComponent($delta + 1, $component);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Inserts a component at a specified delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The zero-based delta in which to insert the component.
 | 
			
		||||
   * @param \Drupal\layout_builder\SectionComponent $new_component
 | 
			
		||||
   *   The component being inserted.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \OutOfBoundsException
 | 
			
		||||
   *   Thrown when the specified delta is invalid.
 | 
			
		||||
   */
 | 
			
		||||
  public function insertComponent($delta, SectionComponent $new_component) {
 | 
			
		||||
    $components = $this->getComponentsByRegion($new_component->getRegion());
 | 
			
		||||
    $count = count($components);
 | 
			
		||||
    if ($delta > $count) {
 | 
			
		||||
      throw new \OutOfBoundsException(sprintf('Invalid delta "%s" for the "%s" component', $delta, $new_component->getUuid()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the delta is the end of the list, append the component instead.
 | 
			
		||||
    if ($delta === $count) {
 | 
			
		||||
      return $this->appendComponent($new_component);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Find the weight of the component that exists at the specified delta.
 | 
			
		||||
    $weight = array_values($components)[$delta]->getWeight();
 | 
			
		||||
    $this->setComponent($new_component->setWeight($weight++));
 | 
			
		||||
 | 
			
		||||
    // Increase the weight of every subsequent component.
 | 
			
		||||
    foreach (array_slice($components, $delta) as $component) {
 | 
			
		||||
      $component->setWeight($weight++);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wraps the layout plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Layout\LayoutPluginManagerInterface
 | 
			
		||||
   *   The layout plugin manager.
 | 
			
		||||
   */
 | 
			
		||||
  protected function layoutPluginManager() {
 | 
			
		||||
    return \Drupal::service('plugin.manager.core.layout');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array representation of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * Only use this method if you are implementing custom storage for sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array representation of the section component.
 | 
			
		||||
   */
 | 
			
		||||
  public function toArray() {
 | 
			
		||||
    return [
 | 
			
		||||
      'layout_id' => $this->getLayoutId(),
 | 
			
		||||
      'layout_settings' => $this->getLayoutSettings(),
 | 
			
		||||
      'components' => array_map(function (SectionComponent $component) {
 | 
			
		||||
        return $component->toArray();
 | 
			
		||||
      }, $this->getComponents()),
 | 
			
		||||
      'third_party_settings' => $this->thirdPartySettings,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates an object from an array representation of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * Only use this method if you are implementing custom storage for sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $section
 | 
			
		||||
   *   An array of section data in the format returned by ::toArray().
 | 
			
		||||
   *
 | 
			
		||||
   * @return static
 | 
			
		||||
   *   The section object.
 | 
			
		||||
   */
 | 
			
		||||
  public static function fromArray(array $section) {
 | 
			
		||||
    // Ensure expected array keys are present.
 | 
			
		||||
    $section += [
 | 
			
		||||
      'layout_id' => '',
 | 
			
		||||
      'layout_settings' => [],
 | 
			
		||||
      'components' => [],
 | 
			
		||||
      'third_party_settings' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    return new static(
 | 
			
		||||
      $section['layout_id'],
 | 
			
		||||
      $section['layout_settings'],
 | 
			
		||||
      array_map([SectionComponent::class, 'fromArray'], $section['components']),
 | 
			
		||||
      $section['third_party_settings']
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Magic method: Implements a deep clone.
 | 
			
		||||
   */
 | 
			
		||||
  public function __clone() {
 | 
			
		||||
    foreach ($this->components as $uuid => $component) {
 | 
			
		||||
      $this->components[$uuid] = clone $component;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getThirdPartySetting($provider, $key, $default = NULL) {
 | 
			
		||||
    return $this->thirdPartySettings[$provider][$key] ?? $default;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getThirdPartySettings($provider) {
 | 
			
		||||
    return $this->thirdPartySettings[$provider] ?? [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setThirdPartySetting($provider, $key, $value) {
 | 
			
		||||
    $this->thirdPartySettings[$provider][$key] = $value;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function unsetThirdPartySetting($provider, $key) {
 | 
			
		||||
    unset($this->thirdPartySettings[$provider][$key]);
 | 
			
		||||
    // If the third party is no longer storing any information, completely
 | 
			
		||||
    // remove the array holding the settings for this provider.
 | 
			
		||||
    if (empty($this->thirdPartySettings[$provider])) {
 | 
			
		||||
      unset($this->thirdPartySettings[$provider]);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getThirdPartyProviders() {
 | 
			
		||||
    return array_keys($this->thirdPartySettings);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wraps the context handler.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface
 | 
			
		||||
   *   The context handler.
 | 
			
		||||
   */
 | 
			
		||||
  protected function contextHandler() {
 | 
			
		||||
    return \Drupal::service('context.handler');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										320
									
								
								web/core/modules/layout_builder/src/SectionComponent.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								web/core/modules/layout_builder/src/SectionComponent.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,320 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Exception\PluginException;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
 | 
			
		||||
use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a value object for a section component.
 | 
			
		||||
 *
 | 
			
		||||
 * A component represents the smallest part of a layout (for example, a block).
 | 
			
		||||
 * Components wrap a renderable plugin, currently using
 | 
			
		||||
 * \Drupal\Core\Block\BlockPluginInterface, and contain the layout region
 | 
			
		||||
 * within the section layout where the component will be rendered.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\Core\Layout\LayoutDefinition
 | 
			
		||||
 * @see \Drupal\layout_builder\Section
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
 */
 | 
			
		||||
class SectionComponent {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The UUID of the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $uuid;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The region the component is placed in.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $region;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array of plugin configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @var mixed[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $configuration;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The weight of the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $weight = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Any additional properties and values.
 | 
			
		||||
   *
 | 
			
		||||
   * @var mixed[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $additional = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new SectionComponent.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $uuid
 | 
			
		||||
   *   The UUID.
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region.
 | 
			
		||||
   * @param mixed[] $configuration
 | 
			
		||||
   *   The plugin configuration.
 | 
			
		||||
   * @param mixed[] $additional
 | 
			
		||||
   *   An additional values.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($uuid, $region, array $configuration = [], array $additional = []) {
 | 
			
		||||
    $this->uuid = $uuid;
 | 
			
		||||
    $this->region = $region;
 | 
			
		||||
    $this->configuration = $configuration;
 | 
			
		||||
    $this->additional = $additional;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the renderable array for this component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
 | 
			
		||||
   *   An array of available contexts.
 | 
			
		||||
   * @param bool $in_preview
 | 
			
		||||
   *   TRUE if the component is being previewed, FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A renderable array representing the content of the component.
 | 
			
		||||
   */
 | 
			
		||||
  public function toRenderArray(array $contexts = [], $in_preview = FALSE) {
 | 
			
		||||
    $event = new SectionComponentBuildRenderArrayEvent($this, $contexts, $in_preview);
 | 
			
		||||
    $this->eventDispatcher()->dispatch($event, LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAY);
 | 
			
		||||
    $output = $event->getBuild();
 | 
			
		||||
    $event->getCacheableMetadata()->applyTo($output);
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets any arbitrary property for the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $property
 | 
			
		||||
   *   The property to retrieve.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   *   The value for that property, or NULL if the property does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function get($property) {
 | 
			
		||||
    if (property_exists($this, $property)) {
 | 
			
		||||
      $value = $this->{$property} ?? NULL;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $value = $this->additional[$property] ?? NULL;
 | 
			
		||||
    }
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets a value to an arbitrary property for the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $property
 | 
			
		||||
   *   The property to use for the value.
 | 
			
		||||
   * @param mixed $value
 | 
			
		||||
   *   The value to set.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function set($property, $value) {
 | 
			
		||||
    if (property_exists($this, $property)) {
 | 
			
		||||
      $this->{$property} = $value;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->additional[$property] = $value;
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the region for the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The region.
 | 
			
		||||
   */
 | 
			
		||||
  public function getRegion() {
 | 
			
		||||
    return $this->region;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the region for the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $region
 | 
			
		||||
   *   The region.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setRegion($region) {
 | 
			
		||||
    $this->region = $region;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the weight of the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The zero-based weight of the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \UnexpectedValueException
 | 
			
		||||
   *   Thrown if the weight was never set.
 | 
			
		||||
   */
 | 
			
		||||
  public function getWeight() {
 | 
			
		||||
    return $this->weight;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the weight of the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $weight
 | 
			
		||||
   *   The zero-based weight of the component.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setWeight($weight) {
 | 
			
		||||
    $this->weight = $weight;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the component plugin configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed[]
 | 
			
		||||
   *   The component plugin configuration.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getConfiguration() {
 | 
			
		||||
    return $this->configuration;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the plugin configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed[] $configuration
 | 
			
		||||
   *   The plugin configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setConfiguration(array $configuration) {
 | 
			
		||||
    $this->configuration = $configuration;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the plugin ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The plugin ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\Component\Plugin\Exception\PluginException
 | 
			
		||||
   *   Thrown if the plugin ID cannot be found.
 | 
			
		||||
   */
 | 
			
		||||
  public function getPluginId() {
 | 
			
		||||
    if (empty($this->configuration['id'])) {
 | 
			
		||||
      throw new PluginException(sprintf('No plugin ID specified for component with "%s" UUID', $this->uuid));
 | 
			
		||||
    }
 | 
			
		||||
    return $this->configuration['id'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the UUID for this component.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The UUID.
 | 
			
		||||
   */
 | 
			
		||||
  public function getUuid() {
 | 
			
		||||
    return $this->uuid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the plugin for this component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
 | 
			
		||||
   *   An array of contexts to set on the plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Component\Plugin\PluginInspectionInterface
 | 
			
		||||
   *   The plugin.
 | 
			
		||||
   */
 | 
			
		||||
  public function getPlugin(array $contexts = []) {
 | 
			
		||||
    $plugin = $this->pluginManager()->createInstance($this->getPluginId(), $this->getConfiguration());
 | 
			
		||||
    if ($contexts && $plugin instanceof ContextAwarePluginInterface) {
 | 
			
		||||
      $this->contextHandler()->applyContextMapping($plugin, $contexts);
 | 
			
		||||
    }
 | 
			
		||||
    return $plugin;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wraps the component plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Block\BlockManagerInterface
 | 
			
		||||
   *   The plugin manager.
 | 
			
		||||
   */
 | 
			
		||||
  protected function pluginManager() {
 | 
			
		||||
    // @todo Figure out the best way to unify fields and blocks and components
 | 
			
		||||
    //   in https://www.drupal.org/node/1875974.
 | 
			
		||||
    return \Drupal::service('plugin.manager.block');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wraps the context handler.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface
 | 
			
		||||
   *   The context handler.
 | 
			
		||||
   */
 | 
			
		||||
  protected function contextHandler() {
 | 
			
		||||
    return \Drupal::service('context.handler');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wraps the event dispatcher.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
 | 
			
		||||
   *   The event dispatcher.
 | 
			
		||||
   */
 | 
			
		||||
  protected function eventDispatcher() {
 | 
			
		||||
    return \Drupal::service('event_dispatcher');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array representation of the section component.
 | 
			
		||||
   *
 | 
			
		||||
   * Only use this method if you are implementing custom storage for sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array representation of the section component.
 | 
			
		||||
   */
 | 
			
		||||
  public function toArray() {
 | 
			
		||||
    return [
 | 
			
		||||
      'uuid' => $this->getUuid(),
 | 
			
		||||
      'region' => $this->getRegion(),
 | 
			
		||||
      'configuration' => $this->getConfiguration(),
 | 
			
		||||
      'weight' => $this->getWeight(),
 | 
			
		||||
      'additional' => $this->additional,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates an object from an array representation of the section component.
 | 
			
		||||
   *
 | 
			
		||||
   * Only use this method if you are implementing custom storage for sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $component
 | 
			
		||||
   *   An array of section component data in the format returned by ::toArray().
 | 
			
		||||
   *
 | 
			
		||||
   * @return static
 | 
			
		||||
   *   The section component object.
 | 
			
		||||
   */
 | 
			
		||||
  public static function fromArray(array $component) {
 | 
			
		||||
    return (new static(
 | 
			
		||||
      $component['uuid'],
 | 
			
		||||
      $component['region'],
 | 
			
		||||
      $component['configuration'],
 | 
			
		||||
      $component['additional']
 | 
			
		||||
    ))->setWeight($component['weight']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								web/core/modules/layout_builder/src/SectionListInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								web/core/modules/layout_builder/src/SectionListInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the interface for an object that stores layout sections.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\Section
 | 
			
		||||
 */
 | 
			
		||||
interface SectionListInterface extends \Countable {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the layout sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Section[]
 | 
			
		||||
   *   A sequentially and numerically keyed array of section objects.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSections();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a domain object for the layout section.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\Section
 | 
			
		||||
   *   The layout section.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSection($delta);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Appends a new section to the end of the list.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Section $section
 | 
			
		||||
   *   The section to append.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function appendSection(Section $section);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Inserts a new section at a given delta.
 | 
			
		||||
   *
 | 
			
		||||
   * If a section exists at the given index, the section at that position and
 | 
			
		||||
   * others after it are shifted backward.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   * @param \Drupal\layout_builder\Section $section
 | 
			
		||||
   *   The section to insert.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function insertSection($delta, Section $section);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes the section at the given delta.
 | 
			
		||||
   *
 | 
			
		||||
   * As sections are stored sequentially and numerically this will re-key every
 | 
			
		||||
   * subsequent section, shifting them forward.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function removeSection($delta);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes all of the sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $set_blank
 | 
			
		||||
   *   (optional) The default implementation of section lists differentiates
 | 
			
		||||
   *   between a list that has never contained any sections and a list that has
 | 
			
		||||
   *   purposefully had all sections removed in order to remain blank. Passing
 | 
			
		||||
   *   TRUE will mirror ::removeSection() by tracking this as a blank list.
 | 
			
		||||
   *   Passing FALSE will reset the list as though it had never contained any
 | 
			
		||||
   *   sections at all. Defaults to FALSE.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function removeAllSections($set_blank = FALSE);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										188
									
								
								web/core/modules/layout_builder/src/SectionListTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								web/core/modules/layout_builder/src/SectionListTrait.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,188 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a trait for maintaining a list of sections.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\layout_builder\SectionListInterface
 | 
			
		||||
 */
 | 
			
		||||
trait SectionListTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stores the information for all sections.
 | 
			
		||||
   *
 | 
			
		||||
   * Implementations of this method are expected to call array_values() to rekey
 | 
			
		||||
   * the list of sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\layout_builder\Section[] $sections
 | 
			
		||||
   *   An array of section objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function setSections(array $sections);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function count(): int {
 | 
			
		||||
    if ($this->hasBlankSection()) {
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return count($this->getSections());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSection($delta) {
 | 
			
		||||
    if (!$this->hasSection($delta)) {
 | 
			
		||||
      throw new \OutOfBoundsException(sprintf(
 | 
			
		||||
        'Invalid section delta "%s", there are %d sections.',
 | 
			
		||||
        $delta,
 | 
			
		||||
        $this->count()
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
    return $this->getSections()[$delta];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the section for the given delta on the display.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   * @param \Drupal\layout_builder\Section $section
 | 
			
		||||
   *   The layout section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setSection($delta, Section $section) {
 | 
			
		||||
    $sections = $this->getSections();
 | 
			
		||||
    $sections[$delta] = $section;
 | 
			
		||||
    $this->setSections($sections);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function appendSection(Section $section) {
 | 
			
		||||
    $delta = $this->count();
 | 
			
		||||
 | 
			
		||||
    $this->setSection($delta, $section);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function insertSection($delta, Section $section) {
 | 
			
		||||
    // Clear the section list if there is currently a blank section.
 | 
			
		||||
    if ($this->hasBlankSection()) {
 | 
			
		||||
      $this->removeAllSections();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($this->hasSection($delta)) {
 | 
			
		||||
      // @todo Use https://www.drupal.org/node/66183 once resolved.
 | 
			
		||||
      $start = array_slice($this->getSections(), 0, $delta);
 | 
			
		||||
      $end = array_slice($this->getSections(), $delta);
 | 
			
		||||
      $this->setSections(array_merge($start, [$section], $end));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->appendSection($section);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a blank section to the list.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\Plugin\Layout\BlankLayout
 | 
			
		||||
   */
 | 
			
		||||
  protected function addBlankSection() {
 | 
			
		||||
    if ($this->hasSection(0)) {
 | 
			
		||||
      throw new \Exception('A blank section must only be added to an empty list');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->appendSection(new Section('layout_builder_blank'));
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates if this section list contains a blank section.
 | 
			
		||||
   *
 | 
			
		||||
   * A blank section is used to differentiate the difference between a layout
 | 
			
		||||
   * that has never been instantiated and one that has purposefully had all
 | 
			
		||||
   * sections removed.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the section list contains a blank section, FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\layout_builder\Plugin\Layout\BlankLayout
 | 
			
		||||
   */
 | 
			
		||||
  protected function hasBlankSection() {
 | 
			
		||||
    // A blank section will only ever exist when the delta is 0, as added by
 | 
			
		||||
    // ::removeSection().
 | 
			
		||||
    return $this->hasSection(0) && $this->getSection(0)->getLayoutId() === 'layout_builder_blank';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function removeSection($delta) {
 | 
			
		||||
    // Clear the section list if there is currently a blank section.
 | 
			
		||||
    if ($this->hasBlankSection()) {
 | 
			
		||||
      $this->removeAllSections();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $sections = $this->getSections();
 | 
			
		||||
    unset($sections[$delta]);
 | 
			
		||||
    $this->setSections($sections);
 | 
			
		||||
    // Add a blank section when the last section is removed.
 | 
			
		||||
    if (empty($sections)) {
 | 
			
		||||
      $this->addBlankSection();
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function removeAllSections($set_blank = FALSE) {
 | 
			
		||||
    $this->setSections([]);
 | 
			
		||||
    if ($set_blank) {
 | 
			
		||||
      $this->addBlankSection();
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates if there is a section at the specified delta.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the section.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if there is a section for this delta, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected function hasSection($delta) {
 | 
			
		||||
    return isset($this->getSections()[$delta]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Magic method: Implements a deep clone.
 | 
			
		||||
   */
 | 
			
		||||
  public function __clone() {
 | 
			
		||||
    $sections = $this->getSections();
 | 
			
		||||
 | 
			
		||||
    foreach ($sections as $delta => $item) {
 | 
			
		||||
      $sections[$delta] = clone $item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->setSections($sections);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,101 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\SectionStorage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface;
 | 
			
		||||
use Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionTrait;
 | 
			
		||||
use Drupal\Component\Plugin\Definition\PluginDefinition;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides section storage type plugin definition.
 | 
			
		||||
 */
 | 
			
		||||
class SectionStorageDefinition extends PluginDefinition implements ContextAwarePluginDefinitionInterface {
 | 
			
		||||
 | 
			
		||||
  use ContextAwarePluginDefinitionTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin weight.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $weight = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Any additional properties and values.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $additional = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * LayoutDefinition constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $definition
 | 
			
		||||
   *   An array of values from the annotation.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $definition = []) {
 | 
			
		||||
    // If there are context definitions in the plugin definition, they should
 | 
			
		||||
    // be added to this object using ::addContextDefinition() so that they can
 | 
			
		||||
    // be manipulated using other ContextAwarePluginDefinitionInterface methods.
 | 
			
		||||
    if (isset($definition['context_definitions'])) {
 | 
			
		||||
      foreach ($definition['context_definitions'] as $name => $context_definition) {
 | 
			
		||||
        $this->addContextDefinition($name, $context_definition);
 | 
			
		||||
      }
 | 
			
		||||
      unset($definition['context_definitions']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($definition as $property => $value) {
 | 
			
		||||
      $this->set($property, $value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets any arbitrary property.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $property
 | 
			
		||||
   *   The property to retrieve.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   *   The value for that property, or NULL if the property does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function get($property) {
 | 
			
		||||
    if (property_exists($this, $property)) {
 | 
			
		||||
      $value = $this->{$property} ?? NULL;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $value = $this->additional[$property] ?? NULL;
 | 
			
		||||
    }
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets a value to an arbitrary property.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $property
 | 
			
		||||
   *   The property to use for the value.
 | 
			
		||||
   * @param mixed $value
 | 
			
		||||
   *   The value to set.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function set($property, $value) {
 | 
			
		||||
    if (property_exists($this, $property)) {
 | 
			
		||||
      $this->{$property} = $value;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->additional[$property] = $value;
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the plugin weight.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The plugin weight.
 | 
			
		||||
   */
 | 
			
		||||
  public function getWeight() {
 | 
			
		||||
    return $this->weight;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,110 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\SectionStorage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Exception\ContextException;
 | 
			
		||||
use Drupal\Core\Cache\CacheBackendInterface;
 | 
			
		||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\DefaultPluginManager;
 | 
			
		||||
use Drupal\layout_builder\Attribute\SectionStorage;
 | 
			
		||||
use Drupal\layout_builder\SectionStorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides the Section Storage type plugin manager.
 | 
			
		||||
 *
 | 
			
		||||
 * Note that while this class extends \Drupal\Core\Plugin\DefaultPluginManager
 | 
			
		||||
 * and includes many additional public methods, only some of them are available
 | 
			
		||||
 * via \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface.
 | 
			
		||||
 * While internally depending on the parent class is necessary, external code
 | 
			
		||||
 * should only use the methods available on that interface.
 | 
			
		||||
 */
 | 
			
		||||
class SectionStorageManager extends DefaultPluginManager implements SectionStorageManagerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The context handler.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $contextHandler;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new SectionStorageManager object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Traversable $namespaces
 | 
			
		||||
   *   An object that implements \Traversable which contains the root paths
 | 
			
		||||
   *   keyed by the corresponding namespace to look for plugin implementations.
 | 
			
		||||
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
 | 
			
		||||
   *   Cache backend instance to use.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
 | 
			
		||||
   *   The module handler to invoke the alter hook with.
 | 
			
		||||
   * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
 | 
			
		||||
   *   The context handler.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ContextHandlerInterface $context_handler) {
 | 
			
		||||
    parent::__construct('Plugin/SectionStorage', $namespaces, $module_handler, SectionStorageInterface::class, SectionStorage::class, '\Drupal\layout_builder\Annotation\SectionStorage');
 | 
			
		||||
 | 
			
		||||
    $this->contextHandler = $context_handler;
 | 
			
		||||
 | 
			
		||||
    $this->alterInfo('layout_builder_section_storage');
 | 
			
		||||
    $this->setCacheBackend($cache_backend, 'layout_builder_section_storage_plugins');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function findDefinitions() {
 | 
			
		||||
    $definitions = parent::findDefinitions();
 | 
			
		||||
 | 
			
		||||
    // Sort the definitions by their weight while preserving the original order
 | 
			
		||||
    // for those with matching weights.
 | 
			
		||||
    $weights = array_map(function (SectionStorageDefinition $definition) {
 | 
			
		||||
      return $definition->getWeight();
 | 
			
		||||
    }, $definitions);
 | 
			
		||||
    $ids = array_keys($definitions);
 | 
			
		||||
    array_multisort($weights, $ids, $definitions);
 | 
			
		||||
    return $definitions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function load($type, array $contexts = []) {
 | 
			
		||||
    $plugin = $this->loadEmpty($type);
 | 
			
		||||
    try {
 | 
			
		||||
      $this->contextHandler->applyContextMapping($plugin, $contexts);
 | 
			
		||||
    }
 | 
			
		||||
    catch (ContextException) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
    return $plugin;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function findByContext(array $contexts, RefinableCacheableDependencyInterface $cacheability) {
 | 
			
		||||
    $storage_types = array_keys($this->contextHandler->filterPluginDefinitionsByContexts($contexts, $this->getDefinitions()));
 | 
			
		||||
 | 
			
		||||
    // Add the manager as a cacheable dependency in order to vary by changes to
 | 
			
		||||
    // the plugin definitions.
 | 
			
		||||
    $cacheability->addCacheableDependency($this);
 | 
			
		||||
 | 
			
		||||
    foreach ($storage_types as $type) {
 | 
			
		||||
      $plugin = $this->load($type, $contexts);
 | 
			
		||||
      if ($plugin && $plugin->isApplicable($cacheability)) {
 | 
			
		||||
        return $plugin;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function loadEmpty($type) {
 | 
			
		||||
    return $this->createInstance($type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,62 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder\SectionStorage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 | 
			
		||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides the interface for a plugin manager of section storage types.
 | 
			
		||||
 */
 | 
			
		||||
interface SectionStorageManagerInterface extends DiscoveryInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads a section storage with the provided contexts applied.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   The section storage type.
 | 
			
		||||
   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
 | 
			
		||||
   *   (optional) The contexts available for this storage to use.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface|null
 | 
			
		||||
   *   The section storage or NULL if its context requirements are not met.
 | 
			
		||||
   */
 | 
			
		||||
  public function load($type, array $contexts = []);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Finds the section storage to load based on available contexts.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
 | 
			
		||||
   *   The contexts which should be used to determine which storage to return.
 | 
			
		||||
   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
 | 
			
		||||
   *   Refinable cacheability object, which will be populated based on the
 | 
			
		||||
   *   cacheability of each section storage candidate. After calling this method
 | 
			
		||||
   *   this parameter will reflect the cacheability information used to
 | 
			
		||||
   *   determine the correct section storage. This must be associated with any
 | 
			
		||||
   *   output that uses the result of this method.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface|null
 | 
			
		||||
   *   The section storage if one matched all contexts, or NULL otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface
 | 
			
		||||
   */
 | 
			
		||||
  public function findByContext(array $contexts, RefinableCacheableDependencyInterface $cacheability);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads a section storage with no associated section list.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   The type of the section storage being instantiated.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorageInterface
 | 
			
		||||
   *   The section storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   *   Section storage relies on context to load section lists. Use ::load()
 | 
			
		||||
   *   when that context is available. This method is intended for use by
 | 
			
		||||
   *   collaborators of the plugins in build-time situations when section
 | 
			
		||||
   *   storage type must be consulted.
 | 
			
		||||
   */
 | 
			
		||||
  public function loadEmpty($type);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										153
									
								
								web/core/modules/layout_builder/src/SectionStorageInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								web/core/modules/layout_builder/src/SectionStorageInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\PluginInspectionInterface;
 | 
			
		||||
use Drupal\Core\Access\AccessibleInterface;
 | 
			
		||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an interface for Section Storage type plugins.
 | 
			
		||||
 */
 | 
			
		||||
interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface, ContextAwarePluginInterface, AccessibleInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an identifier for this storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The unique identifier for this storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorageId();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the type of this storage.
 | 
			
		||||
   *
 | 
			
		||||
   * Used in conjunction with the storage ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The type of storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorageType();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the routes needed for Layout Builder UI.
 | 
			
		||||
   *
 | 
			
		||||
   * Allows the plugin to add or alter routes during the route building process.
 | 
			
		||||
   * \Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait is provided for the
 | 
			
		||||
   * typical use case of building a standard Layout Builder UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\Routing\RouteCollection $collection
 | 
			
		||||
   *   The route collection.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Routing\RoutingEvents::ALTER
 | 
			
		||||
   */
 | 
			
		||||
  public function buildRoutes(RouteCollection $collection);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the URL used when redirecting away from the Layout Builder UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Url
 | 
			
		||||
   *   The URL object.
 | 
			
		||||
   */
 | 
			
		||||
  public function getRedirectUrl();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the URL used to display the Layout Builder UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $rel
 | 
			
		||||
   *   (optional) The link relationship type, for example: 'view' or 'disable'.
 | 
			
		||||
   *   Defaults to 'view'.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Url
 | 
			
		||||
   *   The URL object.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLayoutBuilderUrl($rel = 'view');
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Derives the available plugin contexts from route values.
 | 
			
		||||
   *
 | 
			
		||||
   * This should only be called during section storage instantiation,
 | 
			
		||||
   * specifically for use by the routing system. For all non-routing usages, use
 | 
			
		||||
   * \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValue().
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $value
 | 
			
		||||
   *   The raw value.
 | 
			
		||||
   * @param mixed $definition
 | 
			
		||||
   *   The parameter definition provided in the route options.
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The name of the parameter.
 | 
			
		||||
   * @param array $defaults
 | 
			
		||||
   *   The route defaults array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
 | 
			
		||||
   *   The available plugin contexts.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\ParamConverter\ParamConverterInterface::convert()
 | 
			
		||||
   */
 | 
			
		||||
  public function deriveContextsFromRoute($value, $definition, $name, array $defaults);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets contexts for use during preview.
 | 
			
		||||
   *
 | 
			
		||||
   * When not in preview, ::getContexts() will be used.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
 | 
			
		||||
   *   The plugin contexts suitable for previewing.
 | 
			
		||||
   */
 | 
			
		||||
  public function getContextsDuringPreview();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the label for the object using the sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The label, or NULL if there is no label defined.
 | 
			
		||||
   */
 | 
			
		||||
  public function label();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves the sections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   SAVED_NEW or SAVED_UPDATED is returned depending on the operation
 | 
			
		||||
   *   performed.
 | 
			
		||||
   */
 | 
			
		||||
  public function save();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if this section storage is applicable for the current contexts.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
 | 
			
		||||
   *   Refinable cacheability object, typically provided by the section storage
 | 
			
		||||
   *   manager. When implementing this method, populate $cacheability with any
 | 
			
		||||
   *   information that affects whether this storage is applicable.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if this section storage is applicable, FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   *   This method is intended to be called by
 | 
			
		||||
   *   \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext().
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface
 | 
			
		||||
   */
 | 
			
		||||
  public function isApplicable(RefinableCacheableDependencyInterface $cacheability);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\layout_builder\SectionStorage\SectionStorageDefinition
 | 
			
		||||
   *   The section storage definition.
 | 
			
		||||
   */
 | 
			
		||||
  public function getPluginDefinition();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Overrides \Drupal\Core\Access\AccessibleInterface::access().
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup layout_builder_access
 | 
			
		||||
   */
 | 
			
		||||
  public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\layout_builder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides an interface that allows an object to provide its own tempstore key.
 | 
			
		||||
 *
 | 
			
		||||
 * @todo Move to \Drupal\Core\TempStore in https://www.drupal.org/node/3026957.
 | 
			
		||||
 */
 | 
			
		||||
interface TempStoreIdentifierInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a string suitable for use as a tempstore key.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A string to be used as the key for a tempstore item.
 | 
			
		||||
   */
 | 
			
		||||
  public function getTempstoreKey();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user