diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml index 56b4bf88bd..f0c517d995 100644 --- a/core/modules/layout_builder/layout_builder.services.yml +++ b/core/modules/layout_builder/layout_builder.services.yml @@ -13,6 +13,7 @@ services: plugin.manager.layout_builder.section_storage: class: Drupal\layout_builder\SectionStorage\SectionStorageManager parent: default_plugin_manager + arguments: ['@context.handler'] layout_builder.routes: class: Drupal\layout_builder\Routing\LayoutBuilderRoutes arguments: ['@plugin.manager.layout_builder.section_storage'] diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index 5558f689be..dd52fdda3e 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -281,7 +281,7 @@ protected function getRuntimeSections(FieldableEntityInterface $entity) { if ($this->isOverridable()) { /** @var \Drupal\layout_builder\SectionStorageInterface $storage */ $storage = \Drupal::service('plugin.manager.layout_builder.section_storage') - ->findByContext([ + ->findByContext('view', [ 'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()), 'entity' => EntityContext::fromEntity($entity), ]); diff --git a/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php b/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php index 263b767f72..946b5f35cf 100644 --- a/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php +++ b/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php @@ -46,7 +46,8 @@ public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore */ public function convert($value, $definition, $name, array $defaults) { if (isset($defaults['section_storage_type']) && $this->sectionStorageManager->hasDefinition($defaults['section_storage_type'])) { - if ($section_storage = $this->sectionStorageManager->loadFromRoute($defaults['section_storage_type'], $value, $definition, $name, $defaults)) { + $contexts = $this->sectionStorageManager->loadEmpty($defaults['section_storage_type'])->getContextsFromRoute($value, $definition, $name, $defaults); + if ($section_storage = $this->sectionStorageManager->load($defaults['section_storage_type'], $contexts)) { // Pass the plugin through the tempstore repository. return $this->layoutTempstoreRepository->get($section_storage); } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php index 819f1c4781..5d4e8fb6f1 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php @@ -5,7 +5,7 @@ use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait; +use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\layout_builder\Annotation\SectionStorage; use Drupal\layout_builder\SectionStorageInterface; @@ -20,7 +20,12 @@ */ class SectionStorageManager extends DefaultPluginManager implements SectionStorageManagerInterface { - use ContextAwarePluginManagerTrait; + /** + * The context handler. + * + * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface + */ + protected $contextHandler; /** * Constructs a new SectionStorageManager object. @@ -32,10 +37,14 @@ class SectionStorageManager extends DefaultPluginManager implements SectionStora * Cache backend instance to use. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler to invoke the alter hook with. + * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler + * The context handler. */ - public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ContextHandlerInterface $context_handler) { parent::__construct('Plugin/SectionStorage', $namespaces, $module_handler, SectionStorageInterface::class, SectionStorage::class); + $this->contextHandler = $context_handler; + $this->alterInfo('layout_builder_section_storage'); $this->setCacheBackend($cache_backend, 'layout_builder_section_storage_plugins'); } @@ -59,25 +68,10 @@ protected function findDefinitions() { /** * {@inheritdoc} */ - public function loadEmpty($type) { - return $this->createInstance($type); - } - - /** - * {@inheritdoc} - */ - public function loadFromRoute($type, $value, $definition, $name, array $defaults) { - $contexts = $this->loadEmpty($type)->getContextsFromRoute($value, $definition, $name, $defaults); - return $this->loadFromContext($type, $contexts); - } - - /** - * {@inheritdoc} - */ - public function loadFromContext($type, array $contexts) { + public function load($type, array $contexts) { $plugin = $this->loadEmpty($type); try { - $this->contextHandler()->applyContextMapping($plugin, $contexts); + $this->contextHandler->applyContextMapping($plugin, $contexts); } catch (ContextException $e) { return NULL; @@ -88,16 +82,23 @@ public function loadFromContext($type, array $contexts) { /** * {@inheritdoc} */ - public function findByContext(array $contexts) { - $storage_types = array_keys($this->getDefinitionsForContexts($contexts)); + public function findByContext($operation, array $contexts) { + $storage_types = array_keys($this->contextHandler->filterPluginDefinitionsByContexts($contexts, $this->getDefinitions())); foreach ($storage_types as $type) { - $plugin = $this->loadFromContext($type, $contexts); - if ($plugin && $plugin->access('load')) { + $plugin = $this->load($type, $contexts); + if ($plugin && $plugin->access($operation)) { return $plugin; } } return NULL; } + /** + * {@inheritdoc} + */ + public function loadEmpty($type) { + return $this->createInstance($type); + } + } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php index 4e62e18fb0..26d0f9b42b 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\Core\Plugin\Context\ContextAwarePluginManagerInterface; +use Drupal\Component\Plugin\Discovery\DiscoveryInterface; /** * Provides the interface for a plugin manager of section storage types. @@ -12,39 +12,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface SectionStorageManagerInterface extends ContextAwarePluginManagerInterface { - - /** - * Loads a section storage with no associated section list. - * - * @param string $type - * The type of section storage being instantiated. - * - * @return \Drupal\layout_builder\SectionStorageInterface - * The section storage. - */ - public function loadEmpty($type); - - /** - * Loads a section storage populated with a section list derived from a route. - * - * @param string $type - * The section storage type. - * @param string $value - * The raw value. - * @param mixed $definition - * The parameter definition provided in the route options. - * @param string $name - * The name of the parameter. - * @param array $defaults - * The route defaults array. - * - * @return \Drupal\layout_builder\SectionStorageInterface|null - * The section storage if it could be loaded, or NULL otherwise. - * - * @see \Drupal\Core\ParamConverter\ParamConverterInterface::convert() - */ - public function loadFromRoute($type, $value, $definition, $name, array $defaults); +interface SectionStorageManagerInterface extends DiscoveryInterface { /** * Loads a section storage with the provided contexts applied. @@ -57,17 +25,36 @@ public function loadFromRoute($type, $value, $definition, $name, array $defaults * @return \Drupal\layout_builder\SectionStorageInterface|null * The section storage. */ - public function loadFromContext($type, array $contexts); + public function load($type, array $contexts); /** * Finds the section storage to load based on available contexts. * + * @param string $operation + * The access operation. See \Drupal\Core\Access\AccessibleInterface. * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts - * The contexts which should be used to determine which storage to load. + * The contexts which should be used to determine which storage to return. * * @return \Drupal\layout_builder\SectionStorageInterface|null * The section storage if one matched all contexts, or NULL otherwise. */ - public function findByContext(array $contexts); + public function findByContext($operation, array $contexts); + + /** + * Loads an empty section storage with no associated section list. + * + * @param string $type + * The type of section storage being instantiated. + * + * @return \Drupal\layout_builder\SectionStorageInterface + * The section storage. + * + * @internal + * Section storage relies on context to load section lists. Use ::load() + * when that context is available. This method is intended for use by + * collaborators of the plugins in build-time situations when section + * storage type must be consulted. + */ + public function loadEmpty($type); } diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php index bf078cac98..28cc2f915c 100644 --- a/core/modules/layout_builder/src/SectionStorageInterface.php +++ b/core/modules/layout_builder/src/SectionStorageInterface.php @@ -2,6 +2,7 @@ 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 +15,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface SectionStorageInterface extends SectionListInterface, ContextAwarePluginInterface, AccessibleInterface { +interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface, ContextAwarePluginInterface, AccessibleInterface { /** * Returns an identifier for this storage. @@ -48,26 +49,6 @@ public function getStorageType(); */ public function buildRoutes(RouteCollection $collection); - /** - * Gets the URL used when redirecting away from the Layout Builder UI. - * - * @return \Drupal\Core\Url - * The URL object. - */ - public function getRedirectUrl(); - - /** - * Gets the URL used to display the Layout Builder UI. - * - * @param string $rel - * (optional) The link relationship type, for example: 'view' or 'disable'. - * Defaults to 'view'. - * - * @return \Drupal\Core\Url - * The URL object. - */ - public function getLayoutBuilderUrl($rel = 'view'); - /** * Derives the required plugin contexts from route values. * @@ -85,6 +66,26 @@ public function getLayoutBuilderUrl($rel = 'view'); */ public function getContextsFromRoute($value, $definition, $name, array $defaults); + /** + * Gets the URL used when redirecting away from the Layout Builder UI. + * + * @return \Drupal\Core\Url + * The URL object. + */ + public function getRedirectUrl(); + + /** + * Gets the URL used to display the Layout Builder UI. + * + * @param string $rel + * (optional) The link relationship type, for example: 'view' or 'disable'. + * Defaults to 'view'. + * + * @return \Drupal\Core\Url + * The URL object. + */ + public function getLayoutBuilderUrl($rel = 'view'); + /** * Gets the label for the object using the sections. * diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreParamConverterTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreParamConverterTest.php index 5f8dc051e3..1091cd586f 100644 --- a/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreParamConverterTest.php +++ b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreParamConverterTest.php @@ -32,9 +32,11 @@ public function testConvert() { $expected = 'the_return_value'; $section_storage_manager->hasDefinition('my_type')->willReturn(TRUE); - $section_storage_manager->loadFromRoute('my_type', $value, $definition, $name, $defaults)->willReturn($section_storage); + $section_storage_manager->loadEmpty('my_type')->willReturn($section_storage->reveal()); + $section_storage->getContextsFromRoute($value, $definition, $name, $defaults)->willReturn([]); + $section_storage_manager->load('my_type', [])->willReturn($section_storage->reveal()); - $layout_tempstore_repository->get($section_storage->reveal())->willReturn($expected); + $layout_tempstore_repository->get($section_storage)->willReturn($expected); $result = $converter->convert($value, $definition, $name, $defaults); $this->assertEquals($expected, $result); @@ -54,7 +56,7 @@ public function testConvertNoType() { $defaults = ['section_storage_type' => NULL]; $section_storage_manager->hasDefinition()->shouldNotBeCalled(); - $section_storage_manager->loadFromRoute()->shouldNotBeCalled(); + $section_storage_manager->load()->shouldNotBeCalled(); $layout_tempstore_repository->get()->shouldNotBeCalled(); $result = $converter->convert($value, $definition, $name, $defaults); @@ -75,7 +77,7 @@ public function testConvertInvalidConverter() { $defaults = ['section_storage_type' => 'invalid']; $section_storage_manager->hasDefinition('invalid')->willReturn(FALSE); - $section_storage_manager->loadFromRoute()->shouldNotBeCalled(); + $section_storage_manager->load()->shouldNotBeCalled(); $layout_tempstore_repository->get()->shouldNotBeCalled(); $result = $converter->convert($value, $definition, $name, $defaults); diff --git a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php index 239cea7288..3a33ede44f 100644 --- a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php +++ b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php @@ -7,7 +7,6 @@ use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Component\Plugin\Factory\FactoryInterface; use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; @@ -62,13 +61,8 @@ protected function setUp() { $this->factory = $this->prophesize(FactoryInterface::class); $cache = $this->prophesize(CacheBackendInterface::class); $module_handler = $this->prophesize(ModuleHandlerInterface::class); - $this->manager = new TestSectionStorageManager($this->discovery->reveal(), $this->factory->reveal(), $cache->reveal(), $module_handler->reveal()); - $this->contextHandler = $this->prophesize(ContextHandlerInterface::class); - $container = new ContainerBuilder(); - $container->set('context.handler', $this->contextHandler->reveal()); - \Drupal::setContainer($container); - + $this->manager = new TestSectionStorageManager($this->discovery->reveal(), $this->factory->reveal(), $cache->reveal(), $module_handler->reveal(), $this->contextHandler->reveal()); } /** @@ -83,44 +77,26 @@ public function testLoadEmpty() { } /** - * @covers ::loadFromRoute + * @covers ::load */ - public function testLoadFromRoute() { + public function testLoad() { $plugin = $this->prophesize(SectionStorageInterface::class); $this->factory->createInstance('the_plugin_id', [])->willReturn($plugin->reveal()); $contexts = [ 'the_context' => $this->prophesize(ContextInterface::class)->reveal(), ]; - $plugin->getContextsFromRoute('the_value', [], 'the_parameter_name', [])->willReturn($contexts); $this->contextHandler->applyContextMapping($plugin, $contexts)->shouldBeCalled(); - $result = $this->manager->loadFromRoute('the_plugin_id', 'the_value', [], 'the_parameter_name', []); + $result = $this->manager->load('the_plugin_id', $contexts); $this->assertSame($plugin->reveal(), $result); } /** - * @covers ::loadFromContext + * @covers ::load */ - public function testLoadFromContext() { - $plugin = $this->prophesize(SectionStorageInterface::class); - $this->factory->createInstance('the_plugin_id', [])->willReturn($plugin->reveal()); - - $contexts = [ - 'the_context' => $this->prophesize(ContextInterface::class)->reveal(), - ]; - - $this->contextHandler->applyContextMapping($plugin, $contexts)->shouldBeCalled(); - - $result = $this->manager->loadFromContext('the_plugin_id', $contexts); - $this->assertSame($plugin->reveal(), $result); - } - - /** - * @covers ::loadFromContext - */ - public function testLoadFromContextNull() { + public function testLoadNull() { $plugin = $this->prophesize(SectionStorageInterface::class); $this->factory->createInstance('the_plugin_id', [])->willReturn($plugin->reveal()); @@ -130,7 +106,7 @@ public function testLoadFromContextNull() { $this->contextHandler->applyContextMapping($plugin, $contexts)->willThrow(new ContextException()); - $result = $this->manager->loadFromContext('the_plugin_id', $contexts); + $result = $this->manager->load('the_plugin_id', $contexts); $this->assertNull($result); } @@ -172,10 +148,10 @@ public function testFindByContext($access) { $this->discovery->getDefinitions()->willReturn($definitions); $provider_access = $this->prophesize(SectionStorageInterface::class); - $provider_access->access('load')->willReturn($access); + $provider_access->access('test_operation')->willReturn($access); $no_access = $this->prophesize(SectionStorageInterface::class); - $no_access->access('load')->willReturn(FALSE); + $no_access->access('test_operation')->willReturn(FALSE); $missing_contexts = $this->prophesize(SectionStorageInterface::class); @@ -189,7 +165,7 @@ public function testFindByContext($access) { $this->factory->createInstance('missing_contexts', [])->willReturn($missing_contexts->reveal()); $this->factory->createInstance('provider_access', [])->willReturn($provider_access->reveal()); - $result = $this->manager->findByContext($contexts); + $result = $this->manager->findByContext('test_operation', $contexts); if ($access) { $this->assertSame($provider_access->reveal(), $result); } @@ -218,8 +194,8 @@ class TestSectionStorageManager extends SectionStorageManager { /** * {@inheritdoc} */ - public function __construct(DiscoveryInterface $discovery, FactoryInterface $factory, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { - parent::__construct(new \ArrayObject(), $cache_backend, $module_handler); + public function __construct(DiscoveryInterface $discovery, FactoryInterface $factory, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ContextHandlerInterface $context_handler) { + parent::__construct(new \ArrayObject(), $cache_backend, $module_handler, $context_handler); $this->discovery = $discovery; $this->factory = $factory; }