diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module index 76ec53433d..bda807a1f5 100644 --- a/core/modules/layout_builder/layout_builder.module +++ b/core/modules/layout_builder/layout_builder.module @@ -134,3 +134,12 @@ function layout_builder_module_implements_alter(&$implementations, $hook) { $implementations['layout_builder'] = $group; } } + +/** + * Implements hook_layout_builder_section_storage_alter(). + */ +function layout_builder_layout_builder_section_storage_alter(array &$definitions) { + /** @var \Drupal\layout_builder\SectionStorage\SectionStorageDefinition[] $definitions */ + $definitions['overrides']->getContextDefinition('entity') + ->addConstraint('EntityHasField', 'layout_builder__layout'); +} diff --git a/core/modules/layout_builder/layout_builder.module.rej b/core/modules/layout_builder/layout_builder.module.rej new file mode 100644 index 0000000000..49cd3cb856 --- /dev/null +++ b/core/modules/layout_builder/layout_builder.module.rej @@ -0,0 +1,14 @@ +diff a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module (rejected hunks) +@@ -202,3 +202,12 @@ function layout_builder_block_content_access(EntityInterface $entity, $operation + } + return AccessResult::forbidden(); + } ++ ++/** +++ * Implements hook_layout_builder_section_storage_alter(). +++ */ ++function layout_builder_layout_builder_section_storage_alter(array &$definitions) { ++ /** @var \Drupal\layout_builder\SectionStorage\SectionStorageDefinition[] $definitions */ ++ $definitions['overrides']->getContextDefinition('entity') ++ ->addConstraint('EntityHasField', 'layout_builder__layout'); ++} diff --git a/core/modules/layout_builder/src/Annotation/SectionStorage.php b/core/modules/layout_builder/src/Annotation/SectionStorage.php index 42f4a47fe6..e84fbaad2e 100644 --- a/core/modules/layout_builder/src/Annotation/SectionStorage.php +++ b/core/modules/layout_builder/src/Annotation/SectionStorage.php @@ -22,6 +22,13 @@ class SectionStorage extends Plugin { */ public $id; + /** + * Any required context definitions. + * + * @var array + */ + public $context = []; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index 78ff430b00..305e0a8f53 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -5,6 +5,8 @@ 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\StringTranslation\TranslatableMarkup; use Drupal\field\Entity\FieldConfig; @@ -239,11 +241,27 @@ public function buildMultiple(array $entities) { * The sections. */ protected function getRuntimeSections(FieldableEntityInterface $entity) { - if ($this->isOverridable() && !$entity->get('layout_builder__layout')->isEmpty()) { - return $entity->get('layout_builder__layout')->getSections(); - } + if ($this->isOverridable()) { + $contexts = [ + 'entity' => new Context(ContextDefinition::create('entity'), $entity), + ]; + + /** @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $storage_manager */ + $storage_manager = \Drupal::service('plugin.manager.layout_builder.section_storage'); + + $definitions = $storage_manager->getDefinitionsForContexts($contexts); + if ($definitions) { + $storage_type = key($definitions); - return $this->getSections(); + $storage = $storage_manager->loadEmpty($storage_type); + $storage->setContext('entity', $contexts['entity']); + $section_list = $storage->getSectionListFromContext(); + } + } + if (empty($section_list) || count($section_list) === 0) { + $section_list = $this; + } + return $section_list->getSections(); } /** diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php index 6fab11996b..623bd01252 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php @@ -25,6 +25,9 @@ * * @SectionStorage( * id = "defaults", + * context = { + * "entity_view_display" = @ContextDefinition("entity:entity_view_display"), + * } * ) * * @internal @@ -249,6 +252,13 @@ public function getSectionListFromId($id) { return $display; } + /** + * {@inheritdoc} + */ + public function getSectionListFromContext() { + return $this->getContextValue('entity_view_display'); + } + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index 3a92ee63fa..b13deb0cfe 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -23,6 +23,9 @@ * * @SectionStorage( * id = "overrides", + * context = { + * "entity" = @ContextDefinition("entity"), + * } * ) * * @internal @@ -134,6 +137,13 @@ public function getSectionListFromId($id) { throw new \InvalidArgumentException(sprintf('The "%s" ID for the "%s" section storage type is invalid', $id, $this->getStorageType())); } + /** + * {@inheritdoc} + */ + public function getSectionListFromContext() { + return $this->getContextValue('entity')->get('layout_builder__layout'); + } + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php index c419060c45..ae6569cb33 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php @@ -2,7 +2,7 @@ namespace Drupal\layout_builder\Plugin\SectionStorage; -use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait; use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionListInterface; @@ -16,7 +16,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -abstract class SectionStorageBase extends PluginBase implements SectionStorageInterface { +abstract class SectionStorageBase extends ContextAwarePluginBase implements SectionStorageInterface { use LayoutBuilderRoutesTrait; @@ -103,4 +103,22 @@ public function removeSection($delta) { return $this; } + /** + * {@inheritdoc} + * + * @todo Remove after https://www.drupal.org/project/drupal/issues/2982626. + */ + public function getContextDefinition($name) { + return $this->getPluginDefinition()->getContextDefinition($name); + } + + /** + * {@inheritdoc} + * + * @todo Remove after https://www.drupal.org/project/drupal/issues/2982626. + */ + public function getContextDefinitions() { + return $this->getPluginDefinition()->getContextDefinitions(); + } + } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php index 61b975a471..673cfb31fc 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php @@ -2,6 +2,8 @@ namespace Drupal\layout_builder\SectionStorage; +use Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface; +use Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionTrait; use Drupal\Component\Plugin\Definition\PluginDefinition; /** @@ -12,7 +14,9 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -class SectionStorageDefinition extends PluginDefinition { +class SectionStorageDefinition extends PluginDefinition implements ContextAwarePluginDefinitionInterface { + + use ContextAwarePluginDefinitionTrait; /** * Any additional properties and values. @@ -28,6 +32,13 @@ class SectionStorageDefinition extends PluginDefinition { * An array of values from the annotation. */ public function __construct(array $definition = []) { + if (isset($definition['context'])) { + foreach ($definition['context'] as $name => $context_definition) { + $this->addContextDefinition($name, $context_definition); + } + unset($definition['context']); + } + foreach ($definition as $property => $value) { $this->set($property, $value); } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php index 18147cd1d8..8f24b06cb0 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\layout_builder\Annotation\SectionStorage; use Drupal\layout_builder\SectionStorageInterface; @@ -18,6 +19,8 @@ */ class SectionStorageManager extends DefaultPluginManager implements SectionStorageManagerInterface { + use ContextAwarePluginManagerTrait; + /** * Constructs a new SectionStorageManager object. * diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php index 3b269fcbba..8cf53b620a 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php @@ -2,7 +2,7 @@ namespace Drupal\layout_builder\SectionStorage; -use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface; /** * Provides the interface for a plugin manager of section storage types. @@ -12,7 +12,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface SectionStorageManagerInterface extends DiscoveryInterface { +interface SectionStorageManagerInterface extends ContextAwarePluginManagerInterface { /** * Loads a section storage with no associated section list. diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php index 90ce9072fd..9a1aa61934 100644 --- a/core/modules/layout_builder/src/SectionStorageInterface.php +++ b/core/modules/layout_builder/src/SectionStorageInterface.php @@ -2,8 +2,8 @@ namespace Drupal\layout_builder; -use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Access\AccessibleInterface; +use Drupal\Core\Plugin\ContextAwarePluginInterface; use Symfony\Component\Routing\RouteCollection; /** @@ -14,7 +14,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface, AccessibleInterface { +interface SectionStorageInterface extends SectionListInterface, ContextAwarePluginInterface, AccessibleInterface { /** * Returns an identifier for this storage. @@ -64,6 +64,17 @@ public function setSectionList(SectionListInterface $section_list); */ public function getSectionListFromId($id); + /** + * Derives the section list from the available context. + * + * @return \Drupal\layout_builder\SectionListInterface + * The section list. + * + * @internal + * This should only be called during section storage instantiation. + */ + public function getSectionListFromContext(); + /** * Provides the routes needed for Layout Builder UI. * diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module index e7d310a6cb..38b66f557d 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module @@ -7,6 +7,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\layout_builder_test\Plugin\SectionStorage\OverridesSectionStorage; /** * Implements hook_plugin_filter_TYPE__CONSUMER_alter(). @@ -58,3 +59,11 @@ function layout_builder_test_node_view(array &$build, EntityInterface $entity, E ]; } } + +/** + * Implements hook_layout_builder_section_storage_alter(). + */ +function layout_builder_test_layout_builder_section_storage_alter(array &$storages) { + /** @var \Drupal\layout_builder\SectionStorage\SectionStorageDefinition[] $storages */ + $storages['overrides']->setClass(OverridesSectionStorage::class); +} diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/OverridesSectionStorage.php new file mode 100644 index 0000000000..f6d492d5b8 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -0,0 +1,17 @@ +set('layout_builder_test_context_applied', TRUE); + return parent::getSectionListFromContext(); + } + +} diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php index d39c7e673f..c7a3c5cdc9 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\layout_builder\Functional; +use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\node\Entity\Node; use Drupal\Tests\BrowserTestBase; use Drupal\views\Entity\View; @@ -444,6 +445,27 @@ public function testDeletedView() { $assert_session->pageTextNotContains('Test Block View'); } + /** + * Tests that section loading is delegated to plugins during rendering. + */ + public function testRenderByContextualPluginDelegate() { + $state_key = 'layout_builder_test_context_applied'; + /** @var \Drupal\Core\State\StateInterface $state */ + $state = $this->container->get('state'); + + $this->drupalGet('node/1'); + $this->assertEmpty($state->get($state_key)); + + entity_get_display('node', 'bundle_with_section_field', 'full') + ->enableLayoutBuilder() + ->setOverridable() + ->save(); + + $this->getSession()->reload(); + $state->resetCache(); + $this->assertTrue($state->get($state_key)); + } + /** * Asserts that a text string only appears once on the page. * @@ -451,7 +473,9 @@ public function testDeletedView() { * The string to look for. */ protected function assertTextAppearsOnce($needle) { - $this->assertEquals(1, substr_count($this->getSession()->getPage()->getContent(), $needle), "'$needle' only appears once on the page."); + $this->assertEquals(1, substr_count($this->getSession() + ->getPage() + ->getContent(), $needle), "'$needle' only appears once on the page."); } }