Commit 633d8428 authored by alexpott's avatar alexpott

Issue #2339151 by EclipseGc, tim.plunkett, Gábor Hojtsy, effulgentsia:...

Issue #2339151 by EclipseGc, tim.plunkett, Gábor Hojtsy, effulgentsia: Conditions / context system does not allow for multiple configurable contexts, eg. language types
parent cbc449b8
...@@ -300,12 +300,6 @@ block_settings: ...@@ -300,12 +300,6 @@ block_settings:
view_mode: view_mode:
type: string type: string
label: 'View mode' label: 'View mode'
visibility:
type: sequence
label: 'Visibility Conditions'
sequence:
- type: condition.plugin.[id]
label: 'Visibility Condition'
provider: provider:
type: string type: string
label: 'Provider' label: 'Provider'
......
...@@ -125,4 +125,28 @@ public function setContextValue($name, $value); ...@@ -125,4 +125,28 @@ public function setContextValue($name, $value);
*/ */
public function validateContexts(); public function validateContexts();
/**
* Returns a mapping of the expected assignment names to their context names.
*
* @return array
* A mapping of the expected assignment names to their context names. For
* example, if one of the $contexts is named 'current_user', but the plugin
* expects a context named 'user', then this map would contain
* 'current_user' => 'user'.
*/
public function getContextMapping();
/**
* Sets a mapping of the expected assignment names to their context names.
*
* @param array $context_mapping
* A mapping of the expected assignment names to their context names. For
* example, if one of the $contexts is named 'current_user', but the plugin
* expects a context named 'user', then this map would contain
* 'current_user' => 'user'.
*
* @return $this
*/
public function setContextMapping(array $context_mapping);
} }
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace Drupal\Core\Annotation; namespace Drupal\Core\Annotation;
use Drupal\Component\Annotation\Plugin; use Drupal\Component\Annotation\Plugin;
use Drupal\Core\StringTranslation\TranslationWrapper;
/** /**
* @defgroup plugin_context Annotation for context definition * @defgroup plugin_context Annotation for context definition
...@@ -94,9 +95,18 @@ public function __construct(array $values) { ...@@ -94,9 +95,18 @@ public function __construct(array $values) {
$values += array( $values += array(
'required' => TRUE, 'required' => TRUE,
'multiple' => FALSE, 'multiple' => FALSE,
'label' => NULL,
'description' => NULL,
); );
// Annotation classes extract data from passed annotation classes directly
// used in the classes they pass to.
foreach (['label', 'description'] as $key) {
// @todo Remove this workaround in https://www.drupal.org/node/2362727.
if (isset($values[$key]) && $values[$key] instanceof TranslationWrapper) {
$values[$key] = (string) $values[$key]->get();
}
else {
$values[$key] = NULL;
}
}
if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) { if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) {
throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.'); throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.');
} }
......
...@@ -8,13 +8,7 @@ ...@@ -8,13 +8,7 @@
namespace Drupal\Core\Block; namespace Drupal\Core\Block;
use Drupal\block\BlockInterface; use Drupal\block\BlockInterface;
use Drupal\block\Event\BlockConditionContextEvent;
use Drupal\block\Event\BlockEvents;
use Drupal\Component\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResult;
use Drupal\Core\Condition\ConditionAccessResolverTrait;
use Drupal\Core\Condition\ConditionPluginCollection;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\Core\Plugin\ContextAwarePluginBase;
use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Unicode;
...@@ -35,22 +29,6 @@ ...@@ -35,22 +29,6 @@
*/ */
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface { abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface {
use ConditionAccessResolverTrait;
/**
* The condition plugin collection.
*
* @var \Drupal\Core\Condition\ConditionPluginCollection
*/
protected $conditionCollection;
/**
* The condition plugin manager.
*
* @var \Drupal\Core\Executable\ExecutableManagerInterface
*/
protected $conditionPluginManager;
/** /**
* The transliteration service. * The transliteration service.
* *
...@@ -84,9 +62,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition ...@@ -84,9 +62,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getConfiguration() { public function getConfiguration() {
return array( return $this->configuration;
'visibility' => $this->getVisibilityConditions()->getConfiguration(),
) + $this->configuration;
} }
/** /**
...@@ -107,13 +83,6 @@ public function setConfiguration(array $configuration) { ...@@ -107,13 +83,6 @@ public function setConfiguration(array $configuration) {
* An associative array with the default configuration. * An associative array with the default configuration.
*/ */
protected function baseConfigurationDefaults() { protected function baseConfigurationDefaults() {
// @todo Allow list of conditions to be configured in
// https://drupal.org/node/2284687.
$visibility = array_map(function ($definition) {
return array('id' => $definition['id']);
}, $this->conditionPluginManager()->getDefinitions());
unset($visibility['current_theme']);
return array( return array(
'id' => $this->getPluginId(), 'id' => $this->getPluginId(),
'label' => '', 'label' => '',
...@@ -123,7 +92,6 @@ protected function baseConfigurationDefaults() { ...@@ -123,7 +92,6 @@ protected function baseConfigurationDefaults() {
'max_age' => 0, 'max_age' => 0,
'contexts' => array(), 'contexts' => array(),
), ),
'visibility' => $visibility,
); );
} }
...@@ -152,25 +120,10 @@ public function calculateDependencies() { ...@@ -152,25 +120,10 @@ public function calculateDependencies() {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function access(AccountInterface $account) { public function access(AccountInterface $account) {
// @todo Add in a context mapping until the UI supports configuring them, // @todo Remove self::blockAccess() and force individual plugins to return
// see https://drupal.org/node/2284687. // their own AccessResult logic. Until that is done in
$mappings['user_role']['current_user'] = 'user'; // https://www.drupal.org/node/2375689 the access will be set uncacheable.
if ($this->blockAccess($account)) {
$conditions = $this->getVisibilityConditions();
$contexts = $this->getConditionContexts();
foreach ($conditions as $condition_id => $condition) {
if ($condition instanceof ContextAwarePluginInterface) {
if (!isset($mappings[$condition_id])) {
$mappings[$condition_id] = array();
}
$this->contextHandler()->applyContextMapping($condition, $contexts, $mappings[$condition_id]);
}
}
// This should not be hardcoded to an uncacheable access check result, but
// in order to fix that, we need condition plugins to return cache contexts,
// otherwise it will be impossible to determine by which cache contexts the
// result should be varied.
if ($this->resolveConditions($conditions, 'and', $contexts, $mappings) !== FALSE && $this->blockAccess($account)) {
$access = AccessResult::allowed(); $access = AccessResult::allowed();
} }
else { else {
...@@ -179,18 +132,6 @@ public function access(AccountInterface $account) { ...@@ -179,18 +132,6 @@ public function access(AccountInterface $account) {
return $access->setCacheable(FALSE); return $access->setCacheable(FALSE);
} }
/**
* Gets the values for all defined contexts.
*
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
* An array of set contexts, keyed by context name.
*/
protected function getConditionContexts() {
$conditions = $this->getVisibilityConditions();
$this->eventDispatcher()->dispatch(BlockEvents::CONDITION_CONTEXT, new BlockConditionContextEvent($conditions));
return $conditions->getConditionContexts();
}
/** /**
* Indicates whether the block should be shown. * Indicates whether the block should be shown.
* *
...@@ -287,53 +228,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ...@@ -287,53 +228,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
$form['cache']['contexts']['#description'] .= ' ' . t('This block is <em>always</em> varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list)); $form['cache']['contexts']['#description'] .= ' ' . t('This block is <em>always</em> varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list));
} }
$form['visibility_tabs'] = array(
'#type' => 'vertical_tabs',
'#title' => $this->t('Visibility'),
'#parents' => array('visibility_tabs'),
'#attached' => array(
'library' => array(
'block/drupal.block',
),
),
);
foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
$condition_form = $condition->buildConfigurationForm(array(), $form_state);
$condition_form['#type'] = 'details';
$condition_form['#title'] = $condition->getPluginDefinition()['label'];
$condition_form['#group'] = 'visibility_tabs';
$form['visibility'][$condition_id] = $condition_form;
}
// @todo Determine if there is a better way to rename the conditions.
if (isset($form['visibility']['node_type'])) {
$form['visibility']['node_type']['#title'] = $this->t('Content types');
$form['visibility']['node_type']['bundles']['#title'] = $this->t('Content types');
$form['visibility']['node_type']['negate']['#type'] = 'value';
$form['visibility']['node_type']['negate']['#title_display'] = 'invisible';
$form['visibility']['node_type']['negate']['#value'] = $form['visibility']['node_type']['negate']['#default_value'];
}
if (isset($form['visibility']['user_role'])) {
$form['visibility']['user_role']['#title'] = $this->t('Roles');
unset($form['visibility']['user_role']['roles']['#description']);
$form['visibility']['user_role']['negate']['#type'] = 'value';
$form['visibility']['user_role']['negate']['#value'] = $form['visibility']['user_role']['negate']['#default_value'];
}
if (isset($form['visibility']['request_path'])) {
$form['visibility']['request_path']['#title'] = $this->t('Pages');
$form['visibility']['request_path']['negate']['#type'] = 'radios';
$form['visibility']['request_path']['negate']['#title_display'] = 'invisible';
$form['visibility']['request_path']['negate']['#default_value'] = (int) $form['visibility']['request_path']['negate']['#default_value'];
$form['visibility']['request_path']['negate']['#options'] = array(
$this->t('Show for the listed pages'),
$this->t('Hide for the listed pages'),
);
}
if (isset($form['visibility']['language'])) {
$form['visibility']['language']['negate']['#type'] = 'value';
$form['visibility']['language']['negate']['#value'] = $form['visibility']['language']['negate']['#default_value'];
}
// Add plugin-specific settings for this block type. // Add plugin-specific settings for this block type.
$form += $this->blockForm($form, $form_state); $form += $this->blockForm($form, $form_state);
return $form; return $form;
...@@ -362,15 +256,6 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form ...@@ -362,15 +256,6 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
$contexts = $form_state->getValue(array('cache', 'contexts')); $contexts = $form_state->getValue(array('cache', 'contexts'));
$form_state->setValue(array('cache', 'contexts'), array_values(array_filter($contexts))); $form_state->setValue(array('cache', 'contexts'), array_values(array_filter($contexts)));
foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
// Allow the condition to validate the form.
$condition_values = (new FormState())
->setValues($form_state->getValue(['visibility', $condition_id]));
$condition->validateConfigurationForm($form, $condition_values);
// Update the original form values.
$form_state->setValue(['visibility', $condition_id], $condition_values->getValues());
}
$this->blockValidate($form, $form_state); $this->blockValidate($form, $form_state);
} }
...@@ -394,14 +279,6 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s ...@@ -394,14 +279,6 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
$this->configuration['label_display'] = $form_state->getValue('label_display'); $this->configuration['label_display'] = $form_state->getValue('label_display');
$this->configuration['provider'] = $form_state->getValue('provider'); $this->configuration['provider'] = $form_state->getValue('provider');
$this->configuration['cache'] = $form_state->getValue('cache'); $this->configuration['cache'] = $form_state->getValue('cache');
foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
// Allow the condition to submit the form.
$condition_values = (new FormState())
->setValues($form_state->getValue(['visibility', $condition_id]));
$condition->submitConfigurationForm($form, $condition_values);
// Update the original form values.
$form_state->setValue(['visibility', $condition_id], $condition_values->getValues());
}
$this->blockSubmit($form, $form_state); $this->blockSubmit($form, $form_state);
} }
} }
...@@ -503,61 +380,4 @@ public function isCacheable() { ...@@ -503,61 +380,4 @@ public function isCacheable() {
return $max_age === Cache::PERMANENT || $max_age > 0; return $max_age === Cache::PERMANENT || $max_age > 0;
} }
/**
* {@inheritdoc}
*/
public function getVisibilityConditions() {
if (!isset($this->conditionCollection)) {
$this->conditionCollection = new ConditionPluginCollection($this->conditionPluginManager(), $this->configuration['visibility']);
}
return $this->conditionCollection;
}
/**
* {@inheritdoc}
*/
public function getVisibilityCondition($instance_id) {
return $this->getVisibilityConditions()->get($instance_id);
}
/**
* {@inheritdoc}
*/
public function setVisibilityConfig($instance_id, array $configuration) {
$this->getVisibilityConditions()->setInstanceConfiguration($instance_id, $configuration);
return $this;
}
/**
* Gets the condition plugin manager.
*
* @return \Drupal\Core\Executable\ExecutableManagerInterface
* The condition plugin manager.
*/
protected function conditionPluginManager() {
if (!isset($this->conditionPluginManager)) {
$this->conditionPluginManager = \Drupal::service('plugin.manager.condition');
}
return $this->conditionPluginManager;
}
/**
* Wraps the event dispatcher.
*
* @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
* The event dispatcher.
*/
protected function eventDispatcher() {
return \Drupal::service('event_dispatcher');
}
/**
* Wraps the context handler.
*
* @return \Drupal\Core\Plugin\Context\ContextHandlerInterface
*/
protected function contextHandler() {
return \Drupal::service('context.handler');
}
} }
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\Core\Block; namespace Drupal\Core\Block;
use Drupal\Component\Plugin\Context\ContextInterface;
use Drupal\Component\Plugin\DerivativeInspectionInterface; use Drupal\Component\Plugin\DerivativeInspectionInterface;
use Drupal\Core\Cache\CacheableInterface; use Drupal\Core\Cache\CacheableInterface;
use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Component\Plugin\PluginInspectionInterface;
...@@ -143,35 +142,4 @@ public function blockSubmit($form, FormStateInterface $form_state); ...@@ -143,35 +142,4 @@ public function blockSubmit($form, FormStateInterface $form_state);
*/ */
public function getMachineNameSuggestion(); public function getMachineNameSuggestion();
/**
* Gets conditions for this block.
*
* @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginCollection
* An array or collection of configured condition plugins.
*/
public function getVisibilityConditions();
/**
* Gets a visibility condition plugin instance.
*
* @param string $instance_id
* The condition plugin instance ID.
*
* @return \Drupal\Core\Condition\ConditionInterface
* A condition plugin.
*/
public function getVisibilityCondition($instance_id);
/**
* Sets the visibility condition configuration.
*
* @param string $instance_id
* The condition instance ID.
* @param array $configuration
* The condition configuration.
*
* @return $this
*/
public function setVisibilityConfig($instance_id, array $configuration);
} }
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
use Drupal\Core\Executable\ExecutablePluginBase; use Drupal\Core\Executable\ExecutablePluginBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
/** /**
* Provides a basis for fulfilling contexts for condition plugins. * Provides a basis for fulfilling contexts for condition plugins.
...@@ -21,6 +22,8 @@ ...@@ -21,6 +22,8 @@
*/ */
abstract class ConditionPluginBase extends ExecutablePluginBase implements ConditionInterface { abstract class ConditionPluginBase extends ExecutablePluginBase implements ConditionInterface {
use ContextAwarePluginAssignmentTrait;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -41,6 +44,9 @@ public function isNegated() { ...@@ -41,6 +44,9 @@ public function isNegated() {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function buildConfigurationForm(array $form, FormStateInterface $form_state) { public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$temporary = $form_state->getTemporary();
$contexts = isset($temporary['gathered_contexts']) ? $temporary['gathered_contexts'] : [];
$form['context_mapping'] = $this->addContextAssignmentElement($this, $contexts);
$form['negate'] = array( $form['negate'] = array(
'#type' => 'checkbox', '#type' => 'checkbox',
'#title' => $this->t('Negate the condition'), '#title' => $this->t('Negate the condition'),
......
...@@ -41,6 +41,12 @@ public function getConfiguration() { ...@@ -41,6 +41,12 @@ public function getConfiguration() {
$default_config = array(); $default_config = array();
$default_config['id'] = $instance_id; $default_config['id'] = $instance_id;
$default_config += $this->get($instance_id)->defaultConfiguration(); $default_config += $this->get($instance_id)->defaultConfiguration();
// In order to determine if a plugin is configured, we must compare it to
// its default configuration. The default configuration of a plugin does
// not contain context_mapping and it is not used when the plugin is not
// configured, so remove the context_mapping from the instance config to
// compare the remaining values.
unset($instance_config['context_mapping']);
if ($default_config === $instance_config) { if ($default_config === $instance_config) {
unset($configuration[$instance_id]); unset($configuration[$instance_id]);
} }
......
...@@ -291,10 +291,17 @@ public function buildEntity(array $form, FormStateInterface $form_state) { ...@@ -291,10 +291,17 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
* The current state of the form. * The current state of the form.
*/ */
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
$values = $form_state->getValues();
if ($this->entity instanceof EntityWithPluginCollectionInterface) {
// Do not manually update values represented by plugin collections.
$values = array_diff_key($values, $this->entity->getPluginCollections());
}
// @todo: This relies on a method that only exists for config and content // @todo: This relies on a method that only exists for config and content
// entities, in a different way. Consider moving this logic to a config // entities, in a different way. Consider moving this logic to a config
// entity specific implementation. // entity specific implementation.
foreach ($form_state->getValues() as $key => $value) { foreach ($values as $key => $value) {
$entity->set($key, $value); $entity->set($key, $value);
} }
} }
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\Core\Plugin\Context; namespace Drupal\Core\Plugin\Context;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\ContextAwarePluginInterface; use Drupal\Component\Plugin\ContextAwarePluginInterface;
use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Component\Utility\String; use Drupal\Component\Utility\String;
...@@ -73,12 +72,7 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface ...@@ -73,12 +72,7 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface
* {@inheritdoc} * {@inheritdoc}
*/ */
public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) { public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) {
if ($plugin instanceof ConfigurablePluginInterface) { $mappings += $plugin->getContextMapping();
$configuration = $plugin->getConfiguration();
if (isset($configuration['context_mapping'])) {
$mappings += array_flip($configuration['context_mapping']);
}
}
$plugin_contexts = $plugin->getContextDefinitions(); $plugin_contexts = $plugin->getContextDefinitions();
// Loop through each context and set it on the plugin if it matches one of // Loop through each context and set it on the plugin if it matches one of
// the contexts expected by the plugin. // the contexts expected by the plugin.
......