Commit f0567724 authored by webchick's avatar webchick

Issue #2377757 by EclipseGc, tim.plunkett, Frando, Berdir, japerry, dsnopek,...

Issue #2377757 by EclipseGc, tim.plunkett, Frando, Berdir, japerry, dsnopek, Wim Leers, Bojhan: Expose Block Context mapping in the UI
parent 1d1fe197
......@@ -307,6 +307,11 @@ block_settings:
provider:
type: string
label: 'Provider'
context_mapping:
type: sequence
label: 'Context assignments'
sequence:
type: string
condition.plugin:
type: mapping
......
......@@ -10,6 +10,7 @@
use Drupal\block\BlockInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
use Drupal\Core\Plugin\ContextAwarePluginBase;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\NestedArray;
......@@ -28,6 +29,8 @@
*/
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface {
use ContextAwarePluginAssignmentTrait;
/**
* The transliteration service.
*
......@@ -175,6 +178,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
'#return_value' => BlockInterface::BLOCK_LABEL_VISIBLE,
);
// Add context mapping UI form elements.
$contexts = $form_state->getTemporaryValue('gathered_contexts') ?: [];
$form['context_mapping'] = $this->addContextAssignmentElement($this, $contexts);
// Add plugin-specific settings for this block type.
$form += $this->blockForm($form, $form_state);
return $form;
......
......@@ -56,11 +56,12 @@ protected function addContextAssignmentElement(ContextAwarePluginInterface $plug
if (count($options) > 1) {
$assignments = $plugin->getContextMapping();
$element[$context_slot] = [
'#title' => $this->t('Select a @context value:', ['@context' => $context_slot]),
'#title' => $definition->getLabel() ?: $this->t('Select a @context value:', ['@context' => $context_slot]),
'#type' => 'select',
'#options' => $options,
'#required' => $definition->isRequired(),
'#default_value' => !empty($assignments[$context_slot]) ? $assignments[$context_slot] : '',
'#description' => $definition->getDescription(),
];
}
}
......
......@@ -335,6 +335,12 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
// Call the plugin submit handler.
$entity->getPlugin()->submitConfigurationForm($form, $settings);
$block = $entity->getPlugin();
// If this block is context-aware, set the context mapping.
if ($block instanceof ContextAwarePluginInterface && $block->getContextDefinitions()) {
$context_mapping = $settings->getValue('context_mapping', []);
$block->setContextMapping($context_mapping);
}
// Update the original form values.
$form_state->setValue('settings', $settings->getValues());
......
......@@ -16,6 +16,7 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Render\Element;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -139,6 +140,12 @@ protected static function buildPreRenderableBlock($entity, ModuleHandlerInterfac
$derivative_id = $plugin->getDerivativeId();
$configuration = $plugin->getConfiguration();
// Inject runtime contexts.
if ($plugin instanceof ContextAwarePluginInterface) {
$contexts = \Drupal::service('context.repository')->getRuntimeContexts($plugin->getContextMapping());
\Drupal::service('context.handler')->applyContextMapping($plugin, $contexts);
}
// Create the render array for the block as a whole.
// @see template_preprocess_block().
$build = [
......
......@@ -12,6 +12,7 @@
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Menu\LocalActionManagerInterface;
use Drupal\Core\Plugin\Context\LazyContextRepository;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -29,6 +30,13 @@ class BlockLibraryController extends ControllerBase {
*/
protected $blockManager;
/**
* The context repository.
*
* @var \Drupal\Core\Plugin\Context\LazyContextRepository
*/
protected $contextRepository;
/**
* The route match.
*
......@@ -48,15 +56,18 @@ class BlockLibraryController extends ControllerBase {
*
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The block manager.
* @param \Drupal\Core\Plugin\Context\LazyContextRepository $context_repository
* The context repository.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
* @param \Drupal\Core\Menu\LocalActionManagerInterface $local_action_manager
* The local action manager.
*/
public function __construct(BlockManagerInterface $block_manager, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager) {
public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager) {
$this->blockManager = $block_manager;
$this->routeMatch = $route_match;
$this->localActionManager = $local_action_manager;
$this->contextRepository = $context_repository;
}
/**
......@@ -65,6 +76,7 @@ public function __construct(BlockManagerInterface $block_manager, RouteMatchInte
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.block'),
$container->get('context.repository'),
$container->get('current_route_match'),
$container->get('plugin.manager.menu.local_action')
);
......@@ -95,7 +107,7 @@ public function listBlocks(Request $request, $theme) {
];
// Only add blocks which work without any available context.
$definitions = $this->blockManager->getDefinitionsForContexts();
$definitions = $this->blockManager->getDefinitionsForContexts($this->contextRepository->getAvailableContexts());
// Order by category, and then by admin label.
$definitions = $this->blockManager->getSortedDefinitions($definitions);
......
......@@ -79,6 +79,7 @@ public function testBlockInterface() {
'#default_value' => TRUE,
'#return_value' => 'visible',
),
'context_mapping' => array(),
'display_message' => array(
'#type' => 'textfield',
'#title' => t('Display message'),
......
......@@ -165,22 +165,58 @@ public function testCandidateBlockList() {
$this->assertTrue(!empty($elements), 'The test block appears in a custom category controlled by block_test_block_alter().');
}
/**
* Tests the behavior of unsatisfied context-aware blocks.
*/
public function testContextAwareUnsatisfiedBlocks() {
$arguments = array(
':category' => 'Block test',
':href' => 'admin/structure/block/add/test_context_aware_unsatisfied/classy',
':text' => 'Test context-aware unsatisfied block',
);
$this->drupalGet('admin/structure/block');
$this->clickLinkPartialName('Place block');
$elements = $this->xpath('//tr[.//td/div[text()=:text] and .//td[text()=:category] and .//td//a[contains(@href, :href)]]', $arguments);
$this->assertTrue(empty($elements), 'The context-aware test block does not appear.');
$definition = \Drupal::service('plugin.manager.block')->getDefinition('test_context_aware_unsatisfied');
$this->assertTrue(!empty($definition), 'The context-aware test block does not exist.');
}
/**
* Tests the behavior of context-aware blocks.
*/
public function testContextAwareBlocks() {
$expected_text = '<div id="test_context_aware--username">' . \Drupal::currentUser()->getUsername() . '</div>';
$this->drupalGet('');
$this->assertNoText('Test context-aware block');
$this->assertNoRaw($expected_text);
$block_url = 'admin/structure/block/add/test_context_aware/classy';
$arguments = array(
':ul_class' => 'block-list',
':li_class' => 'test-context-aware',
':href' => 'admin/structure/block/add/test_context_aware/classy',
':text' => 'Test context-aware block',
':title' => 'Test context-aware block',
':category' => 'Block test',
':href' => $block_url,
);
$pattern = '//tr[.//td/div[text()=:title] and .//td[text()=:category] and .//td//a[contains(@href, :href)]]';
$this->drupalGet('admin/structure/block');
$elements = $this->xpath('//details[@id="edit-category-block-test"]//ul[contains(@class, :ul_class)]/li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->assertTrue(empty($elements), 'The context-aware test block does not appear.');
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The context-aware test block appears.');
$definition = \Drupal::service('plugin.manager.block')->getDefinition('test_context_aware');
$this->assertTrue(!empty($definition), 'The context-aware test block exists.');
$edit = [
'region' => 'content',
'settings[context_mapping][user]' => '@block_test.multiple_static_context:user2',
];
$this->drupalPostForm($block_url, $edit, 'Save block');
$this->drupalGet('');
$this->assertText('Test context-aware block');
$this->assertRaw($expected_text);
}
/**
......
services:
block_test.multiple_static_context:
class: Drupal\block_test\ContextProvider\MultipleStaticContext
arguments: ['@current_user', '@entity.manager']
tags:
- { name: 'context_provider' }
<?php
/**
* @file
* Contains \Drupal\block_test\ContextProvider\MultipleStaticContext.
*/
namespace Drupal\block_test\ContextProvider;
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;
/**
* Sets multiple contexts for a static value.
*/
class MultipleStaticContext implements ContextProviderInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* Constructs a new MultipleStaticContext.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(AccountInterface $account, EntityManagerInterface $entity_manager) {
$this->account = $account;
$this->userStorage = $entity_manager->getStorage('user');
}
/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $unqualified_context_ids) {
$current_user = $this->userStorage->load($this->account->id());
$context1 = new Context(new ContextDefinition('entity:user', 'User 1'));
$context1->setContextValue($current_user);
$context2 = new Context(new ContextDefinition('entity:user', 'User 2'));
$context2->setContextValue($current_user);
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['user']);
$context1->addCacheableDependency($cacheability);
$context2->addCacheableDependency($cacheability);
return [
'user1' => $context1,
'user2' => $context2,
];
}
/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
return $this->getRuntimeContexts([]);
}
}
......@@ -29,6 +29,8 @@ public function build() {
/** @var $user \Drupal\user\UserInterface */
$user = $this->getContextValue('user');
return array(
'#prefix' => '<div id="' . $this->getPluginId() . '--username">',
'#suffix' => '</div>',
'#markup' => $user->getUsername(),
);
}
......
<?php
/**
* @file
* Contains \Drupal\block_test\Plugin\Block\TestContextAwareBlock.
*/
namespace Drupal\block_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a context-aware block.
*
* @Block(
* id = "test_context_aware_unsatisfied",
* admin_label = @Translation("Test context-aware unsatisfied block"),
* context = {
* "user" = @ContextDefinition("entity:foobar")
* }
* )
*/
class TestContextAwareUnsatisfiedBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return [
'#markup' => 'test',
];
}
}
......@@ -13,12 +13,15 @@
use Drupal\Core\Plugin\Context\ContextProviderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\Entity\Node;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Sets the current node as a context on node routes.
*/
class NodeRouteContext implements ContextProviderInterface {
use StringTranslationTrait;
/**
* The route match object.
*
......@@ -63,7 +66,7 @@ public function getRuntimeContexts(array $unqualified_context_ids) {
* {@inheritdoc}
*/
public function getAvailableContexts() {
$context = new Context(new ContextDefinition('entity:node'));
$context = new Context(new ContextDefinition('entity:node', $this->t('Node from URL')));
return ['node' => $context];
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment