Updated to Drupal 8.6.4, which is PHP 7.3 friendly. Also updated HTMLaw library....
[yaffs-website] / web / core / modules / layout_builder / src / Entity / LayoutBuilderEntityViewDisplay.php
1 <?php
2
3 namespace Drupal\layout_builder\Entity;
4
5 use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
6 use Drupal\Core\Entity\EntityStorageInterface;
7 use Drupal\Core\Entity\FieldableEntityInterface;
8 use Drupal\Core\Plugin\Context\EntityContext;
9 use Drupal\Core\StringTranslation\TranslatableMarkup;
10 use Drupal\field\Entity\FieldConfig;
11 use Drupal\field\Entity\FieldStorageConfig;
12 use Drupal\layout_builder\Section;
13 use Drupal\layout_builder\SectionComponent;
14 use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
15
16 /**
17  * Provides an entity view display entity that has a layout.
18  *
19  * @internal
20  *   Layout Builder is currently experimental and should only be leveraged by
21  *   experimental modules and development releases of contributed modules.
22  *   See https://www.drupal.org/core/experimental for more information.
23  */
24 class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface {
25
26   use SectionStorageTrait;
27
28   /**
29    * The entity field manager.
30    *
31    * @var \Drupal\Core\Entity\EntityFieldManagerInterface
32    */
33   protected $entityFieldManager;
34
35   /**
36    * {@inheritdoc}
37    */
38   public function __construct(array $values, $entity_type) {
39     // Set $entityFieldManager before calling the parent constructor because the
40     // constructor will call init() which then calls setComponent() which needs
41     // $entityFieldManager.
42     $this->entityFieldManager = \Drupal::service('entity_field.manager');
43     parent::__construct($values, $entity_type);
44   }
45
46   /**
47    * {@inheritdoc}
48    */
49   public function isOverridable() {
50     return $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE);
51   }
52
53   /**
54    * {@inheritdoc}
55    */
56   public function setOverridable($overridable = TRUE) {
57     $this->setThirdPartySetting('layout_builder', 'allow_custom', $overridable);
58     return $this;
59   }
60
61   /**
62    * {@inheritdoc}
63    */
64   public function isLayoutBuilderEnabled() {
65     return (bool) $this->getThirdPartySetting('layout_builder', 'enabled');
66   }
67
68   /**
69    * {@inheritdoc}
70    */
71   public function enableLayoutBuilder() {
72     $this->setThirdPartySetting('layout_builder', 'enabled', TRUE);
73     return $this;
74   }
75
76   /**
77    * {@inheritdoc}
78    */
79   public function disableLayoutBuilder() {
80     $this->setOverridable(FALSE);
81     $this->setThirdPartySetting('layout_builder', 'enabled', FALSE);
82     return $this;
83   }
84
85   /**
86    * {@inheritdoc}
87    */
88   public function getSections() {
89     return $this->getThirdPartySetting('layout_builder', 'sections', []);
90   }
91
92   /**
93    * {@inheritdoc}
94    */
95   protected function setSections(array $sections) {
96     $this->setThirdPartySetting('layout_builder', 'sections', array_values($sections));
97     return $this;
98   }
99
100   /**
101    * {@inheritdoc}
102    */
103   public function preSave(EntityStorageInterface $storage) {
104     parent::preSave($storage);
105
106     $original_value = isset($this->original) ? $this->original->isOverridable() : FALSE;
107     $new_value = $this->isOverridable();
108     if ($original_value !== $new_value) {
109       $entity_type_id = $this->getTargetEntityTypeId();
110       $bundle = $this->getTargetBundle();
111
112       if ($new_value) {
113         $this->addSectionField($entity_type_id, $bundle, 'layout_builder__layout');
114       }
115       else {
116         $this->removeSectionField($entity_type_id, $bundle, 'layout_builder__layout');
117       }
118     }
119
120     $already_enabled = isset($this->original) ? $this->original->isLayoutBuilderEnabled() : FALSE;
121     $set_enabled = $this->isLayoutBuilderEnabled();
122     if ($already_enabled !== $set_enabled) {
123       if ($set_enabled) {
124         // Loop through all existing field-based components and add them as
125         // section-based components.
126         $components = $this->getComponents();
127         // Sort the components by weight.
128         uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
129         foreach ($components as $name => $component) {
130           $this->setComponent($name, $component);
131         }
132       }
133       else {
134         // When being disabled, remove all existing section data.
135         while (count($this) > 0) {
136           $this->removeSection(0);
137         }
138       }
139     }
140   }
141
142   /**
143    * Removes a layout section field if it is no longer needed.
144    *
145    * Because the field is shared across all view modes, the field will only be
146    * removed if no other view modes are using it.
147    *
148    * @param string $entity_type_id
149    *   The entity type ID.
150    * @param string $bundle
151    *   The bundle.
152    * @param string $field_name
153    *   The name for the layout section field.
154    */
155   protected function removeSectionField($entity_type_id, $bundle, $field_name) {
156     $query = $this->entityTypeManager()->getStorage($this->getEntityTypeId())->getQuery()
157       ->condition('targetEntityType', $this->getTargetEntityTypeId())
158       ->condition('bundle', $this->getTargetBundle())
159       ->condition('mode', $this->getMode(), '<>')
160       ->condition('third_party_settings.layout_builder.allow_custom', TRUE);
161     $enabled = (bool) $query->count()->execute();
162     if (!$enabled && $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name)) {
163       $field->delete();
164     }
165   }
166
167   /**
168    * Adds a layout section field to a given bundle.
169    *
170    * @param string $entity_type_id
171    *   The entity type ID.
172    * @param string $bundle
173    *   The bundle.
174    * @param string $field_name
175    *   The name for the layout section field.
176    */
177   protected function addSectionField($entity_type_id, $bundle, $field_name) {
178     $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
179     if (!$field) {
180       $field_storage = FieldStorageConfig::loadByName($entity_type_id, $field_name);
181       if (!$field_storage) {
182         $field_storage = FieldStorageConfig::create([
183           'entity_type' => $entity_type_id,
184           'field_name' => $field_name,
185           'type' => 'layout_section',
186           'locked' => TRUE,
187         ]);
188         $field_storage->save();
189       }
190
191       $field = FieldConfig::create([
192         'field_storage' => $field_storage,
193         'bundle' => $bundle,
194         'label' => t('Layout'),
195       ]);
196       $field->save();
197     }
198   }
199
200   /**
201    * {@inheritdoc}
202    */
203   public function createCopy($mode) {
204     // Disable Layout Builder and remove any sections copied from the original.
205     return parent::createCopy($mode)
206       ->setSections([])
207       ->disableLayoutBuilder();
208   }
209
210   /**
211    * {@inheritdoc}
212    */
213   protected function getDefaultRegion() {
214     if ($this->hasSection(0)) {
215       return $this->getSection(0)->getDefaultRegion();
216     }
217
218     return parent::getDefaultRegion();
219   }
220
221   /**
222    * Wraps the context repository service.
223    *
224    * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
225    *   The context repository service.
226    */
227   protected function contextRepository() {
228     return \Drupal::service('context.repository');
229   }
230
231   /**
232    * {@inheritdoc}
233    */
234   public function buildMultiple(array $entities) {
235     $build_list = parent::buildMultiple($entities);
236     if (!$this->isLayoutBuilderEnabled()) {
237       return $build_list;
238     }
239
240     /** @var \Drupal\Core\Entity\EntityInterface $entity */
241     foreach ($entities as $id => $entity) {
242       $sections = $this->getRuntimeSections($entity);
243       if ($sections) {
244         foreach ($build_list[$id] as $name => $build_part) {
245           $field_definition = $this->getFieldDefinition($name);
246           if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) {
247             unset($build_list[$id][$name]);
248           }
249         }
250
251         // Bypass ::getContexts() in order to use the runtime entity, not a
252         // sample entity.
253         $contexts = $this->contextRepository()->getAvailableContexts();
254         $label = new TranslatableMarkup('@entity being viewed', [
255           '@entity' => $entity->getEntityType()->getSingularLabel(),
256         ]);
257         $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label);
258         foreach ($sections as $delta => $section) {
259           $build_list[$id]['_layout_builder'][$delta] = $section->toRenderArray($contexts);
260         }
261       }
262     }
263
264     return $build_list;
265   }
266
267   /**
268    * Gets the runtime sections for a given entity.
269    *
270    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
271    *   The entity.
272    *
273    * @return \Drupal\layout_builder\Section[]
274    *   The sections.
275    */
276   protected function getRuntimeSections(FieldableEntityInterface $entity) {
277     if ($this->isOverridable() && !$entity->get('layout_builder__layout')->isEmpty()) {
278       return $entity->get('layout_builder__layout')->getSections();
279     }
280
281     return $this->getSections();
282   }
283
284   /**
285    * {@inheritdoc}
286    *
287    * @todo Move this upstream in https://www.drupal.org/node/2939931.
288    */
289   public function label() {
290     $bundle_info = \Drupal::service('entity_type.bundle.info')->getBundleInfo($this->getTargetEntityTypeId());
291     $bundle_label = $bundle_info[$this->getTargetBundle()]['label'];
292     $target_entity_type = $this->entityTypeManager()->getDefinition($this->getTargetEntityTypeId());
293     return new TranslatableMarkup('@bundle @label', ['@bundle' => $bundle_label, '@label' => $target_entity_type->getPluralLabel()]);
294   }
295
296   /**
297    * {@inheritdoc}
298    */
299   public function calculateDependencies() {
300     parent::calculateDependencies();
301
302     foreach ($this->getSections() as $delta => $section) {
303       $this->calculatePluginDependencies($section->getLayout());
304       foreach ($section->getComponents() as $uuid => $component) {
305         $this->calculatePluginDependencies($component->getPlugin());
306       }
307     }
308
309     return $this;
310   }
311
312   /**
313    * {@inheritdoc}
314    */
315   public function onDependencyRemoval(array $dependencies) {
316     $changed = parent::onDependencyRemoval($dependencies);
317
318     // Loop through all sections and determine if the removed dependencies are
319     // used by their layout plugins.
320     foreach ($this->getSections() as $delta => $section) {
321       $layout_dependencies = $this->getPluginDependencies($section->getLayout());
322       $layout_removed_dependencies = $this->getPluginRemovedDependencies($layout_dependencies, $dependencies);
323       if ($layout_removed_dependencies) {
324         // @todo Allow the plugins to react to their dependency removal in
325         //   https://www.drupal.org/project/drupal/issues/2579743.
326         $this->removeSection($delta);
327         $changed = TRUE;
328       }
329       // If the section is not removed, loop through all components.
330       else {
331         foreach ($section->getComponents() as $uuid => $component) {
332           $plugin_dependencies = $this->getPluginDependencies($component->getPlugin());
333           $component_removed_dependencies = $this->getPluginRemovedDependencies($plugin_dependencies, $dependencies);
334           if ($component_removed_dependencies) {
335             // @todo Allow the plugins to react to their dependency removal in
336             //   https://www.drupal.org/project/drupal/issues/2579743.
337             $section->removeComponent($uuid);
338             $changed = TRUE;
339           }
340         }
341       }
342     }
343     return $changed;
344   }
345
346   /**
347    * {@inheritdoc}
348    */
349   public function setComponent($name, array $options = []) {
350     parent::setComponent($name, $options);
351
352     // Only continue if Layout Builder is enabled.
353     if (!$this->isLayoutBuilderEnabled()) {
354       return $this;
355     }
356
357     // Retrieve the updated options after the parent:: call.
358     $options = $this->content[$name];
359     // Provide backwards compatibility by converting to a section component.
360     $field_definition = $this->getFieldDefinition($name);
361     $extra_fields = $this->entityFieldManager->getExtraFields($this->getTargetEntityTypeId(), $this->getTargetBundle());
362     $is_view_configurable_non_extra_field = $field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type']);
363     if ($is_view_configurable_non_extra_field || isset($extra_fields['display'][$name])) {
364       $configuration = [
365         'label_display' => '0',
366         'context_mapping' => ['entity' => 'layout_builder.entity'],
367       ];
368       if ($is_view_configurable_non_extra_field) {
369         $configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
370         $keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
371         $configuration['formatter'] = array_intersect_key($options, $keys);
372       }
373       else {
374         $configuration['id'] = 'extra_field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
375       }
376
377       $section = $this->getDefaultSection();
378       $region = isset($options['region']) ? $options['region'] : $section->getDefaultRegion();
379       $new_component = (new SectionComponent(\Drupal::service('uuid')->generate(), $region, $configuration));
380       $section->appendComponent($new_component);
381     }
382     return $this;
383   }
384
385   /**
386    * Gets a default section.
387    *
388    * @return \Drupal\layout_builder\Section
389    *   The default section.
390    */
391   protected function getDefaultSection() {
392     // If no section exists, append a new one.
393     if (!$this->hasSection(0)) {
394       $this->appendSection(new Section('layout_onecol'));
395     }
396
397     // Return the first section.
398     return $this->getSection(0);
399   }
400
401 }