3 namespace Drupal\layout_builder\Plugin\SectionStorage;
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\Entity\EntityFieldManagerInterface;
7 use Drupal\Core\Entity\EntityTypeInterface;
8 use Drupal\Core\Entity\EntityTypeManagerInterface;
9 use Drupal\Core\Entity\FieldableEntityInterface;
10 use Drupal\Core\Field\FieldItemListInterface;
11 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
12 use Drupal\Core\Plugin\Context\EntityContext;
13 use Drupal\Core\Session\AccountInterface;
15 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
16 use Drupal\layout_builder\OverridesSectionStorageInterface;
17 use Drupal\layout_builder\SectionListInterface;
18 use Symfony\Component\DependencyInjection\ContainerInterface;
19 use Symfony\Component\Routing\RouteCollection;
22 * Defines the 'overrides' section storage type.
29 * Layout Builder is currently experimental and should only be leveraged by
30 * experimental modules and development releases of contributed modules.
31 * See https://www.drupal.org/core/experimental for more information.
33 class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, OverridesSectionStorageInterface {
36 * The entity type manager.
38 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
40 protected $entityTypeManager;
43 * The entity field manager.
45 * @var \Drupal\Core\Entity\EntityFieldManagerInterface
47 protected $entityFieldManager;
52 * @var \Drupal\layout_builder\SectionListInterface|\Drupal\Core\Field\FieldItemListInterface
54 protected $sectionList;
59 public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
60 parent::__construct($configuration, $plugin_id, $plugin_definition);
62 $this->entityTypeManager = $entity_type_manager;
63 $this->entityFieldManager = $entity_field_manager;
69 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
74 $container->get('entity_type.manager'),
75 $container->get('entity_field.manager')
82 public function setSectionList(SectionListInterface $section_list) {
83 if (!$section_list instanceof FieldItemListInterface) {
84 throw new \InvalidArgumentException('Overrides expect a field-based section list');
87 return parent::setSectionList($section_list);
91 * Gets the entity storing the overrides.
93 * @return \Drupal\Core\Entity\FieldableEntityInterface
94 * The entity storing the overrides.
96 protected function getEntity() {
97 return $this->getSectionList()->getEntity();
103 public function getStorageId() {
104 $entity = $this->getEntity();
105 return $entity->getEntityTypeId() . '.' . $entity->id();
111 public function extractIdFromRoute($value, $definition, $name, array $defaults) {
112 if (strpos($value, '.') !== FALSE) {
116 if (isset($defaults['entity_type_id']) && !empty($defaults[$defaults['entity_type_id']])) {
117 $entity_type_id = $defaults['entity_type_id'];
118 $entity_id = $defaults[$entity_type_id];
119 return $entity_type_id . '.' . $entity_id;
126 public function getSectionListFromId($id) {
127 if (strpos($id, '.') !== FALSE) {
128 list($entity_type_id, $entity_id) = explode('.', $id, 2);
129 $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id);
130 if ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout')) {
131 return $entity->get('layout_builder__layout');
134 throw new \InvalidArgumentException(sprintf('The "%s" ID for the "%s" section storage type is invalid', $id, $this->getStorageType()));
140 public function buildRoutes(RouteCollection $collection) {
141 foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
143 $defaults['entity_type_id'] = $entity_type_id;
146 if ($this->hasIntegerId($entity_type)) {
147 $requirements[$entity_type_id] = '\d+';
151 // Ensure that upcasting is run in the correct order.
152 $options['parameters']['section_storage'] = [];
153 $options['parameters'][$entity_type_id]['type'] = 'entity:' . $entity_type_id;
155 $template = $entity_type->getLinkTemplate('canonical') . '/layout';
156 $this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $template, $defaults, $requirements, $options, $entity_type_id);
161 * Determines if this entity type's ID is stored as an integer.
163 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
167 * TRUE if this entity type's ID key is always an integer, FALSE otherwise.
169 protected function hasIntegerId(EntityTypeInterface $entity_type) {
170 $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type->id());
171 return $field_storage_definitions[$entity_type->getKey('id')]->getType() === 'integer';
175 * Returns an array of relevant entity types.
177 * @return \Drupal\Core\Entity\EntityTypeInterface[]
178 * An array of entity types.
180 protected function getEntityTypes() {
181 return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
182 return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
189 public function getDefaultSectionStorage() {
190 // @todo Expand to work for all view modes in
191 // https://www.drupal.org/node/2907413.
192 return LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), 'full');
198 public function getRedirectUrl() {
199 return $this->getEntity()->toUrl('canonical');
205 public function getLayoutBuilderUrl($rel = 'view') {
206 $entity = $this->getEntity();
207 $route_parameters[$entity->getEntityTypeId()] = $entity->id();
208 return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.$rel", $route_parameters);
214 public function getContexts() {
215 $entity = $this->getEntity();
216 $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity);
223 public function label() {
224 return $this->getEntity()->label();
230 public function save() {
231 return $this->getEntity()->save();
237 public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
238 $default_section_storage = $this->getDefaultSectionStorage();
239 $result = AccessResult::allowedIf($default_section_storage->isLayoutBuilderEnabled())->addCacheableDependency($default_section_storage);
240 return $return_as_object ? $result : $result->isAllowed();