Pull merge.
[yaffs-website] / web / core / modules / layout_builder / src / Plugin / SectionStorage / OverridesSectionStorage.php
1 <?php
2
3 namespace Drupal\layout_builder\Plugin\SectionStorage;
4
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;
14 use Drupal\Core\Url;
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;
20
21 /**
22  * Defines the 'overrides' section storage type.
23  *
24  * @SectionStorage(
25  *   id = "overrides",
26  * )
27  *
28  * @internal
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.
32  */
33 class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, OverridesSectionStorageInterface, SectionStorageLocalTaskProviderInterface {
34
35   /**
36    * The field name used by this storage.
37    *
38    * @var string
39    */
40   const FIELD_NAME = 'layout_builder__layout';
41
42   /**
43    * The entity type manager.
44    *
45    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
46    */
47   protected $entityTypeManager;
48
49   /**
50    * The entity field manager.
51    *
52    * @var \Drupal\Core\Entity\EntityFieldManagerInterface
53    */
54   protected $entityFieldManager;
55
56   /**
57    * {@inheritdoc}
58    *
59    * @var \Drupal\layout_builder\SectionListInterface|\Drupal\Core\Field\FieldItemListInterface
60    */
61   protected $sectionList;
62
63   /**
64    * {@inheritdoc}
65    */
66   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
67     parent::__construct($configuration, $plugin_id, $plugin_definition);
68
69     $this->entityTypeManager = $entity_type_manager;
70     $this->entityFieldManager = $entity_field_manager;
71   }
72
73   /**
74    * {@inheritdoc}
75    */
76   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
77     return new static(
78       $configuration,
79       $plugin_id,
80       $plugin_definition,
81       $container->get('entity_type.manager'),
82       $container->get('entity_field.manager')
83     );
84   }
85
86   /**
87    * {@inheritdoc}
88    */
89   public function setSectionList(SectionListInterface $section_list) {
90     if (!$section_list instanceof FieldItemListInterface) {
91       throw new \InvalidArgumentException('Overrides expect a field-based section list');
92     }
93
94     return parent::setSectionList($section_list);
95   }
96
97   /**
98    * Gets the entity storing the overrides.
99    *
100    * @return \Drupal\Core\Entity\FieldableEntityInterface
101    *   The entity storing the overrides.
102    */
103   protected function getEntity() {
104     return $this->getSectionList()->getEntity();
105   }
106
107   /**
108    * {@inheritdoc}
109    */
110   public function getStorageId() {
111     $entity = $this->getEntity();
112     return $entity->getEntityTypeId() . '.' . $entity->id();
113   }
114
115   /**
116    * {@inheritdoc}
117    */
118   public function extractIdFromRoute($value, $definition, $name, array $defaults) {
119     if (strpos($value, '.') !== FALSE) {
120       return $value;
121     }
122
123     if (isset($defaults['entity_type_id']) && !empty($defaults[$defaults['entity_type_id']])) {
124       $entity_type_id = $defaults['entity_type_id'];
125       $entity_id = $defaults[$entity_type_id];
126       return $entity_type_id . '.' . $entity_id;
127     }
128   }
129
130   /**
131    * {@inheritdoc}
132    */
133   public function getSectionListFromId($id) {
134     if (strpos($id, '.') !== FALSE) {
135       list($entity_type_id, $entity_id) = explode('.', $id, 2);
136       $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id);
137       if ($entity instanceof FieldableEntityInterface && $entity->hasField(static::FIELD_NAME)) {
138         return $entity->get(static::FIELD_NAME);
139       }
140     }
141     throw new \InvalidArgumentException(sprintf('The "%s" ID for the "%s" section storage type is invalid', $id, $this->getStorageType()));
142   }
143
144   /**
145    * {@inheritdoc}
146    */
147   public function buildRoutes(RouteCollection $collection) {
148     foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
149       $defaults = [];
150       $defaults['entity_type_id'] = $entity_type_id;
151
152       $requirements = [];
153       if ($this->hasIntegerId($entity_type)) {
154         $requirements[$entity_type_id] = '\d+';
155       }
156
157       $options = [];
158       // Ensure that upcasting is run in the correct order.
159       $options['parameters']['section_storage'] = [];
160       $options['parameters'][$entity_type_id]['type'] = 'entity:' . $entity_type_id;
161
162       $template = $entity_type->getLinkTemplate('canonical') . '/layout';
163       $this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $template, $defaults, $requirements, $options, $entity_type_id);
164     }
165   }
166
167   /**
168    * {@inheritdoc}
169    */
170   public function buildLocalTasks($base_plugin_definition) {
171     $local_tasks = [];
172     foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
173       $local_tasks["layout_builder.overrides.$entity_type_id.view"] = $base_plugin_definition + [
174         'route_name' => "layout_builder.overrides.$entity_type_id.view",
175         'weight' => 15,
176         'title' => $this->t('Layout'),
177         'base_route' => "entity.$entity_type_id.canonical",
178         'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
179       ];
180       $local_tasks["layout_builder.overrides.$entity_type_id.save"] = $base_plugin_definition + [
181         'route_name' => "layout_builder.overrides.$entity_type_id.save",
182         'title' => $this->t('Save Layout'),
183         'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
184         'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
185       ];
186       $local_tasks["layout_builder.overrides.$entity_type_id.cancel"] = $base_plugin_definition + [
187         'route_name' => "layout_builder.overrides.$entity_type_id.cancel",
188         'title' => $this->t('Cancel Layout'),
189         'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
190         'weight' => 5,
191         'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
192       ];
193       // @todo This link should be conditionally displayed, see
194       //   https://www.drupal.org/node/2917777.
195       $local_tasks["layout_builder.overrides.$entity_type_id.revert"] = $base_plugin_definition + [
196         'route_name' => "layout_builder.overrides.$entity_type_id.revert",
197         'title' => $this->t('Revert to defaults'),
198         'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
199         'weight' => 10,
200         'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
201       ];
202     }
203     return $local_tasks;
204   }
205
206   /**
207    * Determines if this entity type's ID is stored as an integer.
208    *
209    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
210    *   An entity type.
211    *
212    * @return bool
213    *   TRUE if this entity type's ID key is always an integer, FALSE otherwise.
214    */
215   protected function hasIntegerId(EntityTypeInterface $entity_type) {
216     $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type->id());
217     return $field_storage_definitions[$entity_type->getKey('id')]->getType() === 'integer';
218   }
219
220   /**
221    * Returns an array of relevant entity types.
222    *
223    * @return \Drupal\Core\Entity\EntityTypeInterface[]
224    *   An array of entity types.
225    */
226   protected function getEntityTypes() {
227     return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
228       return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
229     });
230   }
231
232   /**
233    * {@inheritdoc}
234    */
235   public function getDefaultSectionStorage() {
236     // @todo Expand to work for all view modes in
237     //   https://www.drupal.org/node/2907413.
238     return LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), 'full');
239   }
240
241   /**
242    * {@inheritdoc}
243    */
244   public function getRedirectUrl() {
245     return $this->getEntity()->toUrl('canonical');
246   }
247
248   /**
249    * {@inheritdoc}
250    */
251   public function getLayoutBuilderUrl($rel = 'view') {
252     $entity = $this->getEntity();
253     $route_parameters[$entity->getEntityTypeId()] = $entity->id();
254     return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.$rel", $route_parameters);
255   }
256
257   /**
258    * {@inheritdoc}
259    */
260   public function getContexts() {
261     $entity = $this->getEntity();
262     $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity);
263     return $contexts;
264   }
265
266   /**
267    * {@inheritdoc}
268    */
269   public function label() {
270     return $this->getEntity()->label();
271   }
272
273   /**
274    * {@inheritdoc}
275    */
276   public function save() {
277     return $this->getEntity()->save();
278   }
279
280   /**
281    * {@inheritdoc}
282    */
283   public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
284     $default_section_storage = $this->getDefaultSectionStorage();
285     $result = AccessResult::allowedIf($default_section_storage->isLayoutBuilderEnabled())->addCacheableDependency($default_section_storage);
286     return $return_as_object ? $result : $result->isAllowed();
287   }
288
289 }