Commit 91b4ae58 authored by alexpott's avatar alexpott

Issue #2508884 by EclipseGc, tim.plunkett, dsnopek, japerry, sravya_12,...

Issue #2508884 by EclipseGc, tim.plunkett, dsnopek, japerry, sravya_12, swathin, Wim Leers, Fabianx, dawehner, yched: Make contexts immutable
parent c7de07a6
......@@ -31,20 +31,16 @@ class Context implements ContextInterface {
protected $contextDefinition;
/**
* Sets the contextDefinition for us without needing to call the setter.
* Create a context object.
*
* @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition
* The context definition.
* @param mixed|null $context_value
* The value of the context.
*/
public function __construct(ContextDefinitionInterface $context_definition) {
public function __construct(ContextDefinitionInterface $context_definition, $context_value = NULL) {
$this->contextDefinition = $context_definition;
}
/**
* Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextValue().
*/
public function setContextValue($value) {
$this->contextValue = $value;
$this->contextValue = $context_value;
}
/**
......@@ -74,13 +70,6 @@ public function hasContextValue() {
return (bool) $this->contextValue || (bool) $this->getContextDefinition()->getDefaultValue();
}
/**
* {@inheritdoc}
*/
public function setContextDefinition(ContextDefinitionInterface $context_definition) {
$this->contextDefinition = $context_definition;
}
/**
* Implements \Drupal\Component\Plugin\Context\ContextInterface::getContextDefinition().
*/
......
......@@ -12,16 +12,6 @@
*/
interface ContextInterface {
/**
* Sets the context value.
*
* @param mixed $value
* The value of this context, matching the context definition.
*
* @see \Drupal\Component\Plugin\Context\ContextInterface::setContextDefinition().
*/
public function setContextValue($value);
/**
* Gets the context value.
*
......@@ -38,15 +28,6 @@ public function getContextValue();
*/
public function hasContextValue();
/**
* Sets the definition that the context must conform to.
*
* @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition
* A defining characteristic representation of the context against which
* that context can be validated.
*/
public function setContextDefinition(ContextDefinitionInterface $context_definition);
/**
* Gets the provided definition that the context must conform to.
*
......
......@@ -41,17 +41,30 @@ abstract class ContextAwarePluginBase extends PluginBase implements ContextAware
* The plugin implementation definition.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
$context = array();
if (isset($configuration['context'])) {
$context = $configuration['context'];
$context_configuration = isset($configuration['context']) ? $configuration['context'] : [];
unset($configuration['context']);
}
parent::__construct($configuration, $plugin_id, $plugin_definition);
foreach ($context as $key => $value) {
$this->contexts = $this->createContextFromConfiguration($context_configuration);
}
/**
* Creates context objects from any context mappings in configuration.
*
* @param array $context_configuration
* An associative array of context names and values.
*
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
* An array of context objects.
*/
protected function createContextFromConfiguration(array $context_configuration) {
$contexts = [];
foreach ($context_configuration as $key => $value) {
$context_definition = $this->getContextDefinition($key);
$this->context[$key] = new Context($context_definition);
$this->context[$key]->setContextValue($value);
$contexts[$key] = new Context($context_definition, $value);
}
return $contexts;
}
/**
......@@ -124,7 +137,7 @@ public function getContextValue($name) {
* {@inheritdoc}
*/
public function setContextValue($name, $value) {
$this->getContext($name)->setContextValue($value);
$this->context[$name] = new Context($this->getContextDefinition($name), $value);
return $this;
}
......
......@@ -57,8 +57,7 @@ public function getRuntimeContexts(array $unqualified_context_ids) {
$result = [];
foreach ($language_types as $type_key) {
if (isset($info[$type_key]['name'])) {
$context = new Context(new ContextDefinition('language', $info[$type_key]['name']));
$context->setContextValue($this->languageManager->getCurrentLanguage($type_key));
$context = new Context(new ContextDefinition('language', $info[$type_key]['name']), $this->languageManager->getCurrentLanguage($type_key));
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['languages:' . $type_key]);
......
......@@ -43,11 +43,19 @@ class Context extends ComponentContext implements ContextInterface {
protected $cacheabilityMetadata;
/**
* {@inheritdoc}
* Create a context object.
*
* @param \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context_definition
* The context definition.
* @param mixed $context_value|NULL
* The context value object.
*/
public function __construct(ContextDefinitionInterface $context_definition) {
parent::__construct($context_definition);
public function __construct(ContextDefinitionInterface $context_definition, $context_value = NULL) {
parent::__construct($context_definition, NULL);
$this->cacheabilityMetadata = new CacheableMetadata();
if (!is_null($context_value)) {
$this->setContextValue($context_value);
}
}
/**
......@@ -80,19 +88,22 @@ public function hasContextValue() {
}
/**
* {@inheritdoc}
* Sets the context value.
*
* @param mixed $value
* The value of this context, matching the context definition.
*/
public function setContextValue($value) {
protected function setContextValue($value) {
// Add the value as a cacheable dependency only if implements the interface
// to prevent it from disabling caching with a max-age 0.
if ($value instanceof CacheableDependencyInterface) {
$this->addCacheableDependency($value);
}
if ($value instanceof TypedDataInterface) {
return $this->setContextData($value);
$this->contextData = $value;
}
else {
return $this->setContextData($this->getTypedDataManager()->create($this->contextDefinition->getDataDefinition(), $value));
$this->contextData = $this->getTypedDataManager()->create($this->contextDefinition->getDataDefinition(), $value);
}
}
......@@ -119,13 +130,6 @@ public function getContextData() {
return $this->contextData;
}
/**
* {@inheritdoc}
*/
public function setContextData(TypedDataInterface $data) {
$this->contextData = $data;
return $this;
}
/**
* {@inheritdoc}
......@@ -170,4 +174,16 @@ public function getCacheMaxAge() {
return $this->cacheabilityMetadata->getCacheMaxAge();
}
/**
* {@inheritdoc}
*/
public static function createFromContext(ContextInterface $old_context, $value) {
$context = new static($old_context->getContextDefinition(), $value);
$context->addCacheableDependency($old_context);
if (method_exists($old_context, 'getTypedDataManager')) {
$context->setTypedDataManager($old_context->getTypedDataManager());
}
return $context;
}
}
......@@ -72,6 +72,7 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface
* {@inheritdoc}
*/
public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) {
/** @var $contexts \Drupal\Core\Plugin\Context\ContextInterface[] */
$mappings += $plugin->getContextMapping();
// Loop through each of the expected contexts.
......@@ -94,7 +95,7 @@ public function applyContextMapping(ContextAwarePluginInterface $plugin, $contex
// Pass the value to the plugin if there is one.
if ($contexts[$context_id]->hasContextValue()) {
$plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextValue());
$plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextData());
}
elseif ($plugin_context_definition->isRequired()) {
// Collect required contexts that exist but are missing a value.
......
......@@ -68,7 +68,7 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface
*
* @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
* A plugin about to be evaluated.
* @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
* @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
* An array of contexts to set on the plugin. They will only be set if they
* match the plugin's context definitions.
* @param array $mappings
......
......@@ -9,7 +9,6 @@
use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Interface for context.
......@@ -17,21 +16,18 @@
interface ContextInterface extends ComponentContextInterface, CacheableDependencyInterface {
/**
* Gets the context value as typed data object.
* {@inheritdoc}
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface
*/
public function getContextData();
public function getContextDefinition();
/**
* Sets the context value as typed data object.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $data
* The context value as a typed data object.
* Gets the context value as typed data object.
*
* @return $this
* @return \Drupal\Core\TypedData\TypedDataInterface
*/
public function setContextData(TypedDataInterface $data);
public function getContextData();
/**
* Adds a dependency on an object: merges its cacheability metadata.
......@@ -51,4 +47,17 @@ public function setContextData(TypedDataInterface $data);
*/
public function addCacheableDependency($dependency);
/**
* Creates a new context with a different value.
*
* @param \Drupal\Core\Plugin\Context\ContextInterface $old_context
* The context object used to create a new object. Cacheability metadata
* will be copied over.
* @param mixed $value
* The value of the new context object.
*
* @return static
*/
public static function createFromContext(ContextInterface $old_context, $value);
}
......@@ -32,8 +32,7 @@ interface ContextProviderInterface {
* $node = ...
*
* // Set that specific node as the value of the 'node' context.
* $context = new Context(new ContextDefinition('entity:node'));
* $context->setContextValue($node);
* $context = new Context(new ContextDefinition('entity:node'), $node);
* return ['node' => $context];
* @endcode
*
......
......@@ -30,6 +30,25 @@ abstract class ContextAwarePluginBase extends ComponentContextAwarePluginBase im
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Plugin\Context\ContextInterface[]
*/
protected function createContextFromConfiguration(array $context_configuration) {
// This method is overridden so that it will use
// \Drupal\Core\Plugin\Context\Context instead.
$contexts = [];
foreach ($context_configuration as $key => $value) {
$context_definition = $this->getContextDefinition($key);
$contexts[$key] = new Context($context_definition, $value);
}
return $contexts;
}
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Plugin\Context\ContextInterface
* The context object.
*
* This code is identical to the Component in order to pick up a different
* Context class.
*/
......@@ -52,6 +71,14 @@ public function setContext($name, ComponentContextInterface $context) {
parent::setContext($name, $context);
}
/**
* {@inheritdoc}
*/
public function setContextValue($name, $value) {
$this->context[$name] = Context::createFromContext($this->getContext($name), $value);
return $this;
}
/**
* {@inheritdoc}
*/
......@@ -84,6 +111,15 @@ public function getContextDefinitions() {
return parent::getContextDefinitions();
}
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface
*/
public function getContextDefinition($name) {
return parent::getContextDefinition($name);
}
/**
* Wraps the context handler.
*
......
......@@ -52,11 +52,9 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e
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);
$context1 = new Context(new ContextDefinition('entity:user', 'User 1'), $current_user);
$context2 = new Context(new ContextDefinition('entity:user', 'User 2'));
$context2->setContextValue($current_user);
$context2 = new Context(new ContextDefinition('entity:user', 'User 2'), $current_user);
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['user']);
......
......@@ -44,18 +44,22 @@ public function __construct(RouteMatchInterface $route_match) {
*/
public function getRuntimeContexts(array $unqualified_context_ids) {
$result = [];
$context = new Context(new ContextDefinition('entity:node', NULL, FALSE));
$context_definition = new ContextDefinition('entity:node', NULL, FALSE);
$value = NULL;
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);
$value = $node;
}
}
elseif ($this->routeMatch->getRouteName() == 'node.add') {
$node_type = $this->routeMatch->getParameter('node_type');
$context->setContextValue(Node::create(array('type' => $node_type->id())));
$value = Node::create(array('type' => $node_type->id()));
}
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['route']);
$context = new Context($context_definition, $value);
$context->addCacheableDependency($cacheability);
$result['node'] = $context;
......
......@@ -9,6 +9,7 @@
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\node\Entity\NodeType;
use Drupal\plugin_test\Plugin\MockBlockManager;
use Drupal\simpletest\KernelTestBase;
......@@ -26,6 +27,10 @@ class ContextPluginTest extends KernelTestBase {
*/
function testContext() {
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installEntitySchema('node_type');
$type = NodeType::create(['type' => 'page', 'name' => 'Page']);
$type->save();
$name = $this->randomMachineName();
$manager = new MockBlockManager();
......
......@@ -72,7 +72,7 @@ protected function doTestIdenticalUser() {
'user2' => 'anonymous',
]);
$definition = new ContextDefinition('entity:user');
$contexts['anonymous'] = (new Context($definition))->setContextValue($this->anonymous);
$contexts['anonymous'] = new Context($definition, $this->anonymous);
\Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
$this->assertTrue($condition->execute());
}
......@@ -89,8 +89,8 @@ protected function doTestDifferentUser() {
'user2' => 'authenticated',
]);
$definition = new ContextDefinition('entity:user');
$contexts['anonymous'] = (new Context($definition))->setContextValue($this->anonymous);
$contexts['authenticated'] = (new Context($definition))->setContextValue($this->authenticated);
$contexts['anonymous'] = new Context($definition, $this->anonymous);
$contexts['authenticated'] = new Context($definition, $this->authenticated);
\Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
$this->assertFalse($condition->execute());
}
......
......@@ -10,6 +10,7 @@
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\KernelTestBase;
/**
......@@ -58,6 +59,7 @@ protected function testContextNoValue() {
* Tests with both contexts mapped to the same user.
*/
protected function testContextAvailable() {
NodeType::create(['type' => 'example', 'name' => 'Example'])->save();
/** @var \Drupal\Core\Condition\ConditionPluginBase $condition */
$condition = \Drupal::service('plugin.manager.condition')
->createInstance('condition_test_optional_context')
......@@ -66,7 +68,7 @@ protected function testContextAvailable() {
]);
$definition = new ContextDefinition('entity:node');
$node = Node::create(['type' => 'example']);
$contexts['node'] = (new Context($definition))->setContextValue($node);
$contexts['node'] = new Context($definition, $node);
\Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
$this->assertFalse($condition->execute());
}
......
......@@ -29,8 +29,7 @@ public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $eve
$event->setPluginConfiguration(['required_configuration' => 'A very important, required value.']);
$event->addCacheTags(['custom_cache_tag']);
$context = new Context(new ContextDefinition('string', NULL, TRUE));
$context->setContextValue('Explicitly passed in context.');
$context = new Context(new ContextDefinition('string', NULL, TRUE), 'Explicitly passed in context.');
$event->setContexts(['context' => $context]);
}
......
......@@ -55,8 +55,7 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e
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')));
$context->setContextValue($current_user);
$context = new Context(new ContextDefinition('entity:user', $this->t('Current user')), $current_user);
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['user']);
$context->addCacheableDependency($cacheability);
......
......@@ -83,10 +83,8 @@ public function testSetContextValueTypedData() {
->setMethods(array('getDefaultValue', 'getDataDefinition'))
->getMockForAbstractClass();
$context = new Context($this->contextDefinition);
$context->setTypedDataManager($this->typedDataManager);
$typed_data = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$context->setContextValue($typed_data);
$context = new Context($this->contextDefinition, $typed_data);
$this->assertSame($typed_data, $context->getContextData());
}
......@@ -120,7 +118,7 @@ public function testSetContextValueCacheableDependency() {
->method('getCacheMaxAge')
->willReturn(60);
$context->setContextValue($cacheable_dependency);
$context = Context::createFromContext($context, $cacheable_dependency);
$this->assertSame($cacheable_dependency, $context->getContextData());
$this->assertEquals(['node:1'], $context->getCacheTags());
$this->assertEquals(['route'], $context->getCacheContexts());
......
......@@ -50,11 +50,10 @@ public function testGetContextValue() {
\Drupal::setContainer($container);
$definition = new ContextDefinition('any');
$context = new Context($definition);
$data_definition = DataDefinition::create('string');
$this->typedData = new StringData($data_definition);
$this->typedData->setValue('example string');
$context->setContextData($this->typedData);
$context = new Context($definition, $this->typedData);
$value = $context->getContextValue();
$this->assertSame($value, $this->typedData->getValue());
}
......
......@@ -11,6 +11,8 @@
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\ContextHandler;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\Plugin\DataType\StringData;
use Drupal\Tests\UnitTestCase;
/**
......@@ -229,16 +231,20 @@ public function providerTestFilterPluginDefinitionsByContexts() {
* @covers ::applyContextMapping
*/
public function testApplyContextMapping() {
$context_hit_data = StringData::createInstance(DataDefinition::create('string'));
$context_hit_data->setValue('foo');
$context_hit = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
$context_hit->expects($this->atLeastOnce())
->method('getContextValue')
->will($this->returnValue(array('foo')));
->method('getContextData')
->will($this->returnValue($context_hit_data));
$context_miss_data = StringData::createInstance(DataDefinition::create('string'));
$context_miss_data->setValue('bar');
$context_hit->expects($this->atLeastOnce())
->method('hasContextValue')
->willReturn(TRUE);
$context_miss = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
$context_miss->expects($this->never())
->method('getContextValue');
->method('getContextData');
$contexts = array(
'hit' => $context_hit,
......@@ -256,7 +262,7 @@ public function testApplyContextMapping() {
->will($this->returnValue(array('hit' => $context_definition)));
$plugin->expects($this->once())
->method('setContextValue')
->with('hit', array('foo'));
->with('hit', $context_hit_data);
// Make sure that the cacheability metadata is passed to the plugin context.
$plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
......@@ -416,10 +422,12 @@ public function testApplyContextMappingNoValueNonRequired() {
* @covers ::applyContextMapping
*/
public function testApplyContextMappingConfigurableAssigned() {
$context_data = StringData::createInstance(DataDefinition::create('string'));
$context_data->setValue('foo');
$context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
$context->expects($this->atLeastOnce())
->method('getContextValue')
->will($this->returnValue(array('foo')));
->method('getContextData')
->will($this->returnValue($context_data));
$context->expects($this->atLeastOnce())
->method('hasContextValue')
->willReturn(TRUE);
......@@ -439,7 +447,7 @@ public function testApplyContextMappingConfigurableAssigned() {
->will($this->returnValue(array('hit' => $context_definition)));
$plugin->expects($this->once())
->method('setContextValue')
->with('hit', array('foo'));
->with('hit', $context_data);
// Make sure that the cacheability metadata is passed to the plugin context.
$plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
......
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