Skip to content
Snippets Groups Projects
Commit 89686bf4 authored by Angie Byron's avatar Angie Byron
Browse files

Issue #1743686 by EclipseGc, fago, amateescu, damiankloip: Added Condition Plugin System.

parent b0e08e42
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
Showing
with 827 additions and 125 deletions
......@@ -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;
public function getConstraints() {
if (empty($this->contextDefinition['class'])) {
throw new ContextException("An error was encountered while trying to validate the context.");
}
throw new ContextException("The context passed was not an instance of {$this->contextDefinition['class']}.");
return array(new Type($this->contextDefinition['class']));
}
throw new ContextException("An error was encountered while trying to validate the context.");
/**
* Implements \Drupal\Component\Plugin\Context\ContextInterface::validate().
*/
public function validate() {
$validator = Validation::createValidatorBuilder()
->getValidator();
return $validator->validateValue($this->getContextValue(), $this->getConstraints());
}
}
......@@ -7,8 +7,6 @@
namespace Drupal\Component\Plugin\Context;
use Drupal\Component\Plugin\Exception\ContextException;
/**
* A generic context interface for wrapping data a plugin needs to operate.
*/
......@@ -18,8 +16,9 @@ interface ContextInterface {
* Sets the context value.
*
* @param mixed $value
* The value of this context, generally an object based upon the class
* matching the definition passed to setContextDefinition().
* The value of this context, matching the context definition.
*
* @see \Drupal\Component\Plugin\Context\ContextInterface::setContextDefinition().
*/
public function setContextValue($value);
......@@ -27,42 +26,45 @@ public function setContextValue($value);
* Gets the context value.
*
* @return mixed
* The currently set context value within this class.
* The currently set context value, or NULL if it is not set.
*/
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();
}
......@@ -9,6 +9,8 @@
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Context\Context;
use Symfony\Component\Validator\ConstraintViolationList;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
/**
* Base class for plugins that are context aware.
......@@ -22,23 +24,49 @@ abstract class ContextAwarePluginBase extends PluginBase implements ContextAware
*/
protected $context;
/**
* Overrides \Drupal\Component\Plugin\PluginBase::__construct().
*
* Overrides the construction of context aware plugins to allow for
* unvalidated constructor based injection of contexts.
*
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
*/
public function __construct(array $configuration, $plugin_id, DiscoveryInterface $discovery) {
$context = array();
if (isset($configuration['context'])) {
$context = $configuration['context'];
unset($configuration['context']);
}
parent::__construct($configuration, $plugin_id, $discovery);
foreach ($context as $key => $value) {
$context_definition = $this->getContextDefinition($key);
$this->context[$key] = new Context($context_definition);
$this->context[$key]->setContextValue($value);
}
}
/**
* Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextDefinitions().
*/
public function getContextDefinitions() {
$definition = $this->getDefinition();
return !empty($definition['context']) ? $definition['context'] : NULL;
return !empty($definition['context']) ? $definition['context'] : array();
}
/**
* Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextDefinition().
*/
public function getContextDefinition($key) {
public function getContextDefinition($name) {
$definition = $this->getDefinition();
if (empty($definition['context'][$key])) {
throw new PluginException("The $key context is not a valid context.");
if (empty($definition['context'][$name])) {
throw new PluginException("The $name context is not a valid context.");
}
return $definition['context'][$key];
return $definition['context'][$name];
}
/**
......@@ -46,19 +74,15 @@ public function getContextDefinition($key) {
*/
public function getContexts() {
$definitions = $this->getContextDefinitions();
// If there are no contexts defined by the plugin, return an empty array.
if (empty($definitions)) {
return array();
}
if (empty($this->context)) {
if ($definitions && empty($this->context)) {
throw new PluginException("There are no set contexts.");
}
$contexts = array();
foreach (array_keys($definitions) as $key) {
if (empty($this->context[$key])) {
throw new PluginException("The $key context is not yet set.");
foreach (array_keys($definitions) as $name) {
if (empty($this->context[$name])) {
throw new PluginException("The $name context is not yet set.");
}
$contexts[$key] = $this->context[$key];
$contexts[$name] = $this->context[$name];
}
return $contexts;
}
......@@ -66,44 +90,67 @@ public function getContexts() {
/**
* Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContext().
*/
public function getContext($key) {
public function getContext($name) {
// Check for a valid context definition.
$this->getContextDefinition($key);
$this->getContextDefinition($name);
// Check for a valid context value.
if (empty($this->context[$key])) {
throw new PluginException("The $key context is not yet set.");
if (!isset($this->context[$name])) {
throw new PluginException("The $name context is not yet set.");
}
return $this->context[$key];
return $this->context[$name];
}
/**
* Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValues().
*/
public function getContextValues() {
$contexts = array();
foreach ($this->getContexts() as $key => $context) {
$contexts[$key] = $context->getContextValue();
$values = array();
foreach ($this->getContextDefinitions() as $name => $definition) {
$values[$name] = isset($this->context[$name]) ? $this->context[$name]->getContextValue() : NULL;
}
return $contexts;
return $values;
}
/**
* Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValue().
*/
public function getContextValue($key) {
return $this->getContext($key)->getContextValue();
public function getContextValue($name) {
return $this->getContext($name)->getContextValue();
}
/**
* Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::setContextValue().
*/
public function setContextValue($key, $value) {
$context_definition = $this->getContextDefinition($key);
$this->context[$key] = new Context($context_definition);
$this->context[$key]->setContextValue($value);
public function setContextValue($name, $value) {
$context_definition = $this->getContextDefinition($name);
$this->context[$name] = new Context($context_definition);
$this->context[$name]->setContextValue($value);
// Verify the provided value validates.
$violations = $this->context[$name]->validate();
if (count($violations) > 0) {
throw new PluginException("The provided context value does not pass validation.");
}
return $this;
}
/**
* Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::valdidateContexts().
*/
public function validateContexts() {
$violations = new ConstraintViolationList();
// @todo: Implement symfony validator API to let the validator traverse
// and set property paths accordingly.
foreach ($this->getContextDefinitions() as $name => $definition) {
// Validate any set values.
if (isset($this->context[$name])) {
$violations->addAll($this->context[$name]->validate());
}
// @todo: If no value is set, make sure any mapping is validated.
}
return $violations;
}
}
......@@ -8,35 +8,43 @@
namespace Drupal\Component\Plugin;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Context\Context;
/**
* Interface for defining context aware plugins.
*
* Context aware plugins can specify an array of context definitions keyed by
* context name at the plugin definition under the "context" key.
*/
interface ContextAwarePluginInterface extends PluginInspectionInterface {
/**
* Gets the context definitions of the plugin.
*
* @return array|null
* The context definitions if set, otherwise NULL.
* @return array
* The array of context definitions, keyed by context name.
*/
public function getContextDefinitions();
/**
* Gets the a specific context definition of the plugin.
* Gets a specific context definition of the plugin.
*
* @param string $key
* @param string $name
* The name of the context in the plugin definition.
*
* @return mixed
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If the requested context is not defined.
*
* @return array
* The definition against which the context value must validate.
*/
public function getContextDefinition($key);
public function getContextDefinition($name);
/**
* Gets the defined contexts.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If contexts are defined but not set.
*
* @return array
* The set context objects.
*/
......@@ -45,47 +53,64 @@ public function getContexts();
/**
* Gets a defined context.
*
* @param string $key
* The name of the context in the plugin configuration. This string is
* usually identical to the representative string in the plugin definition.
* @param string $name
* The name of the context 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.
*/
public function getContext($key);
public function getContext($name);
/**
* Gets the values for all defined contexts.
*
* @return array
* The set context object values.
* An array of set context values, keyed by context name. If a context is
* unset its value is returned as NULL.
*/
public function getContextValues();
/**
* Gets the value for a defined context.
*
* @param string $key
* The name of the context in the plugin configuration. This string is
* usually identical to the representative string in the plugin definition.
* @param string $name
* The name of the context in the plugin configuration.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If the requested context is not set.
*
* @return mixed
* The currently set context value.
*/
public function getContextValue($key);
public function getContextValue($name);
/**
* Sets the value for a defined context.
*
* @param string $key
* @param string $name
* The name of the context in the plugin definition.
* @param mixed $value
* The variable to set the context to. This should validate against the
* The value to set the context to. The value has to 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);
public function setContextValue($name, $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 validateContexts();
}
<?php
/**
* @file
* Contains \Drupal\Core\Condition\ConditionInterface.
*/
namespace Drupal\Core\Condition;
use Drupal\Core\Executable\ExecutableInterface;
/**
* An interface for condition plugins.
*
* @see \Drupal\Core\Executable\ExecutableInterface
*/
interface ConditionInterface extends ExecutableInterface {
/**
* Determines whether condition result will be negated.
*
* @return boolean
* Whether the condition result will be negated.
*/
public function isNegated();
/**
* Evaluates the condition and returns TRUE or FALSE accordingly.
*
* @return bool
* TRUE if the condition has been met, FALSE otherwise.
*/
public function evaluate();
}
<?php
/**
* @file
* Contains \Drupal\Core\Condition\ConditionManager.
*/
namespace Drupal\Core\Condition;
use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Executable\ExecutableInterface;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Discovery\AlterDecorator;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Core\Plugin\Discovery\CacheDecorator;
/**
* A plugin manager for condition plugins.
*/
class ConditionManager extends PluginManagerBase implements ExecutableManagerInterface {
/**
* Constructs aa ConditionManager object.
*/
public function __construct() {
$this->discovery = new AnnotatedClassDiscovery('Core', 'Condition');
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
$this->discovery = new AlterDecorator($this->discovery, 'condition_info');
$this->discovery = new CacheDecorator($this->discovery, 'condition:' . language(LANGUAGE_TYPE_INTERFACE)->langcode);
$this->factory = new DefaultFactory($this);
}
/**
* Override of Drupal\Component\Plugin\PluginManagerBase::createInstance().
*/
public function createInstance($plugin_id, array $configuration = array()) {
$plugin = $this->factory->createInstance($plugin_id, $configuration);
return $plugin->setExecutableManager($this);
}
/**
* Implements Drupal\Core\Executable\ExecutableManagerInterface::execute().
*/
public function execute(ExecutableInterface $condition) {
$result = $condition->evaluate();
return $condition->isNegated() ? !$result : $result;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Condition\ConditionPluginBase.
*/
namespace Drupal\Core\Condition;
use Drupal\Core\Executable\ExecutablePluginBase;
/**
* Provides a basis for fulfilling contexts for condition plugins.
*/
abstract class ConditionPluginBase extends ExecutablePluginBase implements ConditionInterface {
/**
* Implements \Drupal\Core\Form\FormInterface::getFormID().
*/
public function getFormID() {
$definition = $this->getDefinition();
return implode('_', array($definition['module'], $definition['id'], 'condition'));
}
/**
* Implements \Drupal\condition\Plugin\ConditionInterface::isNegated().
*/
public function isNegated() {
return !empty($this->configuration['negate']);
}
/**
* Implements \Drupal\Core\Form\FormInterface::buildForm().
*/
public function buildForm(array $form, array &$form_state) {
$form['negate'] = array(
'#type' => 'checkbox',
'#title' => t('Negate the condition.'),
'#default_value' => isset($this->configuration['negate']) ? $this->configuration['negate'] : FALSE,
);
return $form;
}
/**
* Implements \Drupal\Core\Form\FormInterface::validateForm().
*/
public function validateForm(array &$form, array &$form_state) {}
/**
* Implements \Drupal\Core\Form\FormInterface::submitForm().
*/
public function submitForm(array &$form, array &$form_state) {
$this->configuration['negate'] = $form_state['values']['negate'];
}
/**
* Implements \Drupal\Core\Executable\ExecutablePluginBase::execute().
*/
public function execute() {
return $this->executableManager->execute($this);
}
}
......@@ -276,6 +276,8 @@ public function build(ContainerBuilder $container) {
$container->register('flood', 'Drupal\Core\Flood\DatabaseBackend')
->addArgument(new Reference('database'));
$container->register('plugin.manager.condition', 'Drupal\Core\Condition\ConditionManager');
$container->addCompilerPass(new RegisterMatchersPass());
$container->addCompilerPass(new RegisterRouteFiltersPass());
// Add a compiler pass for registering event subscribers.
......
<?php
/**
* @file
* Definition of \Drupal\Core\Executable\ExecutableException.
*/
namespace Drupal\Core\Executable;
use Exception;
use Drupal\Component\Plugin\Exception\ExceptionInterface;
/**
* Generic executable plugin exception class.
*/
class ExecutableException extends Exception implements ExceptionInterface {
}
<?php
/**
* @file
* Contains \Drupal\Core\Executable\ExecutableInterface.
*/
namespace Drupal\Core\Executable;
use Drupal\Component\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Form\FormInterface;
/**
* An interface for executable plugins.
*
* Executable plugins are context-aware and configurable. They support the
* following keys in their plugin definitions:
* - context: An array of context definitions, keyed by context name. Each
* context definition is a typed data definition describing the context. Check
* the typed data definition docs for details.
* - configuration: An array of configuration option definitions, keyed by
* option name. Each option definition is a typed data definition describing
* the configuration option. Check the typed data definition docs for details.
*
* @see \Drupal\Core\TypedData\TypedDataManager::create()
*/
interface ExecutableInterface extends ContextAwarePluginInterface, FormInterface {
/**
* Executes the plugin.
*/
public function execute();
/**
* Provides a human readable summary of the executable's configuration.
*/
public function summary();
/**
* Sets the executable manager class.
*
* @param \Drupal\Core\Condition\ConditionManager $executableManager
* The executable manager.
*/
public function setExecutableManager(ExecutableManagerInterface $executableManager);
}
<?php
/**
* @file
* Contains \Drupal\Core\Executable\ExecutableManagerInterface.
*/
namespace Drupal\Core\Executable;
use Drupal\Component\Plugin\PluginManagerInterface;
/**
* An interface for managers of executable plugins.
*/
interface ExecutableManagerInterface extends PluginManagerInterface {
/**
* Executes an executable plugin.
*
* @param \Drupal\Core\Executable\ExecutableInterface $plugin
* An executable plugin instance managed by the implementing manager.
*
* @throws \Drupal\Core\Executable\ExecutableException
* If the plugin could not be executed.
*
* @return mixed
* The returned data varies by plugin implementation, e.g. conditions return
* the the boolean evaluation result.
*/
public function execute(ExecutableInterface $plugin);
}
<?php
/**
* @file
* Contains \Drupal\Core\Executable\ExecutablePluginBase.
*/
namespace Drupal\Core\Executable;
use Drupal\Core\Plugin\ContextAwarePluginBase;
use Symfony\Component\Validator\Validation;
use Drupal\Component\Plugin\Exception\PluginException;
/**
* Provides the basic architecture for executable plugins.
*/
abstract class ExecutablePluginBase extends ContextAwarePluginBase implements ExecutableInterface {
/**
* The condition manager to proxy execute calls through.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $executableManager;
/**
* Implements \Drupal\Core\Executable\ExecutableInterace::setExecutableManager().
*/
public function setExecutableManager(ExecutableManagerInterface $executableManager) {
$this->executableManager = $executableManager;
return $this;
}
/**
* Gets an array of definitions of available configuration options.
*
* @todo: This needs to go into an interface.
*
* @return array
* An array of typed data definitions describing available configuration
* options, keyed by option name.
*/
public function getConfigDefinitions() {
$definition = $this->getDefinition();
if (!empty($definition['configuration'])) {
return $definition['configuration'];
}
return array();
}
/**
* Gets the definition of a configuration option.
*
* @todo: This needs to go into an interface.
*
* @return array
* The typed data definition describing the configuration option, or FALSE
* if the option does not exist.
*/
public function getConfigDefinition($key) {
$definition = $this->getDefinition();
if (!empty($definition['configuration'][$key])) {
return $definition['configuration'][$key];
}
return FALSE;
}
/**
* Gets all configuration values.
*
* @todo: This needs to go into an interface.
*
* @return array
* The array of all configuration values, keyed by configuration option
* name.
*/
public function getConfig() {
return $this->configuration;
}
/**
* Sets the value of a particular configuration option.
*
* @param string $name
* The name of the configuration option to set.
* @param mixed $value
* The value to set.
*
* @todo This doesn't belong here. Move this into a new base class in
* http://drupal.org/node/1764380.
* @todo This does not set a value in config(), so the name is confusing.
*
* @return \Drupal\Core\Executable\ExecutablePluginBase.
* The executable object for chaining.
*/
public function setConfig($key, $value) {
if ($definition = $this->getConfigDefinition($key)) {
$typed_data = typed_data()->create($definition, $value);
if ($typed_data->validate()->count() > 0) {
throw new PluginException("The provided configuration value does not pass validation.");
}
}
$this->configuration[$key] = $value;
return $this;
}
}
......@@ -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,17 +28,31 @@ 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());
if (!empty($type_definition['primitive type'])) {
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.
*
......@@ -54,21 +70,31 @@ public function getTypedContext() {
}
/**
* Override for \Drupal\Component\Plugin\Context\Context::validate().
* Implements \Drupal\Component\Plugin\Context\ContextInterface::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;
// If we do have typed data, leverage it for getting constraints.
return $this->getTypedContext()->getConstraints();
}
throw new ContextException("The context passed could not be validated through typed data.");
}
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());
}
}
......@@ -8,7 +8,9 @@
namespace Drupal\Core\Plugin;
use Drupal\Component\Plugin\ContextAwarePluginBase as PluginBase;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
/**
* Drupal specific class for plugins that use context.
......@@ -20,13 +22,35 @@
abstract class ContextAwarePluginBase extends PluginBase {
/**
* Override of \Drupal\Component\Plugin\ContextAwarePluginBase::setContextValue().
* Override of \Drupal\Component\Plugin\ContextAwarePluginBase::__construct().
*/
public function setContextValue($key, $value) {
public function __construct(array $configuration, $plugin_id, DiscoveryInterface $discovery) {
$context = array();
if (isset($configuration['context'])) {
$context = $configuration['context'];
unset($configuration['context']);
}
parent::__construct($configuration, $plugin_id, $discovery);
foreach ($context as $key => $value) {
$context_definition = $this->getContextDefinition($key);
$this->context[$key] = new Context($context_definition);
$this->context[$key]->setContextValue($value);
}
}
/**
* Override of \Drupal\Component\Plugin\ContextAwarePluginBase::setContextValue().
*/
public function setContextValue($name, $value) {
$context_definition = $this->getContextDefinition($name);
// Use the Drupal specific context class.
$this->context[$name] = new Context($context_definition);
$this->context[$name]->setContextValue($value);
// Verify the provided value validates.
if ($this->context[$name]->validate()->count() > 0) {
throw new PluginException("The provided context value does not pass validation.");
}
return $this;
}
......
......@@ -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));
}
}
}
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Core\Condition\NodeType.
*/
namespace Drupal\node\Plugin\Core\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
/**
* Provides a 'Node Type' condition.
*
* @Plugin(
* id = "node_type",
* label = @Translation("Node Bundle"),
* module = "node",
* context = {
* "node" = {
* "type" = "entity",
* "constraints" = {
* "EntityType" = "node"
* }
* }
* }
* )
*/
class NodeType extends ConditionPluginBase {
/**
* Implements \Drupal\Core\Form\FormInterface::buildForm().
*/
public function buildForm(array $form, array &$form_state) {
$form = parent::buildForm($form, $form_state);
$options = array();
foreach (node_type_get_types() as $type) {
$options[$type->type] = $type->name;
}
$form['bundles'] = array(
'#type' => 'checkboxes',
'#options' => $options,
'#required' => TRUE,
'#default_value' => isset($this->configuration['bundles']) ? $this->configuration['bundles'] : array(),
);
return $form;
}
/**
* Implements \Drupal\Core\Form\FormInterface::validateForm().
*/
public function validateForm(array &$form, array &$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\Core\Form\FormInterface::submitForm().
*/
public function submitForm(array &$form, array &$form_state) {
$this->configuration['bundles'] = $form_state['values']['bundles'];
parent::submitForm($form, $form_state);
}
/**
* Implements \Drupal\Core\Executable\ExecutableInterface::summary().
*/
public function summary() {
if (count($this->configuration['bundles']) > 1) {
$bundles = $this->configuration['bundles'];
$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));
}
/**
* Implements \Drupal\condition\ConditionInterface::evaluate().
*/
public function evaluate() {
$node = $this->getContextValue('node');
return in_array($node->type, $this->configuration['bundles']);
}
}
<?php
/**
* @file
* Contains \Drupal\condition\Tests\Condition\NodeConditionTest.
*/
namespace Drupal\node\Tests\Condition;
use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\Core\Condition\ConditionManager;
/**
* Tests the node conditions.
*/
class NodeConditionTest extends DrupalUnitTestBase {
public static $modules = array('system', 'node', 'field');
public static function getInfo() {
return array(
'name' => 'Node Condition Plugins',
'description' => 'Tests that conditions, provided by the node module, are working properly.',
'group' => 'Condition API',
);
}
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_type');
$this->installSchema('node', 'node');
$this->installSchema('node', 'node_revision');
$this->installSchema('field', 'field_config');
$this->installSchema('field', 'field_config_instance');
}
/**
* Tests conditions.
*/
function testConditions() {
$manager = new ConditionManager();
// Get some nodes of various types to check against.
$page = entity_create('node', array('type' => 'page', 'title' => $this->randomName()));
$page->save();
$article = entity_create('node', array('type' => 'article', 'title' => $this->randomName()));
$article->save();
$test = entity_create('node', array('type' => 'test', 'title' => $this->randomName()));
$test->save();
// 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('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'));
$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'));
$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());
// Set the context to the article node.
$condition->setContextValue('node', $article);
$this->assertTrue($condition->execute(), 'Article type nodes pass node type checks for pages or articles');
// Set the context to the test node.
$condition->setContextValue('node', $test);
$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'));
$this->assertEqual('The node bundle is page, article or test', $condition->summary());
// Test Constructor injection.
$condition = $manager->createInstance('node_type', array('bundles' => array('article'), 'context' => array('node' => $article)));
$this->assertTrue($condition->execute(), 'Constructor injection of context and configuration working as anticipated.');
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Condition\ConditionFormTest.
*/
namespace Drupal\system\Tests\Condition;
use Drupal\simpletest\WebTestBase;
/**
* Tests condition forms, configuration and execution.
*
* Checks condition forms and submission and gives a very cursory check to make
* sure the configuration that was submitted actually causes the condition to
* validate correctly.
*/
class ConditionFormTest extends WebTestBase {
public static $modules = array('node', 'condition_test');
public static function getInfo() {
return array(
'name' => 'Condition Form Tests',
'description' => 'Tests that condtion plugins basic form handling is working.',
'group' => 'Condition API',
);
}
/**
* Submit the condition_node_type_test_form to test condition forms.
*/
function testConfigForm() {
$article = entity_create('node', array('type' => 'article', 'title' => $this->randomName()));
$article->save();
$this->drupalGet('condition_test');
$this->assertField('bundles[article]', 'There is an article bundle selector.');
$this->assertField('bundles[page]', 'There is a page bundle selector.');
$this->drupalPost(NULL, array('bundles[page]' => 'page', 'bundles[article]' => 'article'), t('Submit'));
$this->assertText('The bundles are article and page', 'The form component appropriately saved the bundles.');
$this->assertText('Executed successfully.', 'The form configured condition executed properly.');
}
}
......@@ -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();
......
name = "Condition Test Support"
description = "Test general form component for condition plugins."
package = Testing
version = VERSION
core = 8.x
hidden = TRUE
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment