diff --git a/core/core.services.yml b/core/core.services.yml index a194ead..5cd3422 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -266,6 +266,9 @@ services: context.handler: class: Drupal\Core\Plugin\Context\ContextHandler arguments: ['@typed_data_manager'] + context.repository: + class: Drupal\Core\Plugin\Context\LazyContextRepository + arguments: ['@service_container'] cron: class: Drupal\Core\Cron arguments: ['@module_handler', '@lock', '@queue', '@state', '@account_switcher', '@logger.channel.cron', '@plugin.manager.queue_worker'] diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index ae0d031..8244320 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -10,6 +10,7 @@ use Drupal\Core\Cache\Context\CacheContextsPass; use Drupal\Core\Cache\ListCacheBinsPass; use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass; +use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass; use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass; use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteEnhancers; use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteFilters; @@ -88,6 +89,7 @@ public function register(ContainerBuilder $container) { // Add the compiler pass that will process the tagged services. $container->addCompilerPass(new ListCacheBinsPass()); $container->addCompilerPass(new CacheContextsPass()); + $container->addCompilerPass(new ContextProvidersPass()); // Register plugin managers. $container->addCompilerPass(new PluginManagerPass()); diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/ContextProvidersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/ContextProvidersPass.php new file mode 100644 index 0000000..b724be8 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/ContextProvidersPass.php @@ -0,0 +1,33 @@ +findTaggedServiceIds('context_provider')) as $id) { + $context_providers[] = $id; + } + + $definition = $container->getDefinition('context.repository'); + $definition->addArgument($context_providers); + } + +} diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php new file mode 100644 index 0000000..e956946 --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php @@ -0,0 +1,76 @@ +setContextValue($node); + * return ['node' => $context]; + * @endcode + * + * @param string[] $unqualified_context_ids + * The requested context IDs. The context provider must only return contexts + * for those IDs. + * + * @return \Drupal\Core\Plugin\Context\ContextInterface[] + * The determined contexts, keyed by the unqualified context_id. + * + * @see static::getAvailableContexts() + */ + public function getRuntimeContexts(array $unqualified_context_ids); + + /** + * Provides the available contexts. + * + * When a context aware plugin is being configured, the configuration UI must + * know which named contexts are potentially available, but does not care + * about the value, since the value can be different for each request, and + * might not be available at all during the configuration UI's request. + * + * For example: + * @code + * // During configuration, there is no specific node to pass as context. + * // However, inform the system that a context named 'node' is + * // available, and provide its definition, so that context aware plugins + * // can be configured to use it. When the plugin, for example a block, + * // needs to evaluate the context, the value of this context will be + * // supplied by getRuntimeContexts(). + * $context = new Context(new ContextDefinition('entity:node')); + * return ['node' => $context]; + * @endcode + * + * @return \Drupal\Core\Plugin\Context\ContextInterface[] + * All available contexts keyed by the unqualified context ID. + * + * @see static::getRuntimeContext() + */ + public function getAvailableContexts(); + +} diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextRepositoryInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextRepositoryInterface.php new file mode 100644 index 0000000..d769aca --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/Context/ContextRepositoryInterface.php @@ -0,0 +1,40 @@ +container = $container; + $this->contextProviderServiceIDs = $context_provider_service_ids; + } + + /** + * {@inheritdoc} + */ + public function getRuntimeContexts(array $context_ids) { + $contexts = []; + + // Create a map of context providers (service IDs) to unqualified context + // IDs. + $context_ids_by_service = []; + foreach ($context_ids as $id) { + if (isset($this->contexts[$id])) { + $contexts[$id] = $this->contexts[$id]; + continue; + } + // The IDs have been passed in @{service_id}:{unqualified_context_id} format. + if ($id[0] === '@' && strpos($id, ':') !== FALSE) { + list($service_id, $unqualified_context_id) = explode(':', $id, 2); + // Remove the leading '@'. + $service_id = substr($service_id, 1); + } + else { + throw new \InvalidArgumentException('You must provide the context IDs in the @{service_id}:{unqualified_context_id} format.'); + } + $context_ids_by_service[$service_id][] = $unqualified_context_id; + } + + // Iterate over all missing context providers (services), gather the + // runtime contexts and assign them as requested. + foreach ($context_ids_by_service as $service_id => $unqualified_context_ids) { + $contexts_by_service = $this->container->get($service_id)->getRuntimeContexts($unqualified_context_ids); + + $wanted_contexts = array_intersect_key($contexts_by_service, array_flip($unqualified_context_ids)); + foreach ($wanted_contexts as $unqualified_context_id => $context) { + $context_id = '@' . $service_id . ':' . $unqualified_context_id; + $this->contexts[$context_id] = $contexts[$context_id] = $context; + } + } + + return $contexts; + } + + /** + * {@inheritdoc} + */ + public function getAvailableContexts() { + $contexts = []; + foreach ($this->contextProviderServiceIDs as $service_id) { + $contexts_by_service = $this->container->get($service_id)->getAvailableContexts(); + foreach ($contexts_by_service as $unqualified_context_id => $context) { + $context_id = '@' . $service_id . ':' . $unqualified_context_id; + $contexts[$context_id] = $context; + } + } + + return $contexts; + } + +} diff --git a/core/modules/block/block.install b/core/modules/block/block.install new file mode 100644 index 0000000..645f357 --- /dev/null +++ b/core/modules/block/block.install @@ -0,0 +1,61 @@ + 'language.current_language_context', + 'node' => 'node.node_route_context', + 'user' => 'user.current_user_context', + ]; + + $config_factory = \Drupal::configFactory(); + $message = NULL; + foreach ($config_factory->listAll('block.block.') as $block_config_name) { + // We deal with config objects directly to avoid the config entity API. + $block = $config_factory->getEditable($block_config_name); + $disable_block = FALSE; + if ($visibility = $block->get('visibility')) { + foreach ($visibility as &$condition) { + foreach ($condition['context_mapping'] as $key => $context) { + if (!isset($context_service_id_map[$key])) { + // Skip unknown entries, so the update process itself runs through + // and users can fix their block placements manually OR + // alternatively contrib modules can run their own update functions + // to update mappings that they provide. + $message = \Drupal::translation()->translate('Encountered an unexpected context mapping key, one or more mappings could not be updated. Please manually review your block placements.'); + $disable_block = TRUE; + continue; + } + $new_context_id = explode('.', $context, 2); + $condition['context_mapping'][$key] = '@' . $context_service_id_map[$key] . ':' . $new_context_id[1]; + } + } + if ($disable_block) { + // This block will have an invalid context mapping service and must be + // disabled to avoid the 'You must provide the context IDs in the + // @{service_id}:{unqualified_context_id} format' exception. + $block->set('status', FALSE); + } + $block->set('visibility', $visibility); + } + + $block->save(); + } + return $message; +} + +/** + * @} End of "addtogroup updates-8.0.x-beta". + */ diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml index df4d0d7..fe20e4a 100644 --- a/core/modules/block/block.services.yml +++ b/core/modules/block/block.services.yml @@ -7,21 +7,6 @@ services: class: Drupal\block\EventSubscriber\BlockPageDisplayVariantSubscriber tags: - { name: event_subscriber } - block.current_user_context: - class: Drupal\block\EventSubscriber\CurrentUserContext - arguments: ['@current_user', '@entity.manager'] - tags: - - { name: 'event_subscriber' } - block.current_language_context: - class: Drupal\block\EventSubscriber\CurrentLanguageContext - arguments: ['@language_manager'] - tags: - - { name: 'event_subscriber' } - block.node_route_context: - class: Drupal\block\EventSubscriber\NodeRouteContext - arguments: ['@current_route_match'] - tags: - - { name: 'event_subscriber' } block.repository: class: Drupal\block\BlockRepository arguments: ['@entity.manager', '@theme.manager', '@context.handler'] diff --git a/core/modules/block/src/BlockAccessControlHandler.php b/core/modules/block/src/BlockAccessControlHandler.php index 473e58e..b7fea60 100644 --- a/core/modules/block/src/BlockAccessControlHandler.php +++ b/core/modules/block/src/BlockAccessControlHandler.php @@ -18,6 +18,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Executable\ExecutableManagerInterface; use Drupal\Core\Plugin\Context\ContextHandlerInterface; +use Drupal\Core\Plugin\Context\ContextRepositoryInterface; use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -46,13 +47,21 @@ class BlockAccessControlHandler extends EntityAccessControlHandler implements En protected $contextHandler; /** + * The context manager service. + * + * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface + */ + protected $contextRepository; + + /** * {@inheritdoc} */ public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { return new static( $entity_type, $container->get('plugin.manager.condition'), - $container->get('context.handler') + $container->get('context.handler'), + $container->get('context.repository') ); } @@ -65,11 +74,14 @@ public static function createInstance(ContainerInterface $container, EntityTypeI * The ConditionManager for checking visibility of blocks. * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler * The ContextHandler for applying contexts to conditions properly. + * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository + * The lazy context repository service. */ - public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler) { + public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler, ContextRepositoryInterface $context_repository ) { parent::__construct($entity_type); $this->manager = $manager; $this->contextHandler = $context_handler; + $this->contextRepository = $context_repository; } @@ -87,12 +99,12 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A return AccessResult::forbidden()->cacheUntilEntityChanges($entity); } else { - $contexts = $entity->getContexts(); $conditions = []; $missing_context = FALSE; foreach ($entity->getVisibilityConditions() as $condition_id => $condition) { if ($condition instanceof ContextAwarePluginInterface) { try { + $contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping())); $this->contextHandler->applyContextMapping($condition, $contexts); } catch (ContextException $e) { diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php index 07da65b..3c07e17 100644 --- a/core/modules/block/src/BlockForm.php +++ b/core/modules/block/src/BlockForm.php @@ -7,8 +7,6 @@ namespace Drupal\block; -use Drupal\block\Event\BlockContextEvent; -use Drupal\block\Event\BlockEvents; use Drupal\Component\Utility\Html; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityManagerInterface; @@ -18,8 +16,8 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Plugin\ContextAwarePluginInterface; +use Drupal\Core\Plugin\Context\ContextRepositoryInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Provides form for block instance forms. @@ -69,23 +67,30 @@ class BlockForm extends EntityForm { protected $themeHandler; /** + * The context manager service. + * + * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface + */ + protected $contextRepository; + + /** * Constructs a BlockForm object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager * The ConditionManager for building the visibility UI. - * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher - * The EventDispatcher for gathering administrative contexts. + * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository + * The lazy context repository service. * @param \Drupal\Core\Language\LanguageManagerInterface $language * The language manager. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. */ - public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, EventDispatcherInterface $dispatcher, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) { + public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) { $this->storage = $entity_manager->getStorage('block'); $this->manager = $manager; - $this->dispatcher = $dispatcher; + $this->contextRepository = $context_repository; $this->language = $language; $this->themeHandler = $theme_handler; } @@ -97,7 +102,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), $container->get('plugin.manager.condition'), - $container->get('event_dispatcher'), + $container->get('context.repository'), $container->get('language_manager'), $container->get('theme_handler') ); @@ -117,7 +122,7 @@ public function form(array $form, FormStateInterface $form_state) { // Store the gathered contexts in the form state for other objects to use // during form building. - $form_state->setTemporaryValue('gathered_contexts', $this->dispatcher->dispatch(BlockEvents::ADMINISTRATIVE_CONTEXT, new BlockContextEvent())->getContexts()); + $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts()); $form['#tree'] = TRUE; $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state); diff --git a/core/modules/block/src/BlockInterface.php b/core/modules/block/src/BlockInterface.php index 39ad9a9..8955be1 100644 --- a/core/modules/block/src/BlockInterface.php +++ b/core/modules/block/src/BlockInterface.php @@ -96,24 +96,6 @@ public function getVisibilityCondition($instance_id); public function setVisibilityConfig($instance_id, array $configuration); /** - * Get all available contexts. - * - * @return \Drupal\Component\Plugin\Context\ContextInterface[] - * An array of set contexts, keyed by context name. - */ - public function getContexts(); - - /** - * Set the contexts that are available for use within the block entity. - * - * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts - * An array of contexts to set on the block. - * - * @return $this - */ - public function setContexts(array $contexts); - - /** * Returns the weight of this block (used for sorting). * * @return int diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php index bc5eb8c..9d56fde 100644 --- a/core/modules/block/src/BlockRepository.php +++ b/core/modules/block/src/BlockRepository.php @@ -50,7 +50,7 @@ public function __construct(EntityManagerInterface $entity_manager, ThemeManager /** * {@inheritdoc} */ - public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_metadata = []) { + public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) { $active_theme = $this->themeManager->getActiveTheme(); // Build an array of the region names in the right order. $empty = array_fill_keys($active_theme->getRegions(), array()); @@ -58,7 +58,6 @@ public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_met $full = array(); foreach ($this->blockStorage->loadByProperties(array('theme' => $active_theme->getName())) as $block_id => $block) { /** @var \Drupal\block\BlockInterface $block */ - $block->setContexts($contexts); $access = $block->access('view', NULL, TRUE); $region = $block->getRegion(); if (!isset($cacheable_metadata[$region])) { diff --git a/core/modules/block/src/BlockRepositoryInterface.php b/core/modules/block/src/BlockRepositoryInterface.php index 00eb5e4..59dc260 100644 --- a/core/modules/block/src/BlockRepositoryInterface.php +++ b/core/modules/block/src/BlockRepositoryInterface.php @@ -12,8 +12,6 @@ /** * Returns an array of regions and their block entities. * - * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts - * An array of contexts to set on the blocks. * @param \Drupal\Core\Cache\CacheableMetadata[] $cacheable_metadata * (optional) List of CacheableMetadata objects, keyed by region. This is * by reference and is used to pass this information back to the caller. @@ -22,6 +20,6 @@ * The array is first keyed by region machine name, with the values * containing an array keyed by block ID, with block entities as the values. */ - public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_metadata = []); + public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []); } diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php index ace30b4..d2b3031 100644 --- a/core/modules/block/src/Entity/Block.php +++ b/core/modules/block/src/Entity/Block.php @@ -253,21 +253,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { /** * {@inheritdoc} */ - public function setContexts(array $contexts) { - $this->contexts = $contexts; - return $this; - } - - /** - * {@inheritdoc} - */ - public function getContexts() { - return $this->contexts; - } - - /** - * {@inheritdoc} - */ public function getVisibility() { return $this->getVisibilityConditions()->getConfiguration(); } diff --git a/core/modules/block/src/Event/BlockContextEvent.php b/core/modules/block/src/Event/BlockContextEvent.php deleted file mode 100644 index 99b0bbd..0000000 --- a/core/modules/block/src/Event/BlockContextEvent.php +++ /dev/null @@ -1,53 +0,0 @@ -contexts[$name] = $context; - return $this; - } - - /** - * Returns the context objects. - * - * @return \Drupal\Component\Plugin\Context\ContextInterface[] - * An array of contexts that have been provided. - */ - public function getContexts() { - return $this->contexts; - } - -} diff --git a/core/modules/block/src/Event/BlockEvents.php b/core/modules/block/src/Event/BlockEvents.php deleted file mode 100644 index 118f2f8..0000000 --- a/core/modules/block/src/Event/BlockEvents.php +++ /dev/null @@ -1,55 +0,0 @@ -setContextValue($node); - * $event->setContext('node.node', $context); - * @endcode - * - * @param \Drupal\block\Event\BlockContextEvent $event - * The Event to which to register available contexts. - */ - abstract public function onBlockActiveContext(BlockContextEvent $event); - - /** - * Determines the available configuration-time contexts. - * - * When a block is being configured, the configuration UI must know which - * named contexts are potentially available, but does not care about the - * value, since the value can be different for each request, and might not - * be available at all during the configuration UI's request. - * - * For example: - * @code - * // During configuration, there is no specific node to pass as context. - * // However, inform the system that a context named 'node.node' is - * // available, and provide its definition, so that blocks can be - * // configured to use it. When the block is rendered, the value of this - * // context will be supplied by onBlockActiveContext(). - * $context = new Context(new ContextDefinition('entity:node')); - * $event->setContext('node.node', $context); - * @endcode - * - * @param \Drupal\block\Event\BlockContextEvent $event - * The Event to which to register available contexts. - * - * @see static::onBlockActiveContext() - */ - abstract public function onBlockAdministrativeContext(BlockContextEvent $event); - -} diff --git a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php index f5caee4..38b3dab 100644 --- a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php +++ b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php @@ -8,18 +8,15 @@ namespace Drupal\block\Plugin\DisplayVariant; use Drupal\block\BlockRepositoryInterface; -use Drupal\block\Event\BlockContextEvent; -use Drupal\block\Event\BlockEvents; use Drupal\Core\Block\MainContentBlockPluginInterface; use Drupal\Core\Block\MessagesBlockPluginInterface; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Display\PageVariantInterface; -use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Display\VariantBase; +use Drupal\Core\Theme\ThemeManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Provides a page display variant that decorates the main content with blocks. @@ -80,16 +77,13 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont * The block repository. * @param \Drupal\Core\Entity\EntityViewBuilderInterface $block_view_builder * The block view builder. - * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher - * The event dispatcher. * @param string[] $block_list_cache_tags * The Block entity type list cache tags. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher, array $block_list_cache_tags) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->blockRepository = $block_repository; $this->blockViewBuilder = $block_view_builder; - $this->dispatcher = $dispatcher; $this->blockListCacheTags = $block_list_cache_tags; } @@ -103,7 +97,6 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_definition, $container->get('block.repository'), $container->get('entity.manager')->getViewBuilder('block'), - $container->get('event_dispatcher'), $container->get('entity.manager')->getDefinition('block')->getListCacheTags() ); } @@ -129,10 +122,9 @@ public function build() { 'tags' => $this->blockListCacheTags, ], ]; - $contexts = $this->getActiveBlockContexts(); // Load all region content assigned via blocks. $cacheable_metadata_list = []; - foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts, $cacheable_metadata_list) as $region => $blocks) { + foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata_list) as $region => $blocks) { /** @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $key => $block) { $block_plugin = $block->getPlugin(); @@ -188,14 +180,4 @@ public function build() { return $build; } - /** - * Returns an array of context objects to set on the blocks. - * - * @return \Drupal\Component\Plugin\Context\ContextInterface[] - * An array of contexts to set on the blocks. - */ - protected function getActiveBlockContexts() { - return $this->dispatcher->dispatch(BlockEvents::ACTIVE_CONTEXT, new BlockContextEvent())->getContexts(); - } - } diff --git a/core/modules/block/src/Tests/BlockLanguageTest.php b/core/modules/block/src/Tests/BlockLanguageTest.php index 56ee191..265524f 100644 --- a/core/modules/block/src/Tests/BlockLanguageTest.php +++ b/core/modules/block/src/Tests/BlockLanguageTest.php @@ -90,7 +90,7 @@ public function testLanguageBlockVisibilityLanguageDelete() { 'langcodes' => array( 'fr' => 'fr', ), - 'context_mapping' => ['language' => 'language.language_interface'], + 'context_mapping' => ['language' => '@language.current_language_context:language_interface'], ), ), ); @@ -142,7 +142,7 @@ public function testMultipleLanguageTypes() { // Enable a standard block and set visibility to French only. $block_id = strtolower($this->randomMachineName(8)); $edit = [ - 'visibility[language][context_mapping][language]' => 'language.language_interface', + 'visibility[language][context_mapping][language]' => '@language.current_language_context:language_interface', 'visibility[language][langcodes][fr]' => TRUE, 'id' => $block_id, 'region' => 'sidebar_first', @@ -168,7 +168,7 @@ public function testMultipleLanguageTypes() { // Change visibility to now depend on content language for this block. $edit = [ - 'visibility[language][context_mapping][language]' => 'language.language_content' + 'visibility[language][context_mapping][language]' => '@language.current_language_context:language_content' ]; $this->drupalPostForm('admin/structure/block/manage/' . $block_id, $edit, t('Save block')); diff --git a/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php b/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php new file mode 100644 index 0000000..1a89537 --- /dev/null +++ b/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php @@ -0,0 +1,78 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php', + ]; + parent::setUp(); + } + + /** + * Tests that block context mapping are updated properly. + */ + public function testUpdateHookN() { + $this->runUpdates(); + $this->assertText(t('Encountered an unexpected context mapping key, one or more mappings could not be updated. Please manually review your block placements.')); + + // Disable maintenance mode. + \Drupal::state()->set('system.maintenance_mode', FALSE); + + // The block that we are testing has the following visibility rules: + // - only visible on node pages + // - only visible to authenticated users. + $block_title = 'Test for 2354889'; + + // Create two nodes, a page and an article. + $page = Node::create([ + 'type' => 'page', + 'title' => 'Page node', + ]); + $page->save(); + + $article = Node::create([ + 'type' => 'article', + 'title' => 'Article node', + ]); + $article->save(); + + // Check that the block appears only on Page nodes for authenticated users. + $this->drupalGet('node/' . $page->id()); + $this->assertRaw($block_title, 'Test block is visible on a Page node as an authenticated user.'); + + $this->drupalGet('node/' . $article->id()); + $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an authenticated user.'); + + $this->drupalLogout(); + + // Check that the block does not appear on any page for anonymous users. + $this->drupalGet('node/' . $page->id()); + $this->assertNoRaw($block_title, 'Test block is not visible on a Page node as an anonymous user.'); + + $this->drupalGet('node/' . $article->id()); + $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an anonymous user.'); + } + +} diff --git a/core/modules/block/tests/src/Unit/BlockFormTest.php b/core/modules/block/tests/src/Unit/BlockFormTest.php index 8acff20..fbb4096 100644 --- a/core/modules/block/tests/src/Unit/BlockFormTest.php +++ b/core/modules/block/tests/src/Unit/BlockFormTest.php @@ -31,13 +31,6 @@ class BlockFormTest extends UnitTestCase { protected $storage; /** - * The event dispatcher service. - * - * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $dispatcher; - - /** * The language manager service. * * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject @@ -60,6 +53,13 @@ class BlockFormTest extends UnitTestCase { protected $entityManager; /** + * The mocked context repository. + * + * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $contextRepository; + + /** * {@inheritdoc} */ protected function setUp() { @@ -67,7 +67,7 @@ protected function setUp() { $this->conditionManager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface'); $this->language = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); - $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->contextRepository = $this->getMock('Drupal\Core\Plugin\Context\ContextRepositoryInterface'); $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); $this->storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); @@ -104,7 +104,7 @@ public function testGetUniqueMachineName() { ->method('getQuery') ->will($this->returnValue($query)); - $block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->dispatcher, $this->language, $this->themeHandler); + $block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->contextRepository, $this->language, $this->themeHandler); // Ensure that the block with just one other instance gets the next available // name suggestion. diff --git a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php index d77d64e..d837848 100644 --- a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php +++ b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php @@ -60,7 +60,7 @@ protected function setUp() { ]); $theme_manager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface'); - $theme_manager->expects($this->once()) + $theme_manager->expects($this->atLeastOnce()) ->method('getActiveTheme') ->will($this->returnValue($active_theme)); @@ -86,9 +86,6 @@ public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expec foreach ($blocks_config as $block_id => $block_config) { $block = $this->getMock('Drupal\block\BlockInterface'); $block->expects($this->once()) - ->method('setContexts') - ->willReturnSelf(); - $block->expects($this->once()) ->method('access') ->will($this->returnValue($block_config[0])); $block->expects($block_config[0] ? $this->atLeastOnce() : $this->never()) @@ -102,7 +99,8 @@ public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expec ->with(['theme' => $this->theme]) ->willReturn($blocks); $result = []; - foreach ($this->blockRepository->getVisibleBlocksPerRegion([]) as $region => $resulting_blocks) { + $cacheable_metadata = []; + foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) { $result[$region] = []; foreach ($resulting_blocks as $plugin_id => $block) { $result[$region][] = $plugin_id; @@ -148,9 +146,6 @@ public function providerBlocksConfig() { public function testGetVisibleBlocksPerRegionWithContext() { $block = $this->getMock('Drupal\block\BlockInterface'); $block->expects($this->once()) - ->method('setContexts') - ->willReturnSelf(); - $block->expects($this->once()) ->method('access') ->willReturn(AccessResult::allowed()->addCacheTags(['config:block.block.block_id'])); $block->expects($this->once()) @@ -158,14 +153,13 @@ public function testGetVisibleBlocksPerRegionWithContext() { ->willReturn('top'); $blocks['block_id'] = $block; - $contexts = []; $this->blockStorage->expects($this->once()) ->method('loadByProperties') ->with(['theme' => $this->theme]) ->willReturn($blocks); $result = []; $cacheable_metadata = []; - foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts, $cacheable_metadata) as $region => $resulting_blocks) { + foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) { $result[$region] = []; foreach ($resulting_blocks as $plugin_id => $block) { $result[$region][] = $plugin_id; diff --git a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php index 5438a99..3f4e3e2 100644 --- a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php +++ b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\DependencyInjection\Container; +use Drupal\Core\Theme\ActiveTheme; use Drupal\Tests\UnitTestCase; /** @@ -32,13 +33,6 @@ class BlockPageVariantTest extends UnitTestCase { protected $blockViewBuilder; /** - * The event dispatcher. - * - * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $dispatcher; - - /** * The plugin context handler. * * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface|\PHPUnit_Framework_MockObject_MockObject @@ -71,12 +65,9 @@ public function setUpDisplayVariant($configuration = array(), $definition = arra $this->blockRepository = $this->getMock('Drupal\block\BlockRepositoryInterface'); $this->blockViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface'); - $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - $this->dispatcher->expects($this->any()) - ->method('dispatch') - ->willReturnArgument(1); + return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant') - ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, ['config:block_list'])) + ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, ['config:block_list'])) ->setMethods(array('getRegionNames')) ->getMock(); } @@ -217,23 +208,26 @@ public function testBuild(array $blocks_config, $visible_block_count, array $exp $messages_block_plugin = $this->getMock('Drupal\Core\Block\MessagesBlockPluginInterface'); foreach ($blocks_config as $block_id => $block_config) { $block = $this->getMock('Drupal\block\BlockInterface'); + $block->expects($this->any()) + ->method('getContexts') + ->willReturn([]); $block->expects($this->atLeastOnce()) ->method('getPlugin') ->willReturn($block_config[1] ? $main_content_block_plugin : ($block_config[2] ? $messages_block_plugin : $block_plugin)); $blocks[$block_config[0]][$block_id] = $block; } - $this->blockViewBuilder->expects($this->exactly($visible_block_count)) ->method('view') ->will($this->returnValue(array())); $this->blockRepository->expects($this->once()) ->method('getVisibleBlocksPerRegion') - ->willReturnCallback(function ($contexts, &$cacheable_metadata) use ($blocks) { + ->willReturnCallback(function (&$cacheable_metadata) use ($blocks) { $cacheable_metadata['top'] = (new CacheableMetadata())->addCacheTags(['route']); return $blocks; }); - $this->assertSame($expected_render_array, $display_variant->build()); + $value = $display_variant->build(); + $this->assertSame($expected_render_array, $value); } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index b256da6..11e3692 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -345,7 +345,7 @@ function comment_entity_storage_load($entities, $entity_type) { if (!\Drupal::entityManager()->getDefinition($entity_type)->isSubclassOf('Drupal\Core\Entity\FieldableEntityInterface')) { return; } - if (!\Drupal::service('comment.manager')->getFields($entity_type)) { + if (!\Drupal::hasService('comment.manager') || !\Drupal::service('comment.manager')->getFields($entity_type)) { // Do not query database when entity has no comment fields. return; } diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml index b9de79a..5a2d52c 100644 --- a/core/modules/language/language.services.yml +++ b/core/modules/language/language.services.yml @@ -24,3 +24,8 @@ services: tags: - { name: paramconverter } lazy: true + language.current_language_context: + class: Drupal\language\ContextProvider\CurrentLanguageContext + arguments: ['@language_manager'] + tags: + - { name: 'context_provider' } diff --git a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php b/core/modules/language/src/ContextProvider/CurrentLanguageContext.php similarity index 66% rename from core/modules/block/src/EventSubscriber/CurrentLanguageContext.php rename to core/modules/language/src/ContextProvider/CurrentLanguageContext.php index 7b1a206..bf59ad2 100644 --- a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php +++ b/core/modules/language/src/ContextProvider/CurrentLanguageContext.php @@ -2,22 +2,22 @@ /** * @file - * Contains \Drupal\block\EventSubscriber\CurrentLanguageContext. + * Contains \Drupal\language\ContextProvider\CurrentLanguageContext. */ -namespace Drupal\block\EventSubscriber; +namespace Drupal\language\ContextProvider; -use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\Plugin\Context\ContextProviderInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Sets the current language as a context. */ -class CurrentLanguageContext extends BlockContextSubscriberBase { +class CurrentLanguageContext implements ContextProviderInterface { use StringTranslationTrait; @@ -41,10 +41,20 @@ public function __construct(LanguageManagerInterface $language_manager) { /** * {@inheritdoc} */ - public function onBlockActiveContext(BlockContextEvent $event) { + public function getRuntimeContexts(array $unqualified_context_ids) { // Add a context for each language type. $language_types = $this->languageManager->getLanguageTypes(); $info = $this->languageManager->getDefinedLanguageTypesInfo(); + + if ($unqualified_context_ids) { + foreach ($unqualified_context_ids as $unqualified_context_id) { + if (array_search($unqualified_context_id, $language_types) === FALSE) { + unset($language_types[$unqualified_context_id]); + } + } + } + + $result = []; foreach ($language_types as $type_key) { if (isset($info[$type_key]['name'])) { $context = new Context(new ContextDefinition('language', $info[$type_key]['name'])); @@ -54,16 +64,18 @@ public function onBlockActiveContext(BlockContextEvent $event) { $cacheability->setCacheContexts(['languages:' . $type_key]); $context->addCacheableDependency($cacheability); - $event->setContext('language.' . $type_key, $context); + $result[$type_key] = $context; } } + + return $result; } /** * {@inheritdoc} */ - public function onBlockAdministrativeContext(BlockContextEvent $event) { - $this->onBlockActiveContext($event); + public function getAvailableContexts() { + return $this->getRuntimeContexts([]); } } diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml index 637cdee..2ff32c3 100644 --- a/core/modules/node/node.services.yml +++ b/core/modules/node/node.services.yml @@ -45,3 +45,8 @@ services: arguments: ['@current_user'] tags: - { name: cache.context } + node.node_route_context: + class: Drupal\node\ContextProvider\NodeRouteContext + arguments: ['@current_route_match'] + tags: + - { name: 'context_provider' } diff --git a/core/modules/block/src/EventSubscriber/NodeRouteContext.php b/core/modules/node/src/ContextProvider/NodeRouteContext.php similarity index 75% rename from core/modules/block/src/EventSubscriber/NodeRouteContext.php rename to core/modules/node/src/ContextProvider/NodeRouteContext.php index 89d24f5..e6f5781 100644 --- a/core/modules/block/src/EventSubscriber/NodeRouteContext.php +++ b/core/modules/node/src/ContextProvider/NodeRouteContext.php @@ -2,22 +2,22 @@ /** * @file - * Contains \Drupal\block\EventSubscriber\NodeRouteContext. + * Contains \Drupal\node\ContextProvider\NodeRouteContext. */ -namespace Drupal\block\EventSubscriber; +namespace Drupal\node\ContextProvider; -use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\Plugin\Context\ContextProviderInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\node\Entity\Node; /** * Sets the current node as a context on node routes. */ -class NodeRouteContext extends BlockContextSubscriberBase { +class NodeRouteContext implements ContextProviderInterface { /** * The route match object. @@ -39,13 +39,13 @@ public function __construct(RouteMatchInterface $route_match) { /** * {@inheritdoc} */ - public function onBlockActiveContext(BlockContextEvent $event) { + public function getRuntimeContexts(array $unqualified_context_ids) { + $result = []; $context = new Context(new ContextDefinition('entity:node', NULL, FALSE)); if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) { if ($node = $this->routeMatch->getParameter('node')) { $context->setContextValue($node); } - $event->setContext('node.node', $context); } elseif ($this->routeMatch->getRouteName() == 'node.add') { $node_type = $this->routeMatch->getParameter('node_type'); @@ -54,15 +54,17 @@ public function onBlockActiveContext(BlockContextEvent $event) { $cacheability = new CacheableMetadata(); $cacheability->setCacheContexts(['route']); $context->addCacheableDependency($cacheability); - $event->setContext('node.node', $context); + $result['node'] = $context; + + return $result; } /** * {@inheritdoc} */ - public function onBlockAdministrativeContext(BlockContextEvent $event) { + public function getAvailableContexts() { $context = new Context(new ContextDefinition('entity:node')); - $event->setContext('node.node', $context); + return ['node' => $context]; } } diff --git a/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php b/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php new file mode 100644 index 0000000..0ad77c5 --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php @@ -0,0 +1,158 @@ + '9d204071-a923-4707-8200-c298a540fb0c', + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [ + 'content' => [ + 'block_content:basic:c1895145-893e-460b-a24e-78cd2cefbb1f', + ], + 'module' => [ + 'block_content', + 'node', + 'user', + ], + 'theme' => [ + 'bartik', + ], + ], + 'id' => 'testfor2354889', + 'theme' => 'bartik', + 'region' => 'content', + 'weight' => -6, + 'provider' => NULL, + 'plugin' => 'block_content:c1895145-893e-460b-a24e-78cd2cefbb1f', + 'settings' => [ + 'id' => 'block_content:c1895145-893e-460b-a24e-78cd2cefbb1f', + 'label' => 'Test for 2354889', + 'provider' => 'block_content', + 'label_display' => 'visible', + 'cache' => [ + 'max_age' => -1, + ], + 'status' => TRUE, + 'info' => '', + 'view_mode' => 'full', + ], + 'visibility' => [ + 'node_type' => [ + 'id' => 'node_type', + 'bundles' => [ + 'page' => 'page', + ], + 'negate' => FALSE, + // Context that will be tested. + 'context_mapping' => [ + 'node' => 'node.node', + ], + ], + 'user_role' => [ + 'id' => 'user_role', + 'roles' => [ + 'authenticated' => 'authenticated', + ], + 'negate' => FALSE, + // Context that will be tested. + 'context_mapping' => [ + 'user' => 'user.current_user', + ], + ], + ], +],[ + 'uuid' => 'C499B41D-035E-432E-9462-36410C43C49F', + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [ + 'module' => [ + 'search', + ], + 'theme' => [ + 'bartik', + ], + ], + 'id' => 'secondtestfor2354889', + 'theme' => 'bartik', + 'region' => 'sidebar_first', + 'weight' => -6, + 'provider' => NULL, + 'plugin' => 'search_form_block', + 'settings' => [ + 'id' => 'search_form_block', + 'label' => 'Search', + 'provider' => 'search', + 'label_display' => 'visible', + 'cache' => [ + 'max_age' => -1, + ], + 'status' => TRUE, + ], + 'visibility' => [], +],[ + 'uuid' => '4558907D-2918-48FE-B56F-8A007B5FBDD5', + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [ + 'module' => [ + 'user', + ], + 'theme' => [ + 'bartik', + ], + ], + 'id' => 'thirdtestfor2354889', + 'theme' => 'bartik', + 'region' => 'sidebar_first', + 'weight' => -6, + 'provider' => NULL, + 'plugin' => 'user_login_block', + 'settings' => [ + 'id' => 'user_login_block', + 'label' => 'User login', + 'provider' => 'user', + 'label_display' => 'visible', + 'cache' => [ + 'max_age' => -1, + ], + 'status' => TRUE, + ], + 'visibility' => [ + 'node_type' => [ + 'id' => 'node_type', + 'bundles' => [ + 'page' => 'page', + ], + 'negate' => FALSE, + // Non-existent mapping that will be tested. + 'context_mapping' => [ + 'baloney' => 'baloney.spam', + ], + ], + ], +]]; + +foreach ($block_configs as $block_config) { + $connection->insert('config') + ->fields(array( + 'collection', + 'name', + 'data', + )) + ->values(array( + 'collection' => '', + 'name' => 'block.block.' . $block_config['id'], + 'data' => serialize($block_config), + )) + ->execute(); +} diff --git a/core/modules/block/src/EventSubscriber/CurrentUserContext.php b/core/modules/user/src/ContextProvider/CurrentUserContext.php similarity index 76% rename from core/modules/block/src/EventSubscriber/CurrentUserContext.php rename to core/modules/user/src/ContextProvider/CurrentUserContext.php index 194a252..1a0f26c 100644 --- a/core/modules/block/src/EventSubscriber/CurrentUserContext.php +++ b/core/modules/user/src/ContextProvider/CurrentUserContext.php @@ -2,23 +2,23 @@ /** * @file - * Contains \Drupal\block\EventSubscriber\CurrentUserContext. + * Contains \Drupal\user\ContextProvider\CurrentUserContext. */ -namespace Drupal\block\EventSubscriber; +namespace Drupal\user\ContextProvider; -use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\Plugin\Context\ContextProviderInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Sets the current user as a context. */ -class CurrentUserContext extends BlockContextSubscriberBase { +class CurrentUserContext implements ContextProviderInterface { use StringTranslationTrait; @@ -52,7 +52,7 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e /** * {@inheritdoc} */ - public function onBlockActiveContext(BlockContextEvent $event) { + public function getRuntimeContexts(array $unqualified_context_ids) { $current_user = $this->userStorage->load($this->account->id()); $context = new Context(new ContextDefinition('entity:user', $this->t('Current user'))); @@ -60,14 +60,19 @@ public function onBlockActiveContext(BlockContextEvent $event) { $cacheability = new CacheableMetadata(); $cacheability->setCacheContexts(['user']); $context->addCacheableDependency($cacheability); - $event->setContext('user.current_user', $context); + + $result = [ + 'current_user' => $context, + ]; + + return $result; } /** * {@inheritdoc} */ - public function onBlockAdministrativeContext(BlockContextEvent $event) { - $this->onBlockActiveContext($event); + public function getAvailableContexts() { + return $this->getRuntimeContexts([]); } } diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml index be4e679..c273cb3 100644 --- a/core/modules/user/user.services.yml +++ b/core/modules/user/user.services.yml @@ -61,6 +61,11 @@ services: user.permissions: class: Drupal\user\PermissionHandler arguments: ['@module_handler', '@string_translation', '@controller_resolver'] + user.current_user_context: + class: Drupal\user\ContextProvider\CurrentUserContext + arguments: ['@current_user', '@entity.manager'] + tags: + - { name: 'context_provider' } parameters: user.tempstore.expire: 604800 diff --git a/core/tests/Drupal/Tests/Core/Plugin/Context/LazyContextRepositoryTest.php b/core/tests/Drupal/Tests/Core/Plugin/Context/LazyContextRepositoryTest.php new file mode 100644 index 0000000..878d7b8 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Plugin/Context/LazyContextRepositoryTest.php @@ -0,0 +1,152 @@ +container = new ContainerBuilder(); + } + + /** + * @covers ::getRuntimeContexts + */ + public function testGetRuntimeContextsSingle() { + $contexts = $this->setupContextAndProvider('test_provider', ['test_context']); + + $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']); + $run_time_contexts = $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context']); + $this->assertEquals(['@test_provider:test_context' => $contexts[0]], $run_time_contexts); + } + + /** + * @covers ::getRuntimeContexts + */ + public function testGetRuntimeMultipleContextsPerService() { + $contexts = $this->setupContextAndProvider('test_provider', ['test_context0', 'test_context1']); + + $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']); + $run_time_contexts = $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider:test_context1']); + $this->assertEquals(['@test_provider:test_context0' => $contexts[0], '@test_provider:test_context1' => $contexts[1]], $run_time_contexts); + } + + /** + * @covers ::getRuntimeContexts + */ + public function testGetRuntimeMultipleContextProviders() { + $contexts0 = $this->setupContextAndProvider('test_provider', ['test_context0', 'test_context1'], ['test_context0']); + $contexts1 = $this->setupContextAndProvider('test_provider2', ['test1_context0', 'test1_context1'], ['test1_context0']); + + $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']); + $run_time_contexts = $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider2:test1_context0']); + $this->assertEquals(['@test_provider:test_context0' => $contexts0[0], '@test_provider2:test1_context0' => $contexts1[1]], $run_time_contexts); + } + + /** + * @covers ::getRuntimeContexts + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You must provide the context IDs in the @{service_id}:{unqualified_context_id} format. + */ + public function testInvalidContextId() { + $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']); + $lazy_context_repository->getRuntimeContexts(['test_context', '@test_provider:test_context1']); + } + + /** + * @covers ::getRuntimeContexts + */ + public function testGetRuntimeStaticCache() { + $context0 = new Context(new ContextDefinition('example')); + $context1 = new Context(new ContextDefinition('example')); + + $context_provider = $this->prophesize('\Drupal\Core\Plugin\Context\ContextProviderInterface'); + $context_provider->getRuntimeContexts(['test_context0', 'test_context1']) + ->shouldBeCalledTimes(1) + ->willReturn(['test_context0' => $context0, 'test_context1' => $context1]); + $context_provider = $context_provider->reveal(); + $this->container->set('test_provider', $context_provider); + + $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']); + $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider:test_context1']); + $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider:test_context1']); + } + + /** + * @covers ::getAvailableContexts + */ + public function testGetAvailableContexts() { + $contexts0 = $this->setupContextAndProvider('test_provider0', ['test0_context0', 'test0_context1']); + $contexts1 = $this->setupContextAndProvider('test_provider1', ['test1_context0', 'test1_context1']); + + $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider0', 'test_provider1']); + $contexts = $lazy_context_repository->getAvailableContexts(); + + $this->assertEquals([ + '@test_provider0:test0_context0' => $contexts0[0], + '@test_provider0:test0_context1' => $contexts0[1], + '@test_provider1:test1_context0' => $contexts1[0], + '@test_provider1:test1_context1' => $contexts1[1], + ], $contexts); + + } + + /** + * Sets up contexts and context providers. + * + * @param string $service_id + * The service ID of the service provider. + * @param string[] $unqualified_context_ids + * An array of context slot names. + * @param string[] $expected_unqualified_context_ids + * The expected context slotes passed to getRuntimeContexts. + * + * @return array + * An array of set up contexts. + */ + protected function setupContextAndProvider($service_id, array $unqualified_context_ids, array $expected_unqualified_context_ids = []) { + $contexts = []; + for ($i = 0; $i < count($unqualified_context_ids); $i++) { + $contexts[] = new Context(new ContextDefinition('example')); + } + + $expected_unqualified_context_ids = $expected_unqualified_context_ids ?: $unqualified_context_ids; + + $context_provider = $this->prophesize('\Drupal\Core\Plugin\Context\ContextProviderInterface'); + $context_provider->getRuntimeContexts($expected_unqualified_context_ids) + ->willReturn(array_combine($unqualified_context_ids, $contexts)); + $context_provider->getAvailableContexts() + ->willReturn(array_combine($unqualified_context_ids, $contexts)); + $context_provider = $context_provider->reveal(); + $this->container->set($service_id, $context_provider); + + return $contexts; + } + +}