Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @ 5:12f9dff5fda9 tip
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:34:47 +0100 |
parents | a9cd425dd02b |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
1 <?php | 1 <?php |
2 | 2 |
3 namespace Drupal\layout_builder\Entity; | 3 namespace Drupal\layout_builder\Entity; |
4 | 4 |
5 use Drupal\Component\Plugin\ConfigurableInterface; | |
6 use Drupal\Component\Plugin\DerivativeInspectionInterface; | |
7 use Drupal\Component\Plugin\PluginBase; | |
8 use Drupal\Core\Cache\CacheableMetadata; | |
5 use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay; | 9 use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay; |
6 use Drupal\Core\Entity\EntityStorageInterface; | 10 use Drupal\Core\Entity\EntityStorageInterface; |
7 use Drupal\Core\Entity\FieldableEntityInterface; | 11 use Drupal\Core\Entity\FieldableEntityInterface; |
12 use Drupal\Core\Plugin\Context\Context; | |
13 use Drupal\Core\Plugin\Context\ContextDefinition; | |
8 use Drupal\Core\Plugin\Context\EntityContext; | 14 use Drupal\Core\Plugin\Context\EntityContext; |
15 use Drupal\Core\Render\Element; | |
9 use Drupal\Core\StringTranslation\TranslatableMarkup; | 16 use Drupal\Core\StringTranslation\TranslatableMarkup; |
10 use Drupal\field\Entity\FieldConfig; | 17 use Drupal\field\Entity\FieldConfig; |
11 use Drupal\field\Entity\FieldStorageConfig; | 18 use Drupal\field\Entity\FieldStorageConfig; |
19 use Drupal\layout_builder\LayoutEntityHelperTrait; | |
12 use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage; | 20 use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage; |
21 use Drupal\layout_builder\QuickEditIntegration; | |
13 use Drupal\layout_builder\Section; | 22 use Drupal\layout_builder\Section; |
14 use Drupal\layout_builder\SectionComponent; | 23 use Drupal\layout_builder\SectionComponent; |
15 use Drupal\layout_builder\SectionStorage\SectionStorageTrait; | 24 use Drupal\layout_builder\SectionStorage\SectionStorageTrait; |
16 | 25 |
17 /** | 26 /** |
18 * Provides an entity view display entity that has a layout. | 27 * Provides an entity view display entity that has a layout. |
19 * | |
20 * @internal | |
21 * Layout Builder is currently experimental and should only be leveraged by | |
22 * experimental modules and development releases of contributed modules. | |
23 * See https://www.drupal.org/core/experimental for more information. | |
24 */ | 28 */ |
25 class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface { | 29 class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface { |
26 | 30 |
27 use SectionStorageTrait; | 31 use SectionStorageTrait; |
32 use LayoutEntityHelperTrait; | |
28 | 33 |
29 /** | 34 /** |
30 * The entity field manager. | 35 * The entity field manager. |
31 * | 36 * |
32 * @var \Drupal\Core\Entity\EntityFieldManagerInterface | 37 * @var \Drupal\Core\Entity\EntityFieldManagerInterface |
46 | 51 |
47 /** | 52 /** |
48 * {@inheritdoc} | 53 * {@inheritdoc} |
49 */ | 54 */ |
50 public function isOverridable() { | 55 public function isOverridable() { |
51 return $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE); | 56 return $this->isLayoutBuilderEnabled() && $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE); |
52 } | 57 } |
53 | 58 |
54 /** | 59 /** |
55 * {@inheritdoc} | 60 * {@inheritdoc} |
56 */ | 61 */ |
57 public function setOverridable($overridable = TRUE) { | 62 public function setOverridable($overridable = TRUE) { |
58 $this->setThirdPartySetting('layout_builder', 'allow_custom', $overridable); | 63 $this->setThirdPartySetting('layout_builder', 'allow_custom', $overridable); |
64 // Enable Layout Builder if it's not already enabled and overriding. | |
65 if ($overridable && !$this->isLayoutBuilderEnabled()) { | |
66 $this->enableLayoutBuilder(); | |
67 } | |
59 return $this; | 68 return $this; |
60 } | 69 } |
61 | 70 |
62 /** | 71 /** |
63 * {@inheritdoc} | 72 * {@inheritdoc} |
64 */ | 73 */ |
65 public function isLayoutBuilderEnabled() { | 74 public function isLayoutBuilderEnabled() { |
75 // To prevent infinite recursion, Layout Builder must not be enabled for the | |
76 // '_custom' view mode that is used for on-the-fly rendering of fields in | |
77 // isolation from the entity. | |
78 if ($this->getOriginalMode() === static::CUSTOM_MODE) { | |
79 return FALSE; | |
80 } | |
66 return (bool) $this->getThirdPartySetting('layout_builder', 'enabled'); | 81 return (bool) $this->getThirdPartySetting('layout_builder', 'enabled'); |
67 } | 82 } |
68 | 83 |
69 /** | 84 /** |
70 * {@inheritdoc} | 85 * {@inheritdoc} |
92 | 107 |
93 /** | 108 /** |
94 * {@inheritdoc} | 109 * {@inheritdoc} |
95 */ | 110 */ |
96 protected function setSections(array $sections) { | 111 protected function setSections(array $sections) { |
97 $this->setThirdPartySetting('layout_builder', 'sections', array_values($sections)); | 112 // Third-party settings must be completely unset instead of stored as an |
113 // empty array. | |
114 if (!$sections) { | |
115 $this->unsetThirdPartySetting('layout_builder', 'sections'); | |
116 } | |
117 else { | |
118 $this->setThirdPartySetting('layout_builder', 'sections', array_values($sections)); | |
119 } | |
98 return $this; | 120 return $this; |
99 } | 121 } |
100 | 122 |
101 /** | 123 /** |
102 * {@inheritdoc} | 124 * {@inheritdoc} |
131 $this->setComponent($name, $component); | 153 $this->setComponent($name, $component); |
132 } | 154 } |
133 } | 155 } |
134 else { | 156 else { |
135 // When being disabled, remove all existing section data. | 157 // When being disabled, remove all existing section data. |
136 while (count($this) > 0) { | 158 $this->removeAllSections(); |
137 $this->removeSection(0); | |
138 } | |
139 } | 159 } |
140 } | 160 } |
141 } | 161 } |
142 | 162 |
143 /** | 163 /** |
184 'entity_type' => $entity_type_id, | 204 'entity_type' => $entity_type_id, |
185 'field_name' => $field_name, | 205 'field_name' => $field_name, |
186 'type' => 'layout_section', | 206 'type' => 'layout_section', |
187 'locked' => TRUE, | 207 'locked' => TRUE, |
188 ]); | 208 ]); |
209 $field_storage->setTranslatable(FALSE); | |
189 $field_storage->save(); | 210 $field_storage->save(); |
190 } | 211 } |
191 | 212 |
192 $field = FieldConfig::create([ | 213 $field = FieldConfig::create([ |
193 'field_storage' => $field_storage, | 214 'field_storage' => $field_storage, |
194 'bundle' => $bundle, | 215 'bundle' => $bundle, |
195 'label' => t('Layout'), | 216 'label' => t('Layout'), |
196 ]); | 217 ]); |
218 $field->setTranslatable(FALSE); | |
197 $field->save(); | 219 $field->save(); |
198 } | 220 } |
199 } | 221 } |
200 | 222 |
201 /** | 223 /** |
232 /** | 254 /** |
233 * {@inheritdoc} | 255 * {@inheritdoc} |
234 */ | 256 */ |
235 public function buildMultiple(array $entities) { | 257 public function buildMultiple(array $entities) { |
236 $build_list = parent::buildMultiple($entities); | 258 $build_list = parent::buildMultiple($entities); |
237 if (!$this->isLayoutBuilderEnabled()) { | 259 |
238 return $build_list; | |
239 } | |
240 | |
241 /** @var \Drupal\Core\Entity\EntityInterface $entity */ | |
242 foreach ($entities as $id => $entity) { | 260 foreach ($entities as $id => $entity) { |
243 $sections = $this->getRuntimeSections($entity); | 261 $build_list[$id]['_layout_builder'] = $this->buildSections($entity); |
244 if ($sections) { | 262 |
263 // If there are any sections, remove all fields with configurable display | |
264 // from the existing build. These fields are replicated within sections as | |
265 // field blocks by ::setComponent(). | |
266 if (!Element::isEmpty($build_list[$id]['_layout_builder'])) { | |
245 foreach ($build_list[$id] as $name => $build_part) { | 267 foreach ($build_list[$id] as $name => $build_part) { |
246 $field_definition = $this->getFieldDefinition($name); | 268 $field_definition = $this->getFieldDefinition($name); |
247 if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) { | 269 if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) { |
248 unset($build_list[$id][$name]); | 270 unset($build_list[$id][$name]); |
249 } | 271 } |
250 } | 272 } |
251 | |
252 // Bypass ::getContexts() in order to use the runtime entity, not a | |
253 // sample entity. | |
254 $contexts = $this->contextRepository()->getAvailableContexts(); | |
255 $label = new TranslatableMarkup('@entity being viewed', [ | |
256 '@entity' => $entity->getEntityType()->getSingularLabel(), | |
257 ]); | |
258 $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label); | |
259 foreach ($sections as $delta => $section) { | |
260 $build_list[$id]['_layout_builder'][$delta] = $section->toRenderArray($contexts); | |
261 } | |
262 } | 273 } |
263 } | 274 } |
264 | 275 |
265 return $build_list; | 276 return $build_list; |
266 } | 277 } |
267 | 278 |
268 /** | 279 /** |
269 * Gets the runtime sections for a given entity. | 280 * Builds the render array for the sections of a given entity. |
270 * | 281 * |
271 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity | 282 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity |
272 * The entity. | 283 * The entity. |
273 * | 284 * |
285 * @return array | |
286 * The render array representing the sections of the entity. | |
287 */ | |
288 protected function buildSections(FieldableEntityInterface $entity) { | |
289 $contexts = $this->getContextsForEntity($entity); | |
290 // @todo Remove in https://www.drupal.org/project/drupal/issues/3018782. | |
291 $label = new TranslatableMarkup('@entity being viewed', [ | |
292 '@entity' => $entity->getEntityType()->getSingularLabel(), | |
293 ]); | |
294 $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label); | |
295 | |
296 $cacheability = new CacheableMetadata(); | |
297 $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability); | |
298 | |
299 $build = []; | |
300 if ($storage) { | |
301 foreach ($storage->getSections() as $delta => $section) { | |
302 $build[$delta] = $section->toRenderArray($contexts); | |
303 } | |
304 } | |
305 // The render array is built based on decisions made by @SectionStorage | |
306 // plugins and therefore it needs to depend on the accumulated | |
307 // cacheability of those decisions. | |
308 $cacheability->applyTo($build); | |
309 return $build; | |
310 } | |
311 | |
312 /** | |
313 * Gets the available contexts for a given entity. | |
314 * | |
315 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity | |
316 * The entity. | |
317 * | |
318 * @return \Drupal\Core\Plugin\Context\ContextInterface[] | |
319 * An array of context objects for a given entity. | |
320 */ | |
321 protected function getContextsForEntity(FieldableEntityInterface $entity) { | |
322 return [ | |
323 'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()), | |
324 'entity' => EntityContext::fromEntity($entity), | |
325 'display' => EntityContext::fromEntity($this), | |
326 ] + $this->contextRepository()->getAvailableContexts(); | |
327 } | |
328 | |
329 /** | |
330 * Gets the runtime sections for a given entity. | |
331 * | |
332 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity | |
333 * The entity. | |
334 * | |
274 * @return \Drupal\layout_builder\Section[] | 335 * @return \Drupal\layout_builder\Section[] |
275 * The sections. | 336 * The sections. |
337 * | |
338 * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. | |
339 * \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext() | |
340 * should be used instead. See https://www.drupal.org/node/3022574. | |
276 */ | 341 */ |
277 protected function getRuntimeSections(FieldableEntityInterface $entity) { | 342 protected function getRuntimeSections(FieldableEntityInterface $entity) { |
278 if ($this->isOverridable() && !$entity->get(OverridesSectionStorage::FIELD_NAME)->isEmpty()) { | 343 @trigger_error('\Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::getRuntimeSections() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext() should be used instead. See https://www.drupal.org/node/3022574.', E_USER_DEPRECATED); |
279 return $entity->get(OverridesSectionStorage::FIELD_NAME)->getSections(); | 344 // For backwards compatibility, mimic the functionality of ::buildSections() |
280 } | 345 // by constructing a cacheable metadata object and retrieving the |
281 | 346 // entity-based contexts. |
282 return $this->getSections(); | 347 $cacheability = new CacheableMetadata(); |
348 $contexts = $this->getContextsForEntity($entity); | |
349 $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability); | |
350 return $storage ? $storage->getSections() : []; | |
283 } | 351 } |
284 | 352 |
285 /** | 353 /** |
286 * {@inheritdoc} | 354 * {@inheritdoc} |
287 * | 355 * |
397 | 465 |
398 // Return the first section. | 466 // Return the first section. |
399 return $this->getSection(0); | 467 return $this->getSection(0); |
400 } | 468 } |
401 | 469 |
470 /** | |
471 * Gets the section storage manager. | |
472 * | |
473 * @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface | |
474 * The section storage manager. | |
475 */ | |
476 private function sectionStorageManager() { | |
477 return \Drupal::service('plugin.manager.layout_builder.section_storage'); | |
478 } | |
479 | |
480 /** | |
481 * {@inheritdoc} | |
482 */ | |
483 public function getComponent($name) { | |
484 if ($this->isLayoutBuilderEnabled() && $section_component = $this->getQuickEditSectionComponent() ?: $this->getSectionComponentForFieldName($name)) { | |
485 $plugin = $section_component->getPlugin(); | |
486 if ($plugin instanceof ConfigurableInterface) { | |
487 $configuration = $plugin->getConfiguration(); | |
488 if (isset($configuration['formatter'])) { | |
489 return $configuration['formatter']; | |
490 } | |
491 } | |
492 } | |
493 return parent::getComponent($name); | |
494 } | |
495 | |
496 /** | |
497 * Returns the Quick Edit formatter settings. | |
498 * | |
499 * @return \Drupal\layout_builder\SectionComponent|null | |
500 * The section component if it is available. | |
501 * | |
502 * @see \Drupal\layout_builder\QuickEditIntegration::entityViewAlter() | |
503 * @see \Drupal\quickedit\MetadataGenerator::generateFieldMetadata() | |
504 */ | |
505 private function getQuickEditSectionComponent() { | |
506 // To determine the Quick Edit view_mode ID we need an originalMode set. | |
507 if ($original_mode = $this->getOriginalMode()) { | |
508 $parts = explode('-', $original_mode); | |
509 // The Quick Edit view mode ID is created by | |
510 // \Drupal\layout_builder\QuickEditIntegration::entityViewAlter() | |
511 // concatenating together the information we need to retrieve the Layout | |
512 // Builder component. It follows the structure prescribed by the | |
513 // documentation of hook_quickedit_render_field(). | |
514 if (count($parts) === 6 && $parts[0] === 'layout_builder') { | |
515 list(, $delta, $component_uuid, $entity_id) = QuickEditIntegration::deconstructViewModeId($original_mode); | |
516 $entity = $this->entityTypeManager()->getStorage($this->getTargetEntityTypeId())->load($entity_id); | |
517 $sections = $this->getEntitySections($entity); | |
518 if (isset($sections[$delta])) { | |
519 $component = $sections[$delta]->getComponent($component_uuid); | |
520 $plugin = $component->getPlugin(); | |
521 // We only care about FieldBlock because these are only components | |
522 // that provide Quick Edit integration: Quick Edit enables in-place | |
523 // editing of fields of entities, not of anything else. | |
524 if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') { | |
525 return $component; | |
526 } | |
527 } | |
528 } | |
529 } | |
530 return NULL; | |
531 } | |
532 | |
533 /** | |
534 * Gets the component for a given field name if any. | |
535 * | |
536 * @param string $field_name | |
537 * The field name. | |
538 * | |
539 * @return \Drupal\layout_builder\SectionComponent|null | |
540 * The section component if it is available. | |
541 */ | |
542 private function getSectionComponentForFieldName($field_name) { | |
543 // Loop through every component until the first match is found. | |
544 foreach ($this->getSections() as $section) { | |
545 foreach ($section->getComponents() as $component) { | |
546 $plugin = $component->getPlugin(); | |
547 if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') { | |
548 // FieldBlock derivative IDs are in the format | |
549 // [entity_type]:[bundle]:[field]. | |
550 list(, , $field_block_field_name) = explode(PluginBase::DERIVATIVE_SEPARATOR, $plugin->getDerivativeId()); | |
551 if ($field_block_field_name === $field_name) { | |
552 return $component; | |
553 } | |
554 } | |
555 } | |
556 } | |
557 return NULL; | |
558 } | |
559 | |
402 } | 560 } |