diff --git a/core/modules/layout_builder/layout_builder.post_update.php b/core/modules/layout_builder/layout_builder.post_update.php index d93113b7ab..0ee7390dd5 100644 --- a/core/modules/layout_builder/layout_builder.post_update.php +++ b/core/modules/layout_builder/layout_builder.post_update.php @@ -65,3 +65,10 @@ function layout_builder_post_update_add_extra_fields(&$sandbox = NULL) { function layout_builder_post_update_section_storage_context_definitions() { // Empty post-update hook. } + +/** + * Clear caches due to changes to annotation changes to the Overrides plugin. + */ +function layout_builder_post_update_overrides_view_mode_annotation() { + // Empty post-update hook. +} diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index bae8174211..bb75e1a3cd 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -3,13 +3,13 @@ namespace Drupal\layout_builder\Entity; use Drupal\Core\Cache\CacheableMetadata; -use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\Core\Render\Element; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -52,7 +52,7 @@ public function __construct(array $values, $entity_type) { * {@inheritdoc} */ public function isOverridable() { - return $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE); + return $this->isLayoutBuilderEnabled() && $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE); } /** @@ -67,6 +67,10 @@ public function setOverridable($overridable = TRUE) { * {@inheritdoc} */ public function isLayoutBuilderEnabled() { + // Layout Builder cannot be enabled for the custom view mode. + if ($this->getOriginalMode() === static::CUSTOM_MODE) { + return FALSE; + } return (bool) $this->getThirdPartySetting('layout_builder', 'enabled'); } @@ -238,46 +242,59 @@ protected function contextRepository() { */ public function buildMultiple(array $entities) { $build_list = parent::buildMultiple($entities); - if (!$this->isLayoutBuilderEnabled()) { - return $build_list; - } - /** @var \Drupal\Core\Entity\EntityInterface $entity */ foreach ($entities as $id => $entity) { - $build_list[$id]['_layout_builder'] = []; + $build_list[$id]['_layout_builder'] = $this->buildSections($entity); - $cacheability = new CacheableMetadata(); - $contexts = $this->getContextsForEntity($entity); - $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability); - // The render array is built based on decisions made by @SectionStorage - // plugins and therefore it needs to depend on the accumulated - // cacheability of those decisions. - $cacheability->applyTo($build_list[$id]['_layout_builder']); - - $sections = $storage ? $storage->getSections() : []; - if ($sections) { + // If there are any sections, remove all fields with configurable display + // from the existing build. These fields are replicated within sections as + // field blocks by ::setComponent(). + if (!Element::isEmpty($build_list[$id]['_layout_builder'])) { foreach ($build_list[$id] as $name => $build_part) { $field_definition = $this->getFieldDefinition($name); if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) { unset($build_list[$id][$name]); } } - - // @todo Remove in https://www.drupal.org/project/drupal/issues/3018782. - $label = new TranslatableMarkup('@entity being viewed', [ - '@entity' => $entity->getEntityType()->getSingularLabel(), - ]); - $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label); - - foreach ($sections as $delta => $section) { - $build_list[$id]['_layout_builder'][$delta] = $section->toRenderArray($contexts); - } } } return $build_list; } + /** + * Builds the render array for the sections of a given entity. + * + * @param \Drupal\Core\Entity\FieldableEntityInterface $entity + * The entity. + * + * @return array + * The render array representing the sections of the entity. + */ + protected function buildSections(FieldableEntityInterface $entity) { + $contexts = $this->getContextsForEntity($entity); + // @todo Remove in https://www.drupal.org/project/drupal/issues/3018782. + $label = new TranslatableMarkup('@entity being viewed', [ + '@entity' => $entity->getEntityType()->getSingularLabel(), + ]); + $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label); + + $cacheability = new CacheableMetadata(); + $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability); + + $build = []; + if ($storage) { + foreach ($storage->getSections() as $delta => $section) { + $build[$delta] = $section->toRenderArray($contexts); + } + } + // The render array is built based on decisions made by @SectionStorage + // plugins and therefore it needs to depend on the accumulated + // cacheability of those decisions. + $cacheability->applyTo($build); + return $build; + } + /** * Gets the available contexts for a given entity. * @@ -300,20 +317,22 @@ protected function getContextsForEntity(FieldableEntityInterface $entity) { * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity. - * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface|null $cacheability - * (optional) Refinable cacheability object, which will be populated based - * on the cacheability of each section storage candidate. * * @return \Drupal\layout_builder\Section[] * The sections. * - * @todo Deprecate this method in https://www.drupal.org/node/2986403. + * @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. */ - protected function getRuntimeSections(FieldableEntityInterface $entity, RefinableCacheableDependencyInterface &$cacheability = NULL) { - if (!$cacheability) { - $cacheability = new CacheableMetadata(); - } - $storage = $this->sectionStorageManager()->findByContext($this->getContextsForEntity($entity), $cacheability); + protected function getRuntimeSections(FieldableEntityInterface $entity) { + @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); + // For backwards compatibility, mimic the functionality of ::buildSections() + // by constructing a cacheable metadata object and retrieving the + // entity-based contexts. + $cacheability = new CacheableMetadata(); + $contexts = $this->getContextsForEntity($entity); + $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability); return $storage ? $storage->getSections() : []; } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php index b18b17767d..928c456abf 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php @@ -456,8 +456,8 @@ public function access($operation, AccountInterface $account = NULL, $return_as_ * {@inheritdoc} */ public function isApplicable(RefinableCacheableDependencyInterface $cacheability) { - // Defaults are always applicable. - return TRUE; + $cacheability->addCacheableDependency($this); + return $this->isLayoutBuilderEnabled(); } } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index 138eb69f83..fa0c6c2f6d 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -9,6 +9,8 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\EntityContext; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; @@ -31,7 +33,7 @@ * weight = -20, * context_definitions = { * "entity" = @ContextDefinition("entity"), - * "view_mode" = @ContextDefinition("string", required = FALSE), + * "view_mode" = @ContextDefinition("string"), * } * ) * @@ -157,6 +159,9 @@ public function deriveContextsFromRoute($value, $definition, $name, array $defau if ($entity = $this->extractEntityFromRoute($value, $defaults)) { $contexts['entity'] = EntityContext::fromEntity($entity); + // @todo Expand to work for all view modes in + // https://www.drupal.org/node/2907413. + $contexts['view_mode'] = new Context(new ContextDefinition('string'), 'full'); } return $contexts; } @@ -285,9 +290,7 @@ protected function getEntityTypes() { * {@inheritdoc} */ public function getDefaultSectionStorage() { - // @todo Expand to work for all view modes in - // https://www.drupal.org/node/2907413. - return LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), 'full'); + return LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), $this->getContextValue('view_mode')); } /** diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php index 240ddd951c..82e7dafbe8 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php @@ -31,17 +31,14 @@ public function load($type, array $contexts = []); /** * Finds the section storage to load based on available contexts. * - * After calling this method the $cacheability parameter will reflect the - * cacheability information used to determine the correct section storage. - * This must be applied to any output that uses the result of this method. - * * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts * The contexts which should be used to determine which storage to return. * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability * Refinable cacheability object, which will be populated based on the - * cacheability of each section storage candidate. This is typically created - * directly before this method call and must be applied to a render array - * after this method call. + * cacheability of each section storage candidate. After calling this method + * this parameter will reflect the cacheability information used to + * determine the correct section storage. This must be associated with any + * output that uses the result of this method. * * @return \Drupal\layout_builder\SectionStorageInterface|null * The section storage if one matched all contexts, or NULL otherwise. diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php index 3d0d00d131..5817ae5dc0 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php @@ -30,7 +30,6 @@ protected function setUp() { // https://www.drupal.org/project/drupal/issues/2917777. $this->drupalPlaceBlock('local_tasks_block'); - // Create two nodes. $this->createContentType(['type' => 'bundle_with_section_field']); $this->createNode([ 'type' => 'bundle_with_section_field', @@ -83,6 +82,20 @@ public function testRenderByContextAwarePluginDelegate() { $this->drupalGet('node/1'); $assert_session->pageTextNotContains('Defaults block title'); $assert_session->pageTextContains('Test block title'); + + // Disabling defaults does not prevent the section storage from running. + $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => FALSE], 'Save'); + $page->pressButton('Confirm'); + $assert_session->pageTextContains('Layout Builder has been disabled'); + $this->drupalGet('node/1'); + $assert_session->pageTextNotContains('Defaults block title'); + $assert_session->pageTextContains('Test block title'); + + // Disabling the test storage restores the original output. + $this->container->get('state')->set('layout_builder_test_state', FALSE); + $this->drupalGet('node/1'); + $assert_session->pageTextNotContains('Defaults block title'); + $assert_session->pageTextNotContains('Test block title'); } } diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php index a554fb526f..9c0276ced7 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\layout_builder\Kernel; use Drupal\Core\Config\Schema\SchemaIncompleteException; +use Drupal\entity_test\Entity\EntityTest; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; /** @@ -23,6 +24,7 @@ protected function getSectionStorage(array $section_data) { 'status' => TRUE, 'third_party_settings' => [ 'layout_builder' => [ + 'enabled' => TRUE, 'sections' => $section_data, ], ], @@ -40,4 +42,23 @@ public function testInvalidConfiguration() { $this->sectionStorage->save(); } + /** + * @covers ::getRuntimeSections + * @group legacy + * @expectedDeprecation \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. + */ + public function testGetRuntimeSections() { + $this->container->get('current_user')->setAccount($this->createUser()); + + $entity = EntityTest::create(); + $entity->save(); + + $reflection = new \ReflectionMethod($this->sectionStorage, 'getRuntimeSections'); + $reflection->setAccessible(TRUE); + + $result = $reflection->invoke($this->sectionStorage, $entity); + + $this->assertEquals($this->sectionStorage->getSections(), $result); + } + } diff --git a/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php index cb8a0d35d7..bea812a87c 100644 --- a/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\layout_builder\Kernel; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\EntityContext; use Drupal\entity_test\Entity\EntityTest; use Drupal\KernelTests\KernelTestBase; @@ -10,7 +12,6 @@ use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionComponent; use Drupal\layout_builder\SectionListInterface; -use Drupal\layout_builder\SectionStorage\SectionStorageDefinition; /** * @coversDefaultClass \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage @@ -47,7 +48,8 @@ protected function setUp() { $this->installSchema('system', ['key_value_expire']); $this->installEntitySchema('entity_test'); - $this->plugin = OverridesSectionStorage::create($this->container, [], 'overrides', new SectionStorageDefinition()); + $definition = $this->container->get('plugin.manager.layout_builder.section_storage')->getDefinition('overrides'); + $this->plugin = OverridesSectionStorage::create($this->container, [], 'overrides', $definition); } /** @@ -81,6 +83,7 @@ public function testAccess($expected, $operation, $is_enabled, array $section_da $entity->save(); $this->plugin->setContext('entity', EntityContext::fromEntity($entity)); + $this->plugin->setContext('view_mode', new Context(new ContextDefinition('string'), 'default')); $result = $this->plugin->access($operation); $this->assertSame($expected, $result); } @@ -118,8 +121,13 @@ public function testGetContexts() { $context = EntityContext::fromEntity($entity); $this->plugin->setContext('entity', $context); - $expected = ['entity' => $context]; - $this->assertSame($expected, $this->plugin->getContexts()); + $expected = [ + 'entity', + 'view_mode', + ]; + $result = $this->plugin->getContexts(); + $this->assertEquals($expected, array_keys($result)); + $this->assertSame($context, $result['entity']); } /** @@ -132,8 +140,13 @@ public function testGetContextsDuringPreview() { $context = EntityContext::fromEntity($entity); $this->plugin->setContext('entity', $context); - $expected = ['layout_builder.entity' => $context]; - $this->assertSame($expected, $this->plugin->getContextsDuringPreview()); + $expected = [ + 'view_mode', + 'layout_builder.entity', + ]; + $result = $this->plugin->getContextsDuringPreview(); + $this->assertEquals($expected, array_keys($result)); + $this->assertSame($context, $result['layout_builder.entity']); } /**