diff --git a/core/lib/Drupal/Component/Plugin/Context/Context.php b/core/lib/Drupal/Component/Plugin/Context/Context.php index 9f4396b..b117a72 100644 --- a/core/lib/Drupal/Component/Plugin/Context/Context.php +++ b/core/lib/Drupal/Component/Plugin/Context/Context.php @@ -8,6 +8,8 @@ namespace Drupal\Component\Plugin\Context; use Drupal\Component\Plugin\Exception\ContextException; +use Symfony\Component\Validator\Constraints\Type; +use Symfony\Component\Validator\Validation; /** * A generic context class for wrapping data a plugin needs to operate. @@ -39,7 +41,6 @@ public function __construct(array $context_definition) { * Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextValue(). */ public function setContextValue($value) { - $value = $this->validate($value); $this->contextValue = $value; } @@ -65,22 +66,22 @@ public function getContextDefinition() { } /** - * Implements \Drupal\Component\Plugin\Context\ContextInterface::validate(). - * - * The default validation method only supports instance of checks between the - * contextDefintion and the contextValue. Other formats of context - * definitions can be supported through a subclass. + * Implements \Drupal\Component\Plugin\Context\ContextInterface::getConstraints(). */ - public function validate($value) { - // Check to make sure we have a class name, and that the passed context is - // an instance of that class name. - if (!empty($this->contextDefinition['class'])) { - if ($value instanceof $this->contextDefinition['class']) { - return $value; - } - throw new ContextException("The context passed was not an instance of {$this->contextDefinition['class']}."); + public function getConstraints() { + if (empty($this->contextDefinition['class'])) { + throw new ContextException("An error was encountered while trying to validate the context."); } - throw new ContextException("An error was encountered while trying to validate the context."); + return array(new Type($this->contextDefinition['class'])); + } + + /** + * Implements \Drupal\Component\Plugin\Context\ContextInterface::validate(). + */ + public function validate() { + $validator = Validation::createValidatorBuilder() + ->getValidator(); + return $validator->validateValue($this->getContextValue(), $this->getConstraints()); } } diff --git a/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php b/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php index 5c4373d..512605a 100644 --- a/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php +++ b/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php @@ -34,35 +34,38 @@ public function getContextValue(); /** * Sets the definition that the context must conform to. * - * @param mixed $contextDefinition + * @param array $contextDefinition * A defining characteristic representation of the context against which - * that context can be validated. This is typically a class name, but could - * be extended to support other validation notation. + * that context can be validated. This is typically an array having a + * class name set under the 'class' key, but it could be extended to support + * other notations. */ public function setContextDefinition(array $contextDefinition); /** * Gets the provided definition that the context must conform to. * - * @return mixed + * @return array * The defining characteristic representation of the context. */ public function getContextDefinition(); /** - * Validate the provided context value against the provided definition. - * - * @param mixed $value - * The context value that should be validated against the context - * definition. + * Gets a list of validation constraints. * - * @return mixed - * Returns the context value passed to it. If it fails validation, an - * exception will be thrown. + * @return array + * Array of constraints, each being an instance of + * \Symfony\Component\Validator\Constraint. + */ + public function getConstraints(); + + /** + * Validates the set context value. * - * @throws \Drupal\Component\Plugin\Exception\ContextException - * If validation fails. + * @return \Symfony\Component\Validator\ConstraintViolationListInterface + * A list of constraint violations. If the list is empty, validation + * succeeded. */ - public function validate($value); + public function validate(); } diff --git a/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php index a628f99..c6ffc3d 100644 --- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php +++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php @@ -9,6 +9,7 @@ use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Component\Plugin\Context\Context; +use Symfony\Component\Validator\ConstraintViolationList; /** * Base class for plugins that are context aware. @@ -81,11 +82,12 @@ public function getContext($key) { * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValues(). */ public function getContextValues() { - $contexts = array(); - foreach ($this->getContexts() as $key => $context) { - $contexts[$key] = $context->getContextValue(); + $definitions = (array) $this->getContextDefinitions(); + $values = array(); + foreach ($definitions as $key => $definition) { + $values[$key] = isset($this->context[$key]) ? $this->context[$key]->getContextValue() : NULL; } - return $contexts; + return $values; } /** @@ -103,7 +105,32 @@ public function setContextValue($key, $value) { $this->context[$key] = new Context($context_definition); $this->context[$key]->setContextValue($value); + // Verify the provided value validates. + $violations = $this->context[$key]->validate(); + if (count($violations) > 0) { + throw new PluginException("The provided context value does not pass validation."); + } return $this; } + /** + * Implements \Drupal\Core\Executable\ExecutableInterface::valdidate(). + */ + public function validate() { + $violations = new ConstraintViolationList(); + // @todo: Implement symfony validator API to let the validator traverse + // and set property paths accordingly. + + if ($definitions = $this->getContextDefinitions()) { + foreach ($definitions as $key => $definition) { + // Validate any set values. + if (isset($this->context[$key])) { + $violations->addAll($this->context[$key]->validate()); + } + // @todo: If no value is set, make sure any mapping is validated. + } + } + return $violations; + } + } diff --git a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php index 68d8012..086a25f 100644 --- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php +++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php @@ -29,7 +29,10 @@ public function getContextDefinitions(); * @param string $key * The name of the context in the plugin definition. * - * @return mixed + * @throws \Drupal\Component\Plugin\Exception\PluginException + * If the requested definition is not set. + * + * @return array * The definition against which the context value must validate. */ public function getContextDefinition($key); @@ -37,6 +40,9 @@ public function getContextDefinition($key); /** * Gets the defined contexts. * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * If contexts are defined but not set. + * * @return array * The set context objects. */ @@ -49,6 +55,9 @@ public function getContexts(); * The name of the context in the plugin configuration. This string is * usually identical to the representative string in the plugin definition. * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * If the requested context is not set. + * * @return \Drupal\Component\Plugin\Context\ContextInterface * The context object. */ @@ -58,7 +67,8 @@ public function getContext($key); * Gets the values for all defined contexts. * * @return array - * The set context object values. + * An array of set context values, keyed by context key. If a context is + * unset its value is returned as NULL. */ public function getContextValues(); @@ -69,6 +79,9 @@ public function getContextValues(); * The name of the context in the plugin configuration. This string is * usually identical to the representative string in the plugin definition. * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * If the requested context is not set. + * * @return mixed * The currently set context value. */ @@ -83,9 +96,21 @@ public function getContextValue($key); * The variable to set the context to. This should validate against the * provided context definition. * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * If the value does not pass validation. + * * @return \Drupal\Component\Plugin\ContextAwarePluginInterface. * A context aware plugin object for chaining. */ public function setContextValue($key, $value); + /** + * Validates the set values for the defined contexts. + * + * @return \Symfony\Component\Validator\ConstraintViolationListInterface + * A list of constraint violations. If the list is empty, validation + * succeeded. + */ + public function validate(); + } diff --git a/core/lib/Drupal/Core/Executable/ExecutableInterface.php b/core/lib/Drupal/Core/Executable/ExecutableInterface.php index 1e3ffdb..4f0605d 100644 --- a/core/lib/Drupal/Core/Executable/ExecutableInterface.php +++ b/core/lib/Drupal/Core/Executable/ExecutableInterface.php @@ -55,4 +55,40 @@ public function getExecutableManager(); */ public function execute(); + /** + * Constructs the executable's configuration form. + * + * This method allows base implementations to add a generic configuration + * form for the plugin. + * + * @param array $form + * The form definition array for the configuration form. + * @param array $form_state + * An array containing the current state of the configuration form. + * + * @return array $form + * The renderable form array representing the entire configuration form. + * + * @see \Drupal\Core\Executable\ExecutableInterface::extractFormValues() + * @see \Drupal\Core\Executable\ExecutableInterface::validate() + */ + public function form($form, &$form_state); + + /** + * Extracts configuration from submitted form values. + * + * @param array $form + * The form definition array for the configuration form. + * @param array $form_state + * An array containing the current state of the configuration form. + * + * @see \Drupal\Core\Executable\ExecutableInterface::form() + * @see \Drupal\Core\Executable\ExecutableInterface::validate() + */ + public function extractFormValues($form, &$form_state); + + /** + * Provides a human readable summary of the executable's configuration. + */ + public function summary(); } diff --git a/core/lib/Drupal/Core/Executable/ExecutablePluginBase.php b/core/lib/Drupal/Core/Executable/ExecutablePluginBase.php index 8a089b3..2dba995 100644 --- a/core/lib/Drupal/Core/Executable/ExecutablePluginBase.php +++ b/core/lib/Drupal/Core/Executable/ExecutablePluginBase.php @@ -99,5 +99,20 @@ public function setConfig($key, $value) { return $this; } + /** + * Implements \Drupal\Core\Executable\ExecutableInterface::form(). + */ + public function form($form, &$form_state) { + // @todo add context definition parsing and provide select elements for + // fulfilling contexts from some source. + return $form; + } + + /** + * Implements \Drupal\Core\Executable\ExecutableInterface::extractFormValues(). + */ + public function extractFormValues($form, &$form_state) { + // Nothing to extract by default. + } } diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php index 9367401..3eb195d 100644 --- a/core/lib/Drupal/Core/Plugin/Context/Context.php +++ b/core/lib/Drupal/Core/Plugin/Context/Context.php @@ -8,9 +8,11 @@ namespace Drupal\Core\Plugin\Context; use Drupal\Component\Plugin\Context\Context as ComponentContext; -use Drupal\Component\Plugin\Exception\ContextException; -use Drupal\Core\TypedData\TypedDataManager; +use Drupal\Core\TypedData\ComplexDataInterface; +use Drupal\Core\TypedData\ListInterface; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\Validation\DrupalTranslator; +use Symfony\Component\Validator\Validation; /** * A Drupal specific context wrapper class. @@ -26,19 +28,32 @@ class Context extends ComponentContext { */ public function getContextValue() { $typed_value = parent::getContextValue(); - // If the data is of a primitive type, directly return the plain value. - // That way, e.g. a string will be return as plain PHP string. - if ($typed_value instanceof \Drupal\Core\TypedData\TypedDataInterface) { - $type_definition = typed_data()->getDefinition($typed_value->getType()); - // @todo We won't need the getType == entity check once #1868004 lands. - if (!empty($type_definition['primitive type']) || $typed_value->getType() == 'entity') { - return $typed_value->getValue(); - } + // If the typed data is complex, pass it on as typed data. Else pass on its + // plain value, such that e.g. a string will be directly returned as PHP + // string. + $is_complex = $typed_value instanceof ComplexDataInterface; + if (!$is_complex && $typed_value instanceof ListInterface) { + $is_complex = $typed_value[0] instanceof ComplexDataInterface; + } + // @todo We won't need the getType == entity check once #1868004 lands. + if ($typed_value instanceof TypedDataInterface && (!$is_complex || $typed_value->getType() == 'entity')) { + return $typed_value->getValue(); } return $typed_value; } /** + * Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextValue(). + */ + public function setContextValue($value) { + // Make sure the value set is a typed data object. + if (!empty($this->contextDefinition['type']) && !$value instanceof TypedDataInterface) { + $value = typed_data()->create($this->contextDefinition, $value); + } + parent::setContextValue($value); + } + + /** * Gets the context value as typed data object. * * parent::getContextValue() does not do all the processing required to @@ -55,21 +70,31 @@ public function getTypedContext() { } /** - * Override for \Drupal\Component\Plugin\Context\Context::validate(). + * Overrides \Drupal\Component\Plugin\Context\Context::getConstraints(). */ - public function validate($value) { + public function getConstraints() { if (!empty($this->contextDefinition['type'])) { - $typed_data_manager = new TypedDataManager(); - $typed_data = $typed_data_manager->create($this->contextDefinition, $value); - // If we do have a typed data definition, validate it and return the - // typed data instance instead. - $violations = $typed_data->validate(); - if (count($violations) == 0) { - return $typed_data; - } - throw new ContextException("The context passed could not be validated through typed data."); + // If we do have typed data, leverage it for getting constraints. + return $this->getTypedContext()->getConstraints(); } - return parent::validate($value); + return parent::getConstraints(); } + /** + * Overrides \Drupal\Component\Plugin\Context\Context::getConstraints(). + */ + public function validate() { + $validator = Validation::createValidatorBuilder() + ->setTranslator(new DrupalTranslator()) + ->getValidator(); + + // @todo We won't need to special case "entity" here once #1868004 lands. + if (!empty($this->contextDefinition['type']) && $this->contextDefinition['type'] == 'entity') { + $value = $this->getTypedContext(); + } + else { + $value = $this->getContextValue(); + } + return $validator->validateValue($value, $this->getConstraints()); + } } diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php index 028befd..e4daeb1 100644 --- a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Plugin; use Drupal\Component\Plugin\ContextAwarePluginBase as PluginBase; +use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Core\Plugin\Context\Context; /** @@ -27,6 +28,10 @@ public function setContextValue($key, $value) { $this->context[$key] = new Context($context_definition); $this->context[$key]->setContextValue($value); + // Verify the provided value validates. + if ($this->context[$key]->validate()->count() > 0) { + throw new PluginException("The provided context value does not pass validation."); + } return $this; } diff --git a/core/lib/Drupal/Core/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php b/core/lib/Drupal/Core/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php index 21b2ac2..1e1aba4 100644 --- a/core/lib/Drupal/Core/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php +++ b/core/lib/Drupal/Core/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php @@ -22,7 +22,7 @@ public function validate($typed_data, Constraint $constraint) { $entity = isset($typed_data) ? $typed_data->getValue() : FALSE; if (!empty($entity) && $entity->entityType() != $constraint->type) { - $this->context->addViolation($constraint->message, array('%type', $constraint->type)); + $this->context->addViolation($constraint->message, array('%type' => $constraint->type)); } } } diff --git a/core/lib/Drupal/Core/TypedData/Type/Any.php b/core/lib/Drupal/Core/TypedData/Type/Any.php new file mode 100644 index 0000000..21afa99 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Type/Any.php @@ -0,0 +1,27 @@ +configuration['negate'] = $boolean; @@ -23,14 +23,14 @@ public function setNegate($boolean) { } /** - * Implements Drupal\condition\Plugin\ConditionInterface::isNegated(). + * Implements \Drupal\condition\Plugin\ConditionInterface::isNegated(). */ public function isNegated() { return !empty($this->configuration['negate']); } /** - * Implements Drupal\condition\Plugin\ConditionInterface::form(). + * Implements \Drupal\condition\Plugin\ConditionInterface::form(). */ public function form($form, &$form_state) { // @todo add context definition parsing and provide select elements for @@ -44,9 +44,9 @@ public function form($form, &$form_state) { } /** - * Implements Drupal\condition\ConditionInterface::submit(). + * Overrides ExecutablePluginBase::extractFormValues(). */ - public function submit($form, &$form_state) { + public function extractFormValues($form, &$form_state) { $this->configuration['negate'] = $form_state['values']['negate']; } diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Condition/NodeType.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Condition/NodeType.php index 828c524..555f5c1 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Core/Condition/NodeType.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Condition/NodeType.php @@ -24,11 +24,16 @@ * "constraints" = { * "EntityType" = "node" * } + * }, + * "bundles" = { + * "type" = "any" * } * } * ) + * + * @todo: Fix validation of multiple values and improve the bundles context + * definition to be more specific. See http://drupal.org/node/1913334. */ - class NodeType extends ConditionPluginBase { /** @@ -36,6 +41,7 @@ class NodeType extends ConditionPluginBase { */ public function form($form, &$form_state) { $form = parent::form($form, $form_state); + $values = $this->getContextValues(); $options = array(); foreach (node_type_get_types() as $type) { $options[$type->type] = $type->name; @@ -44,42 +50,31 @@ public function form($form, &$form_state) { '#type' => 'checkboxes', '#options' => $options, '#required' => TRUE, - '#default_value' => isset($this->configuration['bundles']) ? $this->configuration['bundles'] : array(), + '#default_value' => $values['bundles'], ); return $form; } /** - * Implements \Drupal\condition\ConditionInterface::validate(). - */ - public function validate($form, &$form_state) { - foreach ($form_state['values']['bundles'] as $bundle) { - if (!in_array($bundle, array_keys(node_type_get_types()))) { - form_set_error('bundles', t('You have chosen an invalid node bundle, please check your selection and try again.')); - } - } - } - - /** - * Implements \Drupal\condition\ConditionInterface::submit(). + * Implements \Drupal\condition\ConditionInterface::extractFormValues(). */ - public function submit($form, &$form_state) { - $this->configuration['bundles'] = $form_state['values']['bundles']; - parent::submit($form, $form_state); + public function extractFormValues($form, &$form_state) { + // @todo: How to set the value without triggering validation? + $this->setContextValue($form_state['values']['bundles']); + parent::extractFormValues($form, $form_state); } /** * Implements \Drupal\condition\ConditionInterface::summary(). */ public function summary() { - if (count($this->configuration['bundles']) > 1) { - $bundles = $this->configuration['bundles']; + $bundles = $this->getContextValue('bundles'); + if (count($bundles) > 1) { $last = array_pop($bundles); $bundles = implode(', ', $bundles); return t('The node bundle is @bundles or @last', array('@bundles' => $bundles, '@last' => $last)); } - $bundle = $this->configuration['bundles'][0]; - return t('The node bundle is @bundle', array('@bundle' => $bundle)); + return t('The node bundle is @bundle', array('@bundle' => reset($bundles))); } /** @@ -87,7 +82,7 @@ public function summary() { */ public function evaluate() { $node = $this->getContextValue('node'); - return in_array($node->type, $this->configuration['bundles']); + return in_array($node->type, $this->getContextValue('bundles')); } } diff --git a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php index e6592b7..ca94276 100644 --- a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php @@ -16,7 +16,7 @@ */ class NodeConditionTest extends DrupalUnitTestBase { - public static $modules = array('system', 'node', 'field', 'condition'); + public static $modules = array('system', 'node', 'entity_test', 'field', 'condition'); public static function getInfo() { return array( @@ -33,6 +33,7 @@ protected function setUp() { $this->installSchema('node', 'node_revision'); $this->installSchema('field', 'field_config'); $this->installSchema('field', 'field_config_instance'); + $this->installSchema('entity_test', 'entity_test'); } /** @@ -53,20 +54,20 @@ function testConditions() { // Grab the node type condition and configure it to check against node type // of 'article' and set the context to the page type node. $condition = $manager->createInstance('node_type') - ->setConfig('bundles', array('article')) + ->setContextValue('bundles', array('article')) ->setContextValue('node', $page); $this->assertFalse($condition->execute(), 'Page type nodes fail node type checks for articles.'); // Check for the proper summary. $this->assertEqual('The node bundle is article', $condition->summary()); // Set the node type check to page. - $condition->setConfig('bundles', array('page')); + $condition->setContextValue('bundles', array('page')); $this->assertTrue($condition->execute(), 'Page type nodes pass node type checks for pages'); // Check for the proper summary. $this->assertEqual('The node bundle is page', $condition->summary()); // Set the node type check to page or article. - $condition->setConfig('bundles', array('page', 'article')); + $condition->setContextValue('bundles', array('page', 'article')); $this->assertTrue($condition->execute(), 'Page type nodes pass node type checks for pages or articles'); // Check for the proper summary. $this->assertEqual('The node bundle is page or article', $condition->summary()); @@ -80,7 +81,16 @@ function testConditions() { $this->assertFalse($condition->execute(), 'Test type nodes pass node type checks for pages or articles'); // Check a greater than 2 bundles summary scenario. - $condition->setConfig('bundles', array('page', 'article', 'test')); + $condition->setContextValue('bundles', array('page', 'article', 'test')); $this->assertEqual('The node bundle is page, article or test', $condition->summary()); + + // Test validation. + $this->assertEqual($condition->validate()->count(), 0, 'Validation passes.'); + $entity = entity_create('entity_test', array('name' => $this->randomString())); + $entity->save(); + $condition->getContext('node')->setContextValue($entity); + $violations = $condition->validate(); + $this->assertEqual($violations->count(), 1, 'Validation fails if another entity is passed as node.'); + $this->assertEqual($violations[0]->getMessage(), t('The entity must be of type %type.', array('%type' => 'node'))); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php index 888f1e6..c22f222 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php @@ -93,8 +93,8 @@ function testContext() { $plugin->setContextValue('user', $node); $this->fail('The node context should fail validation for a user context.'); } - catch (ContextException $e) { - $this->assertEqual($e->getMessage(), 'The context passed was not an instance of Drupal\user\Plugin\Core\Entity\User.'); + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The provided context value does not pass validation.'); } // Set an appropriate context value appropriately and check to make sure @@ -157,13 +157,8 @@ function testContext() { } // With no contexts set, try to get the context values. - try { - $complex_plugin->getContextValues(); - $this->fail('There should not be any contexts set yet.'); - } - catch (PluginException $e) { - $this->assertEqual($e->getMessage(), 'There are no set contexts.'); - } + $values = $complex_plugin->getContextValues(); + $this->assertIdentical(array_filter($values), array(), 'There are no set contexts.'); // Set the user context value. $complex_plugin->setContextValue('user', $user); @@ -178,13 +173,9 @@ function testContext() { } // With only the user context set, try to get the context values. - try { - $complex_plugin->getContextValues(); - $this->fail('The node context should not yet be set.'); - } - catch (PluginException $e) { - $this->assertEqual($e->getMessage(), 'The node context is not yet set.'); - } + $values = $complex_plugin->getContextValues(); + $this->assertNull($values['node'], 'The node context is not yet set.'); + $this->assertNotNull($values['user'], 'The user context is set'); $complex_plugin->setContextValue('node', $node); $context_wrappers = $complex_plugin->getContexts(); diff --git a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php index a401183..b3e8117 100644 --- a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php @@ -197,6 +197,25 @@ public function testGetAndSet() { $this->assertEqual($typed_data->validate()->count(), 0); $typed_data->setValue('invalid'); $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.'); + + // Any type. + $value = array('foo'); + $typed_data = $this->createTypedData(array('type' => 'any'), $value); + $this->assertIdentical($typed_data->getValue(), $value, 'Any value was fetched.'); + $new_value = 'test@example.com'; + $typed_data->setValue($new_value); + $this->assertIdentical($typed_data->getValue(), $new_value, 'Any value was changed.'); + $this->assertTrue(is_string($typed_data->getString()), 'Any value was converted to string'); + $this->assertEqual($typed_data->validate()->count(), 0); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getValue(), 'Any wrapper is null-able.'); + $this->assertEqual($typed_data->validate()->count(), 0); + // We cannot test invalid values as everything is valid for the any type, + // but make sure an array or object value passes validation also. + $typed_data->setValue(array('entry')); + $this->assertEqual($typed_data->validate()->count(), 0); + $typed_data->setValue((object) array('entry')); + $this->assertEqual($typed_data->validate()->count(), 0); } /** diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 223af2e..c51f234 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2231,6 +2231,10 @@ function system_data_type_info() { 'class' => '\Drupal\Core\TypedData\Type\Binary', 'primitive type' => Primitive::BINARY, ), + 'any' => array( + 'label' => t('Any'), + 'class' => '\Drupal\Core\TypedData\Type\Any', + ), 'language' => array( 'label' => t('Language'), 'description' => t('A language object.'),