Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / modules / layout_builder / src / Entity / LayoutBuilderEntityViewDisplay.php
1 <?php
2
3 namespace Drupal\layout_builder\Entity;
4
5 use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
6 use Drupal\Component\Plugin\DependentPluginInterface;
7 use Drupal\Component\Plugin\PluginInspectionInterface;
8 use Drupal\Component\Utility\NestedArray;
9 use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
10 use Drupal\Core\Entity\EntityStorageInterface;
11 use Drupal\Core\Entity\FieldableEntityInterface;
12 use Drupal\Core\Plugin\Context\Context;
13 use Drupal\Core\Plugin\Context\ContextDefinition;
14 use Drupal\Core\Plugin\Definition\DependentPluginDefinitionInterface;
15 use Drupal\Core\StringTranslation\TranslatableMarkup;
16 use Drupal\field\Entity\FieldConfig;
17 use Drupal\field\Entity\FieldStorageConfig;
18 use Drupal\layout_builder\Section;
19 use Drupal\layout_builder\SectionComponent;
20 use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
21
22 /**
23  * Provides an entity view display entity that has a layout.
24  *
25  * @internal
26  *   Layout Builder is currently experimental and should only be leveraged by
27  *   experimental modules and development releases of contributed modules.
28  *   See https://www.drupal.org/core/experimental for more information.
29  */
30 class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface {
31
32   use SectionStorageTrait;
33
34   /**
35    * {@inheritdoc}
36    */
37   public function isOverridable() {
38     return $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE);
39   }
40
41   /**
42    * {@inheritdoc}
43    */
44   public function setOverridable($overridable = TRUE) {
45     $this->setThirdPartySetting('layout_builder', 'allow_custom', $overridable);
46     return $this;
47   }
48
49   /**
50    * {@inheritdoc}
51    */
52   public function getSections() {
53     return $this->getThirdPartySetting('layout_builder', 'sections', []);
54   }
55
56   /**
57    * {@inheritdoc}
58    */
59   protected function setSections(array $sections) {
60     $this->setThirdPartySetting('layout_builder', 'sections', array_values($sections));
61     return $this;
62   }
63
64   /**
65    * {@inheritdoc}
66    */
67   public function preSave(EntityStorageInterface $storage) {
68     parent::preSave($storage);
69
70     $original_value = isset($this->original) ? $this->original->isOverridable() : FALSE;
71     $new_value = $this->isOverridable();
72     if ($original_value !== $new_value) {
73       $entity_type_id = $this->getTargetEntityTypeId();
74       $bundle = $this->getTargetBundle();
75
76       if ($new_value) {
77         $this->addSectionField($entity_type_id, $bundle, 'layout_builder__layout');
78       }
79       elseif ($field = FieldConfig::loadByName($entity_type_id, $bundle, 'layout_builder__layout')) {
80         $field->delete();
81       }
82     }
83   }
84
85   /**
86    * Adds a layout section field to a given bundle.
87    *
88    * @param string $entity_type_id
89    *   The entity type ID.
90    * @param string $bundle
91    *   The bundle.
92    * @param string $field_name
93    *   The name for the layout section field.
94    */
95   protected function addSectionField($entity_type_id, $bundle, $field_name) {
96     $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
97     if (!$field) {
98       $field_storage = FieldStorageConfig::loadByName($entity_type_id, $field_name);
99       if (!$field_storage) {
100         $field_storage = FieldStorageConfig::create([
101           'entity_type' => $entity_type_id,
102           'field_name' => $field_name,
103           'type' => 'layout_section',
104           'locked' => TRUE,
105         ]);
106         $field_storage->save();
107       }
108
109       $field = FieldConfig::create([
110         'field_storage' => $field_storage,
111         'bundle' => $bundle,
112         'label' => t('Layout'),
113       ]);
114       $field->save();
115     }
116   }
117
118   /**
119    * {@inheritdoc}
120    */
121   protected function getDefaultRegion() {
122     if ($this->hasSection(0)) {
123       return $this->getSection(0)->getDefaultRegion();
124     }
125
126     return parent::getDefaultRegion();
127   }
128
129   /**
130    * Wraps the context repository service.
131    *
132    * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
133    *   The context repository service.
134    */
135   protected function contextRepository() {
136     return \Drupal::service('context.repository');
137   }
138
139   /**
140    * {@inheritdoc}
141    */
142   public function buildMultiple(array $entities) {
143     $build_list = parent::buildMultiple($entities);
144
145     foreach ($entities as $id => $entity) {
146       $sections = $this->getRuntimeSections($entity);
147       if ($sections) {
148         foreach ($build_list[$id] as $name => $build_part) {
149           $field_definition = $this->getFieldDefinition($name);
150           if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) {
151             unset($build_list[$id][$name]);
152           }
153         }
154
155         // Bypass ::getContexts() in order to use the runtime entity, not a
156         // sample entity.
157         $contexts = $this->contextRepository()->getAvailableContexts();
158         // @todo Use EntityContextDefinition after resolving
159         //   https://www.drupal.org/node/2932462.
160         $contexts['layout_builder.entity'] = new Context(new ContextDefinition("entity:{$entity->getEntityTypeId()}", new TranslatableMarkup('@entity being viewed', ['@entity' => $entity->getEntityType()->getLabel()])), $entity);
161         foreach ($sections as $delta => $section) {
162           $build_list[$id]['_layout_builder'][$delta] = $section->toRenderArray($contexts);
163         }
164       }
165     }
166
167     return $build_list;
168   }
169
170   /**
171    * Gets the runtime sections for a given entity.
172    *
173    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
174    *   The entity.
175    *
176    * @return \Drupal\layout_builder\Section[]
177    *   The sections.
178    */
179   protected function getRuntimeSections(FieldableEntityInterface $entity) {
180     if ($this->isOverridable() && !$entity->get('layout_builder__layout')->isEmpty()) {
181       return $entity->get('layout_builder__layout')->getSections();
182     }
183
184     return $this->getSections();
185   }
186
187   /**
188    * {@inheritdoc}
189    *
190    * @todo Move this upstream in https://www.drupal.org/node/2939931.
191    */
192   public function label() {
193     $bundle_info = \Drupal::service('entity_type.bundle.info')->getBundleInfo($this->getTargetEntityTypeId());
194     $bundle_label = $bundle_info[$this->getTargetBundle()]['label'];
195     $target_entity_type = $this->entityTypeManager()->getDefinition($this->getTargetEntityTypeId());
196     return new TranslatableMarkup('@bundle @label', ['@bundle' => $bundle_label, '@label' => $target_entity_type->getPluralLabel()]);
197   }
198
199   /**
200    * {@inheritdoc}
201    */
202   public function calculateDependencies() {
203     parent::calculateDependencies();
204
205     foreach ($this->getSections() as $delta => $section) {
206       $this->calculatePluginDependencies($section->getLayout());
207       foreach ($section->getComponents() as $uuid => $component) {
208         $this->calculatePluginDependencies($component->getPlugin());
209       }
210     }
211
212     return $this;
213   }
214
215   /**
216    * {@inheritdoc}
217    */
218   public function onDependencyRemoval(array $dependencies) {
219     $changed = parent::onDependencyRemoval($dependencies);
220
221     // Loop through all sections and determine if the removed dependencies are
222     // used by their layout plugins.
223     foreach ($this->getSections() as $delta => $section) {
224       $layout_dependencies = $this->getPluginDependencies($section->getLayout());
225       $layout_removed_dependencies = $this->getPluginRemovedDependencies($layout_dependencies, $dependencies);
226       if ($layout_removed_dependencies) {
227         // @todo Allow the plugins to react to their dependency removal in
228         //   https://www.drupal.org/project/drupal/issues/2579743.
229         $this->removeSection($delta);
230         $changed = TRUE;
231       }
232       // If the section is not removed, loop through all components.
233       else {
234         foreach ($section->getComponents() as $uuid => $component) {
235           $plugin_dependencies = $this->getPluginDependencies($component->getPlugin());
236           $component_removed_dependencies = $this->getPluginRemovedDependencies($plugin_dependencies, $dependencies);
237           if ($component_removed_dependencies) {
238             // @todo Allow the plugins to react to their dependency removal in
239             //   https://www.drupal.org/project/drupal/issues/2579743.
240             $section->removeComponent($uuid);
241             $changed = TRUE;
242           }
243         }
244       }
245     }
246     return $changed;
247   }
248
249   /**
250    * Calculates and returns dependencies of a specific plugin instance.
251    *
252    * @param \Drupal\Component\Plugin\PluginInspectionInterface $instance
253    *   The plugin instance.
254    *
255    * @return array
256    *   An array of dependencies keyed by the type of dependency.
257    *
258    * @todo Replace this in https://www.drupal.org/project/drupal/issues/2939925.
259    */
260   protected function getPluginDependencies(PluginInspectionInterface $instance) {
261     $dependencies = [];
262     $definition = $instance->getPluginDefinition();
263     if ($definition instanceof PluginDefinitionInterface) {
264       $dependencies['module'][] = $definition->getProvider();
265       if ($definition instanceof DependentPluginDefinitionInterface && $config_dependencies = $definition->getConfigDependencies()) {
266         $dependencies = NestedArray::mergeDeep($dependencies, $config_dependencies);
267       }
268     }
269     elseif (is_array($definition)) {
270       $dependencies['module'][] = $definition['provider'];
271       // Plugins can declare additional dependencies in their definition.
272       if (isset($definition['config_dependencies'])) {
273         $dependencies = NestedArray::mergeDeep($dependencies, $definition['config_dependencies']);
274       }
275     }
276
277     // If a plugin is dependent, calculate its dependencies.
278     if ($instance instanceof DependentPluginInterface && $plugin_dependencies = $instance->calculateDependencies()) {
279       $dependencies = NestedArray::mergeDeep($dependencies, $plugin_dependencies);
280     }
281     return $dependencies;
282   }
283
284   /**
285    * {@inheritdoc}
286    */
287   public function setComponent($name, array $options = []) {
288     parent::setComponent($name, $options);
289
290     // @todo Remove workaround for EntityViewBuilder::getSingleFieldDisplay() in
291     //   https://www.drupal.org/project/drupal/issues/2936464.
292     if ($this->getMode() === static::CUSTOM_MODE) {
293       return $this;
294     }
295
296     // Retrieve the updated options after the parent:: call.
297     $options = $this->content[$name];
298     // Provide backwards compatibility by converting to a section component.
299     $field_definition = $this->getFieldDefinition($name);
300     if ($field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type'])) {
301       $configuration = [];
302       $configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
303       $configuration['label_display'] = FALSE;
304       $keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
305       $configuration['formatter'] = array_intersect_key($options, $keys);
306       $configuration['context_mapping']['entity'] = 'layout_builder.entity';
307
308       $section = $this->getDefaultSection();
309       $region = isset($options['region']) ? $options['region'] : $section->getDefaultRegion();
310       $new_component = (new SectionComponent(\Drupal::service('uuid')->generate(), $region, $configuration));
311       $section->appendComponent($new_component);
312     }
313     return $this;
314   }
315
316   /**
317    * Gets a default section.
318    *
319    * @return \Drupal\layout_builder\Section
320    *   The default section.
321    */
322   protected function getDefaultSection() {
323     // If no section exists, append a new one.
324     if (!$this->hasSection(0)) {
325       $this->appendSection(new Section('layout_onecol'));
326     }
327
328     // Return the first section.
329     return $this->getSection(0);
330   }
331
332 }