diff --git a/core/lib/Drupal/Component/Plugin/Context/Context.php b/core/lib/Drupal/Component/Plugin/Context/Context.php new file mode 100644 index 0000000..b117a72 --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Context/Context.php @@ -0,0 +1,87 @@ +contextDefinition = $context_definition; + } + + /** + * Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextValue(). + */ + public function setContextValue($value) { + $this->contextValue = $value; + } + + /** + * Implements \Drupal\Component\Plugin\Context\ContextInterface::getContextValue(). + */ + public function getContextValue() { + return $this->contextValue; + } + + /** + * Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextDefinition(). + */ + public function setContextDefinition(array $context_definition) { + $this->contextDefinition = $context_definition; + } + + /** + * Implements \Drupal\Component\Plugin\Context\ContextInterface::getContextDefinition(). + */ + public function getContextDefinition() { + return $this->contextDefinition; + } + + /** + * Implements \Drupal\Component\Plugin\Context\ContextInterface::getConstraints(). + */ + public function getConstraints() { + if (empty($this->contextDefinition['class'])) { + 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 new file mode 100644 index 0000000..512605a --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php @@ -0,0 +1,71 @@ +getDefinition(); + return !empty($definition['context']) ? $definition['context'] : NULL; + } + + /** + * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextDefinition(). + */ + public function getContextDefinition($key) { + $definition = $this->getDefinition(); + if (empty($definition['context'][$key])) { + throw new PluginException("The $key context is not a valid context."); + } + return $definition['context'][$key]; + } + + /** + * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContexts(). + */ + 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)) { + 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."); + } + $contexts[$key] = $this->context[$key]; + } + return $contexts; + } + + /** + * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContext(). + */ + public function getContext($key) { + // Check for a valid context definition. + $this->getContextDefinition($key); + // Check for a valid context value. + if (empty($this->context[$key])) { + throw new PluginException("The $key context is not yet set."); + } + + return $this->context[$key]; + } + + /** + * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValues(). + */ + public function getContextValues() { + $definitions = (array) $this->getContextDefinitions(); + $values = array(); + foreach ($definitions as $key => $definition) { + $values[$key] = isset($this->context[$key]) ? $this->context[$key]->getContextValue() : NULL; + } + return $values; + } + + /** + * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValue(). + */ + public function getContextValue($key) { + return $this->getContext($key)->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); + + // 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 new file mode 100644 index 0000000..086a25f --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php @@ -0,0 +1,116 @@ +register('flood', 'Drupal\Core\Flood\DatabaseBackend') ->addArgument(new Reference('database')); + $container->register('plugin.manager.processor', 'Drupal\Core\Processor\ProcessorManager'); + $container->addCompilerPass(new RegisterMatchersPass()); $container->addCompilerPass(new RegisterRouteFiltersPass()); // Add a compiler pass for registering event subscribers. diff --git a/core/lib/Drupal/Core/Executable/ExecutableException.php b/core/lib/Drupal/Core/Executable/ExecutableException.php new file mode 100644 index 0000000..7ea5d98 --- /dev/null +++ b/core/lib/Drupal/Core/Executable/ExecutableException.php @@ -0,0 +1,17 @@ +processorManager = $processorManager; + return $this; + } + + /** + * Implements \Drupal\Core\Executable\ExecutableInterface::setProcessor(). + */ + public function setProcessor($plugin_id, array $configuration) { + // @todo Do we want to check this before actually assigning? + $this->configuration['processors'][] = $this->processorManager->createInstance($plugin_id, $configuration); + return $this; + } + + /** + * Implements \Drupal\Core\Executable\ExecutableInterface::getProcessor(). + */ + public function getProcessors() { + return $this->configuration['processors']; + } + + /** + * Implements \Drupal\Core\Executable\ExecutableInterface::setExecutableManager(). + */ + public function setExecutableManager(ExecutableManagerInterface $executableManager) { + $this->executableManager = $executableManager; + } + + /** + * Implements \Drupal\Core\Executable\ExecutableInterface::getExecutableManager(). + */ + public function getExecutableManager() { + return $this->executableManager; + } + + /** + * Gets all configuration values. + * + * @see \Drupal\Component\Plugin\PluginBase::$configuration + * + * @return array + */ + public function getConfig() { + return $this->configuration; + } + + /** + * Sets a particular value in the block settings. + * + * @param string $key + * The key of PluginBase::$configuration to set. + * @param mixed $value + * The value to set for the provided key. + * + * @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. + * + * @see \Drupal\Component\Plugin\PluginBase::$configuration + */ + public function setConfig($key, $value) { + $this->configuration[$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 new file mode 100644 index 0000000..3eb195d --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/Context/Context.php @@ -0,0 +1,100 @@ +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 + * return plain value of a TypedData object. This class overrides that method + * to return the appropriate values from TypedData objects, but the object + * itself can be useful as well, so this method is provided to allow for + * access to the TypedData object. Since parent::getContextValue() already + * does all the processing we need, we simply proxy to it here. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + */ + public function getTypedContext() { + return parent::getContextValue(); + } + + /** + * Overrides \Drupal\Component\Plugin\Context\Context::getConstraints(). + */ + public function getConstraints() { + if (!empty($this->contextDefinition['type'])) { + // If we do have typed data, leverage it for getting constraints. + return $this->getTypedContext()->getConstraints(); + } + 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 new file mode 100644 index 0000000..e4daeb1 --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php @@ -0,0 +1,38 @@ +getContextDefinition($key); + $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/Processor/ProcessorInterface.php b/core/lib/Drupal/Core/Processor/ProcessorInterface.php new file mode 100644 index 0000000..a3e15e7 --- /dev/null +++ b/core/lib/Drupal/Core/Processor/ProcessorInterface.php @@ -0,0 +1,15 @@ +discovery = new AnnotatedClassDiscovery('Core', 'Processor'); + $this->discovery = new AlterDecorator($this->discovery, 'processor'); + $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition')); + $this->discovery = new CacheDecorator($this->discovery, 'processor:' . language(LANGUAGE_TYPE_INTERFACE)->langcode); + + $this->factory = new DefaultFactory($this); + } + +} + 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 @@ +get('plugin.manager.condition')->createInstance($plugin_id, $options); +} diff --git a/core/modules/condition/lib/Drupal/condition/ConditionBundle.php b/core/modules/condition/lib/Drupal/condition/ConditionBundle.php new file mode 100644 index 0000000..b41a3db --- /dev/null +++ b/core/modules/condition/lib/Drupal/condition/ConditionBundle.php @@ -0,0 +1,28 @@ +register('plugin.manager.condition', 'Drupal\condition\Plugin\Type\ConditionManager')->addArgument(new Reference('plugin.manager.processor')); + } + +} diff --git a/core/modules/condition/lib/Drupal/condition/Plugin/ConditionInterface.php b/core/modules/condition/lib/Drupal/condition/Plugin/ConditionInterface.php new file mode 100644 index 0000000..f393c47 --- /dev/null +++ b/core/modules/condition/lib/Drupal/condition/Plugin/ConditionInterface.php @@ -0,0 +1,43 @@ +configuration['negate'] = $boolean; + return $this; + } + + /** + * Implements \Drupal\condition\Plugin\ConditionInterface::isNegated(). + */ + public function isNegated() { + return !empty($this->configuration['negate']); + } + + /** + * Implements \Drupal\condition\Plugin\ConditionInterface::form(). + */ + public function form($form, &$form_state) { + // @todo add context definition parsing and provide select elements for + // fulfilling contexts from some source. + $form['negate'] = array( + '#type' => 'checkbox', + '#title' => t('Negate the condition.'), + '#default_value' => isset($this->configuration['negate']) ? $this->configuration['negate'] : FALSE, + ); + return $form; + } + + /** + * Overrides ExecutablePluginBase::extractFormValues(). + */ + public function extractFormValues($form, &$form_state) { + $this->configuration['negate'] = $form_state['values']['negate']; + } + + /** + * Implements ExecutablePluginBase::execute(). + */ + public function execute() { + return $this->executableManager->execute($this); + } + +} diff --git a/core/modules/condition/lib/Drupal/condition/Plugin/Type/ConditionManager.php b/core/modules/condition/lib/Drupal/condition/Plugin/Type/ConditionManager.php new file mode 100644 index 0000000..2879083 --- /dev/null +++ b/core/modules/condition/lib/Drupal/condition/Plugin/Type/ConditionManager.php @@ -0,0 +1,61 @@ +processorManager = $process_manager; + $this->discovery = new AnnotatedClassDiscovery('Core', 'Condition'); + $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); + $plugin->setProcessorManager($this->processorManager); + $plugin->setExecutableManager($this); + return $plugin; + } + + /** + * Implements \Drupal\Core\Executable\ExecutableManagerInterface::execute() + */ + public function execute(ExecutableInterface $condition) { + $result = $condition->evaluate(); + return $condition->isNegated() ? !$result : $result; + } + +} 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 new file mode 100644 index 0000000..555f5c1 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Condition/NodeType.php @@ -0,0 +1,88 @@ +getContextValues(); + $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' => $values['bundles'], + ); + return $form; + } + + /** + * Implements \Drupal\condition\ConditionInterface::extractFormValues(). + */ + 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() { + $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)); + } + return t('The node bundle is @bundle', array('@bundle' => reset($bundles))); + } + + /** + * Implements \Drupal\condition\ConditionInterface::evaluate(). + */ + public function evaluate() { + $node = $this->getContextValue('node'); + 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 new file mode 100644 index 0000000..ca94276 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php @@ -0,0 +1,96 @@ + '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'); + $this->installSchema('entity_test', 'entity_test'); + } + + /** + * Tests conditions. + */ + function testConditions() { + $processor = new ProcessorManager(); + $manager = new ConditionManager($processor); + + // 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') + ->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->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->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()); + + // 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->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 new file mode 100644 index 0000000..c22f222 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php @@ -0,0 +1,194 @@ + 'Contextual Plugins', + 'description' => 'Tests that contexts are properly set and working within plugins.', + 'group' => 'Plugin API', + ); + } + + protected function setUp() { + parent::setUp(); + $this->installSchema('node', 'node_type'); + } + + /** + * Tests basic context definition and value getters and setters. + */ + function testContext() { + $name = $this->randomName(); + $manager = new MockBlockManager(); + $plugin = $manager->createInstance('user_name'); + // Create a node, add it as context, catch the exception. + $node = entity_create('node', array('title' => $name)); + + // Try to get a valid context that has not been set. + try { + $plugin->getContext('user'); + $this->fail('The user context should not yet be set.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The user context is not yet set.'); + } + + // Try to get an invalid context. + try { + $plugin->getContext('node'); + $this->fail('The node context should not be a valid context.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The node context is not a valid context.'); + } + + // Try to get a valid context value that has not been set. + try { + $plugin->getContextValue('user'); + $this->fail('The user context should not yet be set.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The user context is not yet set.'); + } + + // Try to call a method of the plugin that requires context before it has + // been set. + try { + $plugin->getTitle(); + $this->fail('The user context should not yet be set.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The user context is not yet set.'); + } + + // Try to get a context value that is not valid. + try { + $plugin->getContextValue('node'); + $this->fail('The node context should not be a valid context.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The node context is not a valid context.'); + } + + // Try to pass the wrong class type as a context value. + try { + $plugin->setContextValue('user', $node); + $this->fail('The node context should fail validation for a user context.'); + } + 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 + // its methods work as expected. + $user = entity_create('user', array('name' => $name)); + $plugin->setContextValue('user', $user); + $this->assertEqual($user->label(), $plugin->getTitle()); + + // Test the getContextDefinitions() method. + $this->assertIdentical($plugin->getContextDefinitions(), array('user' => array('class' => 'Drupal\user\Plugin\Core\Entity\User'))); + + // Test the getContextDefinition() method for a valid context. + $this->assertEqual($plugin->getContextDefinition('user'), array('class' => 'Drupal\user\Plugin\Core\Entity\User')); + + // Test the getContextDefinition() method for an invalid context. + try { + $plugin->getContextDefinition('node'); + $this->fail('The node context should not be a valid context.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The node context is not a valid context.'); + } + + // Test typed data context plugins. + $typed_data_plugin = $manager->createInstance('string_context'); + + // Try to get a valid context value that has not been set. + try { + $typed_data_plugin->getContextValue('string'); + $this->fail('The string context should not yet be set.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The string context is not yet set.'); + } + + // Try to call a method of the plugin that requires a context value before + // it has been set. + try { + $typed_data_plugin->getTitle(); + $this->fail('The string context should not yet be set.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The string context is not yet set.'); + } + + // Set the context value appropriately and check the title. + $typed_data_plugin->setContextValue('string', $name); + $this->assertEqual($name, $typed_data_plugin->getTitle()); + + // Test Complex compound context handling. + $complex_plugin = $manager->createInstance('complex_context'); + + // With no contexts set, try to get the contexts. + try { + $complex_plugin->getContexts(); + $this->fail('There should not be any contexts set yet.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'There are no set contexts.'); + } + + // With no contexts set, try to get the context values. + $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); + + // With only the user context set, try to get the contexts. + try { + $complex_plugin->getContexts(); + $this->fail('The node context should not yet be set.'); + } + catch (PluginException $e) { + $this->assertEqual($e->getMessage(), 'The node context is not yet set.'); + } + + // With only the user context set, try to get the context values. + $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(); + // Make sure what came out of the wrappers is good. + $this->assertEqual($context_wrappers['user']->getContextValue()->label(), $user->label()); + $this->assertEqual($context_wrappers['node']->getContextValue()->label(), $node->label()); + + // Make sure what comes out of the context values is good. + $contexts = $complex_plugin->getContextValues(); + $this->assertEqual($contexts['user']->label(), $user->label()); + $this->assertEqual($contexts['node']->label(), $node->label()); + + // Test the title method for the complex context plugin. + $this->assertEqual($user->label() . ' -- ' . $node->label(), $complex_plugin->getTitle()); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/PluginTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/PluginTestBase.php index 5db9322..c927f7e 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Plugin/PluginTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/PluginTestBase.php @@ -70,6 +70,28 @@ public function setUp() { 'label' => 'Layout Foo', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock', ), + 'user_name' => array( + 'label' => 'User name', + 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock', + 'context' => array( + 'user' => array('class' => 'Drupal\user\Plugin\Core\Entity\User') + ), + ), + 'string_context' => array( + 'label' => 'String typed data', + 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock', + 'context' => array( + 'string' => array('type' => 'string'), + ), + ), + 'complex_context' => array( + 'label' => 'Complex context', + 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock', + 'context' => array( + 'user' => array('class' => 'Drupal\user\Plugin\Core\Entity\User'), + 'node' => array('class' => 'Drupal\node\Plugin\Core\Entity\Node'), + ), + ), ); $this->defaultsTestPluginExpectedDefinitions = array( 'test_block1' => array( 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.'), diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/MockBlockManager.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/MockBlockManager.php index fcf1824..7e29259 100644 --- a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/MockBlockManager.php +++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/MockBlockManager.php @@ -66,6 +66,35 @@ public function __construct() { 'derivative' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlockDeriver', )); + // A block plugin that requires context to function. This block requires a + // user object in order to return the user name from the getTitle() method. + $this->discovery->setDefinition('user_name', array( + 'label' => t('User name'), + 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock', + 'context' => array( + 'user' => array('class' => 'Drupal\user\Plugin\Core\Entity\User') + ), + )); + + // A block plugin that requires a typed data string context to function. + $this->discovery->setDefinition('string_context', array( + 'label' => t('String typed data'), + 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock', + 'context' => array( + 'string' => array('type' => 'string'), + ), + )); + + // A complex context plugin that requires both a user and node for context. + $this->discovery->setDefinition('complex_context', array( + 'label' => t('Complex context'), + 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock', + 'context' => array( + 'user' => array('class' => 'Drupal\user\Plugin\Core\Entity\User'), + 'node' => array('class' => 'Drupal\node\Plugin\Core\Entity\Node'), + ), + )); + // In addition to finding all of the plugins available for a type, a plugin // type must also be able to create instances of that plugin. For example, a // specific instance of a "Main menu" menu block, configured to show just diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/MockComplexContextBlock.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/MockComplexContextBlock.php new file mode 100644 index 0000000..1cb657a --- /dev/null +++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/MockComplexContextBlock.php @@ -0,0 +1,24 @@ +getContextValue('user'); + $node = $this->getContextValue('node'); + return $user->label() . ' -- ' . $node->label(); + } +} diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/MockUserNameBlock.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/MockUserNameBlock.php new file mode 100644 index 0000000..3fc1f84 --- /dev/null +++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/MockUserNameBlock.php @@ -0,0 +1,24 @@ +getContextValue('user'); + return $user->label(); + } +} diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/TypedDataStringBlock.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/TypedDataStringBlock.php new file mode 100644 index 0000000..14e0ab3 --- /dev/null +++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/mock_block/TypedDataStringBlock.php @@ -0,0 +1,23 @@ +getContextValue('string'); + } +}