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 }