Commit 89686bf4 authored by webchick's avatar webchick

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

parent b0e08e42
......@@ -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());
}
}
......@@ -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);
}