From 633d84281a84a518a9b6f09b4cab6cd4ceed670e Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 19 Nov 2014 21:03:06 +0000 Subject: [PATCH] =?UTF-8?q?Issue=20#2339151=20by=20EclipseGc,=20tim.plunke?= =?UTF-8?q?tt,=20G=C3=A1bor=20Hojtsy,=20effulgentsia:=20Conditions=20/=20c?= =?UTF-8?q?ontext=20system=20does=20not=20allow=20for=20multiple=20configu?= =?UTF-8?q?rable=20contexts,=20eg.=20language=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/config/schema/core.data_types.schema.yml | 6 - .../Plugin/ContextAwarePluginInterface.php | 24 +++ .../Core/Annotation/ContextDefinition.php | 14 +- core/lib/Drupal/Core/Block/BlockBase.php | 190 +----------------- .../Core/Block/BlockPluginInterface.php | 32 --- .../Core/Condition/ConditionPluginBase.php | 6 + .../Condition/ConditionPluginCollection.php | 6 + core/lib/Drupal/Core/Entity/EntityForm.php | 9 +- .../Core/Plugin/Context/ContextHandler.php | 8 +- .../ContextAwarePluginAssignmentTrait.php | 72 +++++++ .../Core/Plugin/ContextAwarePluginBase.php | 42 ++++ core/modules/block/block.module | 2 +- core/modules/block/block.services.yml | 2 +- .../block/config/schema/block.schema.yml | 6 + core/modules/block/js/block.js | 6 +- .../block/src/BlockAccessControlHandler.php | 85 +++++++- core/modules/block/src/BlockForm.php | 163 ++++++++++++++- core/modules/block/src/BlockInterface.php | 49 +++++ core/modules/block/src/BlockRepository.php | 12 +- .../block/src/BlockRepositoryInterface.php | 5 +- core/modules/block/src/Entity/Block.php | 97 ++++++++- .../src/Event/BlockConditionContextEvent.php | 39 ---- .../block/src/Event/BlockContextEvent.php | 53 +++++ core/modules/block/src/Event/BlockEvents.php | 12 +- .../BlockConditionContextSubscriberBase.php | 61 ------ .../BlockContextSubscriberBase.php | 75 +++++++ .../CurrentLanguageContext.php | 25 ++- .../EventSubscriber/CurrentUserContext.php | 14 +- .../src/EventSubscriber/NodeRouteContext.php | 17 +- .../DisplayVariant/BlockPageVariant.php | 24 ++- .../block/src/Tests/BlockInterfaceTest.php | 1 - .../block/src/Tests/BlockLanguageTest.php | 78 ++++++- .../block/src/Tests/BlockStorageUnitTest.php | 4 +- core/modules/block/src/Tests/BlockTest.php | 44 +++- .../config/install/block.block.test_block.yml | 2 +- .../block/tests/src/Unit/BlockFormTest.php | 68 +++++-- .../tests/src/Unit/BlockRepositoryTest.php | 110 ++++++++-- .../DisplayVariant/BlockPageVariantTest.php | 21 +- .../install/block.block.foobargorilla.yml | 10 +- .../image/src/Form/ImageStyleEditForm.php | 13 -- .../src/Plugin/Condition/Language.php | 8 +- .../LanguageBlockSettingsVisibilityTest.php | 8 +- .../src/Tests/d6/MigrateBlockTest.php | 24 +-- .../src/Tests/NodeBlockFunctionalTest.php | 3 + .../search/src/Tests/SearchBlockTest.php | 2 +- core/modules/simpletest/src/WebTestBase.php | 7 +- .../src/Unit/Plugin/Block/ViewsBlockTest.php | 8 - .../install/block.block.stark_admin.yml | 2 +- .../install/block.block.stark_login.yml | 2 +- .../install/block.block.stark_tools.yml | 2 +- .../block.block.bartik_account_menu.yml | 2 +- .../block.block.bartik_breadcrumbs.yml | 2 +- .../install/block.block.bartik_content.yml | 2 +- .../install/block.block.bartik_footer.yml | 2 +- .../install/block.block.bartik_help.yml | 2 +- .../install/block.block.bartik_login.yml | 2 +- .../install/block.block.bartik_main_menu.yml | 2 +- .../install/block.block.bartik_powered.yml | 2 +- .../install/block.block.bartik_search.yml | 2 +- .../install/block.block.bartik_tools.yml | 2 +- .../install/block.block.seven_breadcrumbs.yml | 2 +- .../install/block.block.seven_content.yml | 2 +- .../config/install/block.block.seven_help.yml | 2 +- .../install/block.block.seven_login.yml | 2 +- .../Drupal/Tests/Core/Block/BlockBaseTest.php | 57 ------ .../Tests/Core/Entity/EntityFormTest.php | 29 +++ .../Tests/Core/Plugin/ContextHandlerTest.php | 12 ++ 67 files changed, 1151 insertions(+), 548 deletions(-) create mode 100644 core/lib/Drupal/Core/Plugin/ContextAwarePluginAssignmentTrait.php delete mode 100644 core/modules/block/src/Event/BlockConditionContextEvent.php create mode 100644 core/modules/block/src/Event/BlockContextEvent.php delete mode 100644 core/modules/block/src/EventSubscriber/BlockConditionContextSubscriberBase.php create mode 100644 core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index ffd998869db9..4f6ef0cefc9b 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -300,12 +300,6 @@ block_settings: view_mode: type: string label: 'View mode' - visibility: - type: sequence - label: 'Visibility Conditions' - sequence: - - type: condition.plugin.[id] - label: 'Visibility Condition' provider: type: string label: 'Provider' diff --git a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php index e88cefa01470..2ba0a7ee4daf 100644 --- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php +++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php @@ -125,4 +125,28 @@ public function setContextValue($name, $value); */ 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); + } diff --git a/core/lib/Drupal/Core/Annotation/ContextDefinition.php b/core/lib/Drupal/Core/Annotation/ContextDefinition.php index 1a6b00afb885..987bab95af97 100644 --- a/core/lib/Drupal/Core/Annotation/ContextDefinition.php +++ b/core/lib/Drupal/Core/Annotation/ContextDefinition.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Annotation; use Drupal\Component\Annotation\Plugin; +use Drupal\Core\StringTranslation\TranslationWrapper; /** * @defgroup plugin_context Annotation for context definition @@ -94,9 +95,18 @@ public function __construct(array $values) { $values += array( 'required' => TRUE, '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']))) { throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.'); } diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php index 2be36701cc32..ba1026b55649 100644 --- a/core/lib/Drupal/Core/Block/BlockBase.php +++ b/core/lib/Drupal/Core/Block/BlockBase.php @@ -8,13 +8,7 @@ namespace Drupal\Core\Block; 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\Condition\ConditionAccessResolverTrait; -use Drupal\Core\Condition\ConditionPluginCollection; -use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\Component\Utility\Unicode; @@ -35,22 +29,6 @@ */ 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. * @@ -84,9 +62,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * {@inheritdoc} */ public function getConfiguration() { - return array( - 'visibility' => $this->getVisibilityConditions()->getConfiguration(), - ) + $this->configuration; + return $this->configuration; } /** @@ -107,13 +83,6 @@ public function setConfiguration(array $configuration) { * An associative array with the default configuration. */ 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( 'id' => $this->getPluginId(), 'label' => '', @@ -123,7 +92,6 @@ protected function baseConfigurationDefaults() { 'max_age' => 0, 'contexts' => array(), ), - 'visibility' => $visibility, ); } @@ -152,25 +120,10 @@ public function calculateDependencies() { * {@inheritdoc} */ public function access(AccountInterface $account) { - // @todo Add in a context mapping until the UI supports configuring them, - // see https://drupal.org/node/2284687. - $mappings['user_role']['current_user'] = 'user'; - - $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)) { + // @todo Remove self::blockAccess() and force individual plugins to return + // their own AccessResult logic. Until that is done in + // https://www.drupal.org/node/2375689 the access will be set uncacheable. + if ($this->blockAccess($account)) { $access = AccessResult::allowed(); } else { @@ -179,18 +132,6 @@ public function access(AccountInterface $account) { 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. * @@ -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['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. $form += $this->blockForm($form, $form_state); return $form; @@ -362,15 +256,6 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $contexts = $form_state->getValue(array('cache', '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); } @@ -394,14 +279,6 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s $this->configuration['label_display'] = $form_state->getValue('label_display'); $this->configuration['provider'] = $form_state->getValue('provider'); $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); } } @@ -503,61 +380,4 @@ public function isCacheable() { 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'); - } - } diff --git a/core/lib/Drupal/Core/Block/BlockPluginInterface.php b/core/lib/Drupal/Core/Block/BlockPluginInterface.php index 01f97adc98c6..f63a95589ecc 100644 --- a/core/lib/Drupal/Core/Block/BlockPluginInterface.php +++ b/core/lib/Drupal/Core/Block/BlockPluginInterface.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Block; -use Drupal\Component\Plugin\Context\ContextInterface; use Drupal\Component\Plugin\DerivativeInspectionInterface; use Drupal\Core\Cache\CacheableInterface; use Drupal\Component\Plugin\PluginInspectionInterface; @@ -143,35 +142,4 @@ public function blockSubmit($form, FormStateInterface $form_state); */ 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); - } diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php index ed28d69a73a2..ece4e205b53d 100644 --- a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php +++ b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php @@ -9,6 +9,7 @@ use Drupal\Core\Executable\ExecutablePluginBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait; /** * Provides a basis for fulfilling contexts for condition plugins. @@ -21,6 +22,8 @@ */ abstract class ConditionPluginBase extends ExecutablePluginBase implements ConditionInterface { + use ContextAwarePluginAssignmentTrait; + /** * {@inheritdoc} */ @@ -41,6 +44,9 @@ public function isNegated() { * {@inheritdoc} */ 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( '#type' => 'checkbox', '#title' => $this->t('Negate the condition'), diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php b/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php index c2e532f15893..97392fe421bd 100644 --- a/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php +++ b/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php @@ -41,6 +41,12 @@ public function getConfiguration() { $default_config = array(); $default_config['id'] = $instance_id; $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) { unset($configuration[$instance_id]); } diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php index 301b5ae3d2f3..a078aee36c11 100644 --- a/core/lib/Drupal/Core/Entity/EntityForm.php +++ b/core/lib/Drupal/Core/Entity/EntityForm.php @@ -291,10 +291,17 @@ public function buildEntity(array $form, FormStateInterface $form_state) { * The current state of the form. */ 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 // entities, in a different way. Consider moving this logic to a config // entity specific implementation. - foreach ($form_state->getValues() as $key => $value) { + foreach ($values as $key => $value) { $entity->set($key, $value); } } diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php index a5a5a3a6fa2e..019ad4e61e06 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Plugin\Context; -use Drupal\Component\Plugin\ConfigurablePluginInterface; use Drupal\Component\Plugin\ContextAwarePluginInterface; use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Component\Utility\String; @@ -73,12 +72,7 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface * {@inheritdoc} */ public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) { - if ($plugin instanceof ConfigurablePluginInterface) { - $configuration = $plugin->getConfiguration(); - if (isset($configuration['context_mapping'])) { - $mappings += array_flip($configuration['context_mapping']); - } - } + $mappings += $plugin->getContextMapping(); $plugin_contexts = $plugin->getContextDefinitions(); // Loop through each context and set it on the plugin if it matches one of // the contexts expected by the plugin. diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginAssignmentTrait.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginAssignmentTrait.php new file mode 100644 index 000000000000..57899ff88255 --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginAssignmentTrait.php @@ -0,0 +1,72 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait. + */ + +namespace Drupal\Core\Plugin; + +use Drupal\Component\Plugin\ContextAwarePluginInterface; + +/** + * Handles context assignments for context-aware plugins. + */ +trait ContextAwarePluginAssignmentTrait { + + /** + * Ensures the t() method is available. + * + * @see \Drupal\Core\StringTranslation\StringTranslationTrait + */ + abstract protected function t($string, array $args = array(), array $options = array()); + + /** + * Wraps the context handler. + * + * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface + */ + protected function contextHandler() { + return \Drupal::service('context.handler'); + } + + /** + * Builds a form element for assigning a context to a given slot. + * + * @param \Drupal\Component\Plugin\ContextAwarePluginInterface $plugin + * The context-aware plugin. + * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts + * An array of contexts. + * + * @return array + * A form element for assigning context. + */ + protected function addContextAssignmentElement(ContextAwarePluginInterface $plugin, array $contexts) { + $element = []; + foreach ($plugin->getContextDefinitions() as $context_slot => $definition) { + $valid_contexts = $this->contextHandler()->getMatchingContexts($contexts, $definition); + $options = []; + foreach ($valid_contexts as $context_id => $context) { + $element['#tree'] = TRUE; + $options[$context_id] = $context->getContextDefinition()->getLabel(); + $element[$context_slot] = [ + '#type' => 'value', + '#value' => $context_id, + ]; + } + + if (count($options) > 1) { + $assignments = $plugin->getContextMapping(); + $element[$context_slot] = [ + '#title' => $this->t('Select a @context value:', ['@context' => $context_slot]), + '#type' => 'select', + '#options' => $options, + '#required' => $definition->isRequired(), + '#default_value' => !empty($assignments[$context_slot]) ? $assignments[$context_slot] : '', + ]; + } + } + return $element; + } + +} diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php index 1fec19384259..3cfa67819d25 100644 --- a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Plugin; +use Drupal\Component\Plugin\ConfigurablePluginInterface; use Drupal\Component\Plugin\ContextAwarePluginBase as ComponentContextAwarePluginBase; use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Core\DependencyInjection\DependencySerializationTrait; @@ -49,4 +50,45 @@ public function setContext($name, ComponentContextInterface $context) { parent::setContext($name, $context); } + /** + * {@inheritdoc} + */ + public function getContextMapping() { + $configuration = $this instanceof ConfigurablePluginInterface ? $this->getConfiguration() : $this->configuration; + return isset($configuration['context_mapping']) ? array_flip($configuration['context_mapping']) : []; + } + + /** + * {@inheritdoc} + */ + public function setContextMapping(array $context_mapping) { + if ($this instanceof ConfigurablePluginInterface) { + $configuration = $this->getConfiguration(); + $configuration['context_mapping'] = $context_mapping; + $this->setConfiguration($configuration); + } + else { + $this->configuration['context_mapping'] = $context_mapping; + } + return $this; + } + + /** + * {@inheritdoc} + * + * @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface[] + */ + public function getContextDefinitions() { + return parent::getContextDefinitions(); + } + + /** + * Wraps the context handler. + * + * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface + */ + protected function contextHandler() { + return \Drupal::service('context.handler'); + } + } diff --git a/core/modules/block/block.module b/core/modules/block/block.module index a294dc806a31..6506ab208fe4 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -301,7 +301,7 @@ function block_configurable_language_delete(ConfigurableLanguageInterface $langu $visibility = $block->getVisibility(); if (isset($visibility['language']['langcodes'][$language->id()])) { unset($visibility['language']['langcodes'][$language->id()]); - $block->getPlugin()->setVisibilityConfig('language', $visibility['language']); + $block->setVisibilityConfig('language', $visibility['language']); $block->save(); } } diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml index 6442712a5cf0..df4d0d7f8121 100644 --- a/core/modules/block/block.services.yml +++ b/core/modules/block/block.services.yml @@ -24,4 +24,4 @@ services: - { name: 'event_subscriber' } block.repository: class: Drupal\block\BlockRepository - arguments: ['@entity.manager', '@theme.manager'] + arguments: ['@entity.manager', '@theme.manager', '@context.handler'] diff --git a/core/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml index c142f898f757..1e6b8235746a 100644 --- a/core/modules/block/config/schema/block.schema.yml +++ b/core/modules/block/config/schema/block.schema.yml @@ -24,6 +24,12 @@ block.block.*: label: 'Plugin' settings: type: block.settings.[%parent.plugin] + visibility: + type: sequence + label: 'Visibility Conditions' + sequence: + - type: condition.plugin.[id] + label: 'Visibility Condition' block.settings.*: type: block_settings diff --git a/core/modules/block/js/block.js b/core/modules/block/js/block.js index 3affe12afb15..c25842f20235 100644 --- a/core/modules/block/js/block.js +++ b/core/modules/block/js/block.js @@ -26,10 +26,10 @@ return vals.join(', '); } - $('#edit-settings-visibility-node-type, #edit-settings-visibility-language, #edit-settings-visibility-user-role').drupalSetSummary(checkboxesSummary); + $('#edit-visibility-node-type, #edit-visibility-language, #edit-visibility-user-role').drupalSetSummary(checkboxesSummary); - $('#edit-settings-visibility-request-path').drupalSetSummary(function (context) { - var $pages = $(context).find('textarea[name="settings[visibility][request_path][pages]"]'); + $('#edit-visibility-request-path').drupalSetSummary(function (context) { + var $pages = $(context).find('textarea[name="visibility[request_path][pages]"]'); if (!$pages.val()) { return Drupal.t('Not restricted'); } diff --git a/core/modules/block/src/BlockAccessControlHandler.php b/core/modules/block/src/BlockAccessControlHandler.php index 8bfa3656f463..82318c16c18e 100644 --- a/core/modules/block/src/BlockAccessControlHandler.php +++ b/core/modules/block/src/BlockAccessControlHandler.php @@ -7,23 +7,75 @@ namespace Drupal\block; +use Drupal\Component\Plugin\ContextAwarePluginInterface; +use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Condition\ConditionAccessResolverTrait; use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Executable\ExecutableManagerInterface; +use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Session\AccountInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines the access control handler for the block entity type. * * @see \Drupal\block\Entity\Block */ -class BlockAccessControlHandler extends EntityAccessControlHandler { +class BlockAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface { + + use ConditionAccessResolverTrait; + + /** + * The condition plugin manager. + * + * @var \Drupal\Core\Executable\ExecutableManagerInterface + */ + protected $manager; + + /** + * The plugin context handler. + * + * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface + */ + protected $contextHandler; + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('plugin.manager.condition'), + $container->get('context.handler') + ); + } + + /** + * Constructs the block access control handler instance + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager + * The ConditionManager for checking visibility of blocks. + * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler + * The ContextHandler for applying contexts to conditions properly. + */ + public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler) { + parent::__construct($entity_type); + $this->manager = $manager; + $this->contextHandler = $context_handler; + } + /** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { - /** @var $entity \Drupal\block\BlockInterface */ + /** @var \Drupal\block\BlockInterface $entity */ if ($operation != 'view') { return parent::checkAccess($entity, $operation, $langcode, $account); } @@ -33,8 +85,33 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A return AccessResult::forbidden()->cacheUntilEntityChanges($entity); } else { - // Delegate to the plugin. - return $entity->getPlugin()->access($account)->cacheUntilEntityChanges($entity); + $contexts = $entity->getContexts(); + $conditions = []; + foreach ($entity->getVisibilityConditions() as $condition_id => $condition) { + if ($condition instanceof ContextAwarePluginInterface) { + try { + $this->contextHandler->applyContextMapping($condition, $contexts); + } + catch (ContextException $e) { + return AccessResult::forbidden()->setCacheable(FALSE); + } + } + $conditions[$condition_id] = $condition; + } + if ($this->resolveConditions($conditions, 'and') !== FALSE) { + // Delegate to the plugin. + $access = $entity->getPlugin()->access($account); + } + else { + $access = AccessResult::forbidden(); + } + // 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. + // @todo Change this to use $access->cacheUntilEntityChanges($entity) once + // https://www.drupal.org/node/2375695 is resolved. + return $access->setCacheable(FALSE); } } diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php index 73e7ea4bb1f1..d2dc42ffeea1 100644 --- a/core/modules/block/src/BlockForm.php +++ b/core/modules/block/src/BlockForm.php @@ -7,11 +7,17 @@ namespace Drupal\block; +use Drupal\block\Event\BlockContextEvent; +use Drupal\block\Event\BlockEvents; +use Drupal\Component\Plugin\ContextAwarePluginInterface; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Executable\ExecutableManagerInterface; use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Provides form for block instance forms. @@ -32,14 +38,44 @@ class BlockForm extends EntityForm { */ protected $storage; + /** + * The condition plugin manager. + * + * @var \Drupal\Core\Condition\ConditionManager + */ + protected $manager; + + /** + * The event dispatcher service. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $dispatcher; + + /** + * The language manager service. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $language; + /** * Constructs a BlockForm object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager + * The ConditionManager for building the visibility UI. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * The EventDispatcher for gathering administrative contexts. + * @param \Drupal\Core\Language\LanguageManagerInterface $language + * The language manager. */ - public function __construct(EntityManagerInterface $entity_manager) { + public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, EventDispatcherInterface $dispatcher, LanguageManagerInterface $language) { $this->storage = $entity_manager->getStorage('block'); + $this->manager = $manager; + $this->dispatcher = $dispatcher; + $this->language = $language; } /** @@ -47,7 +83,10 @@ public function __construct(EntityManagerInterface $entity_manager) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager') + $container->get('entity.manager'), + $container->get('plugin.manager.condition'), + $container->get('event_dispatcher'), + $container->get('language_manager') ); } @@ -63,8 +102,15 @@ public function form(array $form, FormStateInterface $form_state) { } $form_state->set('block_theme', $theme); + // Store the gathered contexts in the form state for other objects to use + // during form building. + $temporary = $form_state->getTemporary(); + $temporary['gathered_contexts'] = $this->dispatcher->dispatch(BlockEvents::ADMINISTRATIVE_CONTEXT, new BlockContextEvent())->getContexts(); + $form_state->setTemporary($temporary); + $form['#tree'] = TRUE; $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state); + $form['visibility'] = $this->buildVisibilityInterface([], $form_state); // If creating a new block, calculate a safe default machine name. $form['id'] = array( @@ -132,6 +178,79 @@ public function themeSwitch($form, FormStateInterface $form_state) { return $form['region']; } + /** + * Helper function for building the visibility UI form. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form array with the visibility UI added in. + */ + protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) { + $form['visibility_tabs'] = [ + '#type' => 'vertical_tabs', + '#title' => $this->t('Visibility'), + '#parents' => ['visibility_tabs'], + '#attached' => [ + 'library' => [ + 'block/drupal.block', + ], + ], + ]; + // @todo Allow list of conditions to be configured in + // https://drupal.org/node/2284687. + $visibility = $this->entity->getVisibility(); + foreach ($this->manager->getDefinitions() as $condition_id => $definition) { + // Don't display the current theme condition. + if ($condition_id == 'current_theme') { + continue; + } + // Don't display the language condition until we have multiple languages. + if ($condition_id == 'language' && !$this->language->isMultilingual()) { + continue; + } + /** @var \Drupal\Core\Condition\ConditionInterface $condition */ + $condition = $this->manager->createInstance($condition_id, isset($visibility[$condition_id]) ? $visibility[$condition_id] : []); + $form_state->set(['conditions', $condition_id], $condition); + $condition_form = $condition->buildConfigurationForm([], $form_state); + $condition_form['#type'] = 'details'; + $condition_form['#title'] = $condition->getPluginDefinition()['label']; + $condition_form['#group'] = 'visibility_tabs'; + $form[$condition_id] = $condition_form; + } + + if (isset($form['node_type'])) { + $form['node_type']['#title'] = $this->t('Content types'); + $form['node_type']['bundles']['#title'] = $this->t('Content types'); + $form['node_type']['negate']['#type'] = 'value'; + $form['node_type']['negate']['#title_display'] = 'invisible'; + $form['node_type']['negate']['#value'] = $form['node_type']['negate']['#default_value']; + } + if (isset($form['user_role'])) { + $form['user_role']['#title'] = $this->t('Roles'); + unset($form['user_role']['roles']['#description']); + $form['user_role']['negate']['#type'] = 'value'; + $form['user_role']['negate']['#value'] = $form['user_role']['negate']['#default_value']; + } + if (isset($form['request_path'])) { + $form['request_path']['#title'] = $this->t('Pages'); + $form['request_path']['negate']['#type'] = 'radios'; + $form['request_path']['negate']['#title_display'] = 'invisible'; + $form['request_path']['negate']['#options'] = [ + $this->t('Show for the listed pages'), + $this->t('Hide for the listed pages'), + ]; + } + if (isset($form['language'])) { + $form['language']['negate']['#type'] = 'value'; + $form['language']['negate']['#value'] = $form['language']['negate']['#default_value']; + } + return $form; + } + /** * {@inheritdoc} */ @@ -154,6 +273,28 @@ public function validate(array $form, FormStateInterface $form_state) { $this->entity->getPlugin()->validateConfigurationForm($form, $settings); // Update the original form values. $form_state->setValue('settings', $settings->getValues()); + $this->validateVisibility($form, $form_state); + } + + /** + * Helper function to independently validate the visibility UI. + * + * @param array $form + * A nested array form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function validateVisibility(array $form, FormStateInterface $form_state) { + // Validate visibility condition settings. + foreach ($form_state->getValue('visibility') as $condition_id => $values) { + // Allow the condition to validate the form. + $condition = $form_state->get(['conditions', $condition_id]); + $condition_values = (new FormState()) + ->setValues($values); + $condition->validateConfigurationForm($form, $condition_values); + // Update the original form values. + $form_state->setValue(['visibility', $condition_id], $condition_values->getValues()); + } } /** @@ -173,6 +314,24 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Update the original form values. $form_state->setValue('settings', $settings->getValues()); + // Submit visibility condition settings. + foreach ($form_state->getValue('visibility') as $condition_id => $values) { + // Allow the condition to submit the form. + $condition = $form_state->get(['conditions', $condition_id]); + $condition_values = (new FormState()) + ->setValues($values); + $condition->submitConfigurationForm($form, $condition_values); + if ($condition instanceof ContextAwarePluginInterface) { + $context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : []; + $condition->setContextMapping($context_mapping); + } + // Update the original form values. + $condition_configuration = $condition->getConfiguration(); + $form_state->setValue(['visibility', $condition_id], $condition_configuration); + // Update the visibility conditions on the block. + $entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration); + } + // Save the settings of the plugin. $entity->save(); diff --git a/core/modules/block/src/BlockInterface.php b/core/modules/block/src/BlockInterface.php index 432e7994126d..d5dd321ea6c4 100644 --- a/core/modules/block/src/BlockInterface.php +++ b/core/modules/block/src/BlockInterface.php @@ -40,4 +40,53 @@ public function getPlugin(); */ public function getVisibility(); + /** + * 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); + + /** + * Get all available contexts. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of set contexts, keyed by context name. + */ + public function getContexts(); + + /** + * Set the contexts that are available for use within the block entity. + * + * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts + * An array of contexts to set on the block. + * + * @return $this + */ + public function setContexts(array $contexts); + } diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php index 7e17105fcd01..c11a4fe08020 100644 --- a/core/modules/block/src/BlockRepository.php +++ b/core/modules/block/src/BlockRepository.php @@ -8,6 +8,7 @@ namespace Drupal\block; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Theme\ThemeManagerInterface; /** @@ -36,10 +37,13 @@ class BlockRepository implements BlockRepositoryInterface { * The entity manager. * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager * The theme manager. + * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler + * The plugin context handler. */ - public function __construct(EntityManagerInterface $entity_manager, ThemeManagerInterface $theme_manager) { + public function __construct(EntityManagerInterface $entity_manager, ThemeManagerInterface $theme_manager, ContextHandlerInterface $context_handler) { $this->blockStorage = $entity_manager->getStorage('block'); $this->themeManager = $theme_manager; + $this->contextHandler = $context_handler; } /** @@ -65,13 +69,15 @@ protected function getTheme() { /** * {@inheritdoc} */ - public function getVisibleBlocksPerRegion() { + public function getVisibleBlocksPerRegion(array $contexts) { // Build an array of the region names in the right order. $empty = array_fill_keys(array_keys($this->getRegionNames()), array()); $full = array(); foreach ($this->blockStorage->loadByProperties(array('theme' => $this->getTheme())) as $block_id => $block) { - if ($block->access('view')) { + /** @var \Drupal\block\BlockInterface $block */ + // Set the contexts on the block before checking access. + if ($block->setContexts($contexts)->access('view')) { $full[$block->get('region')][$block_id] = $block; } } diff --git a/core/modules/block/src/BlockRepositoryInterface.php b/core/modules/block/src/BlockRepositoryInterface.php index 19cfb5ad0a33..082456f0d367 100644 --- a/core/modules/block/src/BlockRepositoryInterface.php +++ b/core/modules/block/src/BlockRepositoryInterface.php @@ -12,10 +12,13 @@ interface BlockRepositoryInterface { /** * Returns an array of regions and their block entities. * + * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts + * An array of contexts to set on the blocks. + * * @return array * The array is first keyed by region machine name, with the values * containing an array keyed by block ID, with block entities as the values. */ - public function getVisibleBlocksPerRegion(); + public function getVisibleBlocksPerRegion(array $contexts); } diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php index 6f7f27692caa..0edc9616fcba 100644 --- a/core/modules/block/src/Entity/Block.php +++ b/core/modules/block/src/Entity/Block.php @@ -8,6 +8,7 @@ namespace Drupal\block\Entity; use Drupal\Core\Cache\Cache; +use Drupal\Core\Condition\ConditionPluginCollection; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\block\BlockPluginCollection; use Drupal\block\BlockInterface; @@ -77,6 +78,13 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin */ protected $plugin; + /** + * The visibility settings for this block. + * + * @var array + */ + protected $visibility = []; + /** * The plugin collection that holds the block plugin for this entity. * @@ -84,6 +92,27 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin */ protected $pluginCollection; + /** + * The available contexts for this block and its visibility conditions. + * + * @var array + */ + protected $contexts = []; + + /** + * The visibility collection. + * + * @var \Drupal\Core\Condition\ConditionPluginCollection + */ + protected $visibilityCollection; + + /** + * The condition plugin manager. + * + * @var \Drupal\Core\Executable\ExecutableManagerInterface + */ + protected $conditionPluginManager; + /** * {@inheritdoc} */ @@ -108,7 +137,10 @@ protected function getPluginCollection() { * {@inheritdoc} */ public function getPluginCollections() { - return array('settings' => $this->getPluginCollection()); + return [ + 'settings' => $this->getPluginCollection(), + 'visibility' => $this->getVisibilityConditions(), + ]; } /** @@ -183,11 +215,72 @@ public function getCacheTags() { return Cache::mergeTags(parent::getCacheTags(), ['theme:' . $this->theme]); } + /** + * {@inheritdoc} + */ + public function setContexts(array $contexts) { + $this->contexts = $contexts; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContexts() { + return $this->contexts; + } + /** * {@inheritdoc} */ public function getVisibility() { - return $this->getPlugin()->getVisibilityConditions()->getConfiguration(); + return $this->getVisibilityConditions()->getConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function setVisibilityConfig($instance_id, array $configuration) { + $conditions = $this->getVisibilityConditions(); + if (!$conditions->has($instance_id)) { + $configuration['id'] = $instance_id; + $conditions->addInstanceId($instance_id, $configuration); + } + else { + $conditions->setInstanceConfiguration($instance_id, $configuration); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function getVisibilityConditions() { + if (!isset($this->visibilityCollection)) { + $this->visibilityCollection = new ConditionPluginCollection($this->conditionPluginManager(), $this->get('visibility')); + } + return $this->visibilityCollection; + } + + /** + * {@inheritdoc} + */ + public function getVisibilityCondition($instance_id) { + return $this->getVisibilityConditions()->get($instance_id); + } + + /** + * Gets the condition plugin manager. + * + * @return \Drupal\Core\Executable\ExecutableManagerInterface + * The condition plugin manager. + */ + protected function conditionPluginManager() { + $this->conditionPluginManager; + if (!isset($this->conditionPluginManager)) { + $this->conditionPluginManager = \Drupal::service('plugin.manager.condition'); + } + return $this->conditionPluginManager; } } diff --git a/core/modules/block/src/Event/BlockConditionContextEvent.php b/core/modules/block/src/Event/BlockConditionContextEvent.php deleted file mode 100644 index af155b72ad44..000000000000 --- a/core/modules/block/src/Event/BlockConditionContextEvent.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\block\Event\BlockContextEvent. - */ - -namespace Drupal\block\Event; - -use Drupal\Core\Condition\ConditionPluginCollection; -use Symfony\Component\EventDispatcher\Event; - -/** - * Wraps block conditions in order for event subscribers to add context. - * - * @see \Drupal\block\Event\BlockEvents::CONDITION_CONTEXT - */ -class BlockConditionContextEvent extends Event { - - /** - * @var \Drupal\Core\Condition\ConditionPluginCollection - */ - protected $conditions; - - /** - * @param \Drupal\Core\Condition\ConditionPluginCollection $conditions - */ - public function __construct(ConditionPluginCollection $conditions) { - $this->conditions = $conditions; - } - - /** - * @return \Drupal\Core\Block\BlockPluginInterface - */ - public function getConditions() { - return $this->conditions; - } - -} diff --git a/core/modules/block/src/Event/BlockContextEvent.php b/core/modules/block/src/Event/BlockContextEvent.php new file mode 100644 index 000000000000..99b0bbdde013 --- /dev/null +++ b/core/modules/block/src/Event/BlockContextEvent.php @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Contains \Drupal\block\Event\BlockContextEvent. + */ + +namespace Drupal\block\Event; + +use Drupal\Core\Plugin\Context\ContextInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Event subscribers can add context to be used by the block and its conditions. + * + * @see \Drupal\block\Event\BlockEvents::ACTIVE_CONTEXT + * @see \Drupal\block\Event\BlockEvents::ADMINISTRATIVE_CONTEXT + */ +class BlockContextEvent extends Event { + + /** + * The array of available contexts for blocks. + * + * @var array + */ + protected $contexts = []; + + /** + * Sets the context object for a given name. + * + * @param string $name + * The name to store the context object under. + * @param \Drupal\Core\Plugin\Context\ContextInterface $context + * The context object to set. + * + * @return $this + */ + public function setContext($name, ContextInterface $context) { + $this->contexts[$name] = $context; + return $this; + } + + /** + * Returns the context objects. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of contexts that have been provided. + */ + public function getContexts() { + return $this->contexts; + } + +} diff --git a/core/modules/block/src/Event/BlockEvents.php b/core/modules/block/src/Event/BlockEvents.php index 8d71c0702efc..22d3e90dc10d 100644 --- a/core/modules/block/src/Event/BlockEvents.php +++ b/core/modules/block/src/Event/BlockEvents.php @@ -16,8 +16,16 @@ final class BlockEvents { * Name of the event when gathering condition context for a block plugin. * * @see \Drupal\Core\Block\BlockBase::getConditionContexts() - * @see \Drupal\block\Event\BlockConditionContextEvent + * @see \Drupal\block\Event\BlockContextEvent */ - const CONDITION_CONTEXT = 'block.condition_context'; + const ACTIVE_CONTEXT = 'block.active_context'; + + /** + * Name of the event when gathering contexts for plugin configuration. + * + * @see \Drupal\block\BlockForm::form() + * @see \Drupal\block\Event\BlockContextEvent + */ + const ADMINISTRATIVE_CONTEXT = 'block.administrative_context'; } diff --git a/core/modules/block/src/EventSubscriber/BlockConditionContextSubscriberBase.php b/core/modules/block/src/EventSubscriber/BlockConditionContextSubscriberBase.php deleted file mode 100644 index 9fc2e87e34cd..000000000000 --- a/core/modules/block/src/EventSubscriber/BlockConditionContextSubscriberBase.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\block\EventSubscriber\BlockContextSubscriberBase. - */ - -namespace Drupal\block\EventSubscriber; - -use Drupal\block\Event\BlockConditionContextEvent; -use Drupal\block\Event\BlockEvents; -use Drupal\Component\Plugin\Context\ContextInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; - -/** - * Provides a base class for block context subscribers. - */ -abstract class BlockConditionContextSubscriberBase implements EventSubscriberInterface { - - /** - * @var \Drupal\Core\Condition\ConditionPluginCollection - */ - protected $conditions; - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events[BlockEvents::CONDITION_CONTEXT][] = 'onBlockConditionContext'; - return $events; - } - - /** - * Subscribes to the event and delegates to the subclass. - */ - public function onBlockConditionContext(BlockConditionContextEvent $event) { - $this->conditions = $event->getConditions(); - $this->determineBlockContext(); - } - - /** - * Determines the contexts for a given block. - */ - abstract protected function determineBlockContext(); - - /** - * Sets the condition context for a given name. - * - * @param string $name - * The name of the context. - * @param \Drupal\Component\Plugin\Context\ContextInterface $context - * The context to add. - * - * @return $this - */ - public function addContext($name, ContextInterface $context) { - $this->conditions->addContext($name, $context); - return $this; - } - -} diff --git a/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php b/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php new file mode 100644 index 000000000000..9707edc13998 --- /dev/null +++ b/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php @@ -0,0 +1,75 @@ +<?php + +/** + * @file + * Contains \Drupal\block\EventSubscriber\BlockContextSubscriberBase. + */ + +namespace Drupal\block\EventSubscriber; + +use Drupal\block\Event\BlockContextEvent; +use Drupal\block\Event\BlockEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Provides a base class for block context subscribers. + */ +abstract class BlockContextSubscriberBase implements EventSubscriberInterface { + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[BlockEvents::ACTIVE_CONTEXT][] = 'onBlockActiveContext'; + $events[BlockEvents::ADMINISTRATIVE_CONTEXT][] = 'onBlockAdministrativeContext'; + return $events; + } + + /** + * Determines the available run-time contexts. + * + * For blocks to render correctly, all of the contexts that they require + * must be populated with values. So this method must set a value for each + * context that it adds. For example: + * @code + * // Determine a specific node to pass as context to blocks. + * $node = ... + * + * // Set that specific node as the value of the 'node' context. + * $context = new Context(new ContextDefinition('entity:node')); + * $context->setContextValue($node); + * $event->setContext('node', $context); + * @endcode + * + * @param \Drupal\block\Event\BlockContextEvent $event + * The Event to which to register available contexts. + */ + abstract public function onBlockActiveContext(BlockContextEvent $event); + + /** + * Determines the available configuration-time contexts. + * + * When a block is being configured, the configuration UI must know which + * named contexts are potentially available, but does not care about the + * value, since the value can be different for each request, and might not + * be available at all during the configuration UI's request. + * + * For example: + * @code + * // During configuration, there is no specific node to pass as context. + * // However, inform the system that a context named 'node' is available, + * // and provide its definition, so that blocks can be configured to use + * // it. When the block is rendered, the value of this context will be + * // supplied by onBlockActiveContext(). + * $context = new Context(new ContextDefinition('entity:node')); + * $event->setContext('node', $context); + * @endcode + * + * @param \Drupal\block\Event\BlockContextEvent $event + * The Event to which to register available contexts. + * + * @see static::onBlockActiveContext() + */ + abstract public function onBlockAdministrativeContext(BlockContextEvent $event); + +} diff --git a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php index 458417295c0f..023f7ccfe3d8 100644 --- a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php +++ b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php @@ -7,6 +7,7 @@ namespace Drupal\block\EventSubscriber; +use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; @@ -15,7 +16,7 @@ /** * Sets the current language as a context. */ -class CurrentLanguageContext extends BlockConditionContextSubscriberBase { +class CurrentLanguageContext extends BlockContextSubscriberBase { use StringTranslationTrait; @@ -39,10 +40,24 @@ public function __construct(LanguageManagerInterface $language_manager) { /** * {@inheritdoc} */ - protected function determineBlockContext() { - $context = new Context(new ContextDefinition('language', $this->t('Current language'))); - $context->setContextValue($this->languageManager->getCurrentLanguage()); - $this->addContext('language', $context); + public function onBlockActiveContext(BlockContextEvent $event) { + // Add a context for each language type. + $language_types = $this->languageManager->getLanguageTypes(); + $info = $this->languageManager->getDefinedLanguageTypesInfo(); + foreach ($language_types as $type_key) { + if (isset($info[$type_key]['name'])) { + $context = new Context(new ContextDefinition('language', $info[$type_key]['name'])); + $context->setContextValue($this->languageManager->getCurrentLanguage($type_key)); + $event->setContext('language.' . $type_key, $context); + } + } + } + + /** + * {@inheritdoc} + */ + public function onBlockAdministrativeContext(BlockContextEvent $event) { + $this->onBlockActiveContext($event); } } diff --git a/core/modules/block/src/EventSubscriber/CurrentUserContext.php b/core/modules/block/src/EventSubscriber/CurrentUserContext.php index 2a5a19ebc851..cb70f3d5f776 100644 --- a/core/modules/block/src/EventSubscriber/CurrentUserContext.php +++ b/core/modules/block/src/EventSubscriber/CurrentUserContext.php @@ -7,6 +7,7 @@ namespace Drupal\block\EventSubscriber; +use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; @@ -16,7 +17,7 @@ /** * Sets the current user as a context. */ -class CurrentUserContext extends BlockConditionContextSubscriberBase { +class CurrentUserContext extends BlockContextSubscriberBase { use StringTranslationTrait; @@ -50,12 +51,19 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e /** * {@inheritdoc} */ - protected function determineBlockContext() { + public function onBlockActiveContext(BlockContextEvent $event) { $current_user = $this->userStorage->load($this->account->id()); $context = new Context(new ContextDefinition('entity:user', $this->t('Current user'))); $context->setContextValue($current_user); - $this->addContext('current_user', $context); + $event->setContext('user.current_user', $context); + } + + /** + * {@inheritdoc} + */ + public function onBlockAdministrativeContext(BlockContextEvent $event) { + $this->onBlockActiveContext($event); } } diff --git a/core/modules/block/src/EventSubscriber/NodeRouteContext.php b/core/modules/block/src/EventSubscriber/NodeRouteContext.php index c1bd2bf273e8..04eb339a7ec8 100644 --- a/core/modules/block/src/EventSubscriber/NodeRouteContext.php +++ b/core/modules/block/src/EventSubscriber/NodeRouteContext.php @@ -7,6 +7,7 @@ namespace Drupal\block\EventSubscriber; +use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Routing\RouteMatchInterface; @@ -15,7 +16,7 @@ /** * Sets the current node as a context on node routes. */ -class NodeRouteContext extends BlockConditionContextSubscriberBase { +class NodeRouteContext extends BlockContextSubscriberBase { /** * The route match object. @@ -37,20 +38,28 @@ public function __construct(RouteMatchInterface $route_match) { /** * {@inheritdoc} */ - protected function determineBlockContext() { + public function onBlockActiveContext(BlockContextEvent $event) { if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) { $context = new Context(new ContextDefinition($route_contexts['node']['type'])); if ($node = $this->routeMatch->getParameter('node')) { $context->setContextValue($node); } - $this->addContext('node', $context); + $event->setContext('node.node', $context); } elseif ($this->routeMatch->getRouteName() == 'node.add') { $node_type = $this->routeMatch->getParameter('node_type'); $context = new Context(new ContextDefinition('entity:node')); $context->setContextValue(Node::create(array('type' => $node_type->id()))); - $this->addContext('node', $context); + $event->setContext('node.node', $context); } } + /** + * {@inheritdoc} + */ + public function onBlockAdministrativeContext(BlockContextEvent $event) { + $context = new Context(new ContextDefinition('entity:node')); + $event->setContext('node', $context); + } + } diff --git a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php index fe78cb995ed8..895e2c627220 100644 --- a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php +++ b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php @@ -8,12 +8,15 @@ namespace Drupal\block\Plugin\DisplayVariant; use Drupal\block\BlockRepositoryInterface; +use Drupal\block\Event\BlockContextEvent; +use Drupal\block\Event\BlockEvents; use Drupal\Core\Block\MainContentBlockPluginInterface; use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Display\VariantBase; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Provides a page display variant that decorates the main content with blocks. @@ -66,11 +69,14 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont * The block repository. * @param \Drupal\Core\Entity\EntityViewBuilderInterface $block_view_builder * The block view builder. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * The event dispatcher. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->blockRepository = $block_repository; $this->blockViewBuilder = $block_view_builder; + $this->dispatcher = $dispatcher; } /** @@ -82,7 +88,8 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->get('block.repository'), - $container->get('entity.manager')->getViewBuilder('block') + $container->get('entity.manager')->getViewBuilder('block'), + $container->get('event_dispatcher') ); } @@ -102,8 +109,9 @@ public function build() { $main_content_block_displayed = FALSE; $build = array(); + $contexts = $this->getActiveBlockContexts(); // Load all region content assigned via blocks. - foreach ($this->blockRepository->getVisibleBlocksPerRegion() as $region => $blocks) { + foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $blocks) { /** @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $key => $block) { $block_plugin = $block->getPlugin(); @@ -130,4 +138,14 @@ public function build() { return $build; } + /** + * Returns an array of context objects to set on the blocks. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of contexts to set on the blocks. + */ + protected function getActiveBlockContexts() { + return $this->dispatcher->dispatch(BlockEvents::ACTIVE_CONTEXT, new BlockContextEvent())->getContexts(); + } + } diff --git a/core/modules/block/src/Tests/BlockInterfaceTest.php b/core/modules/block/src/Tests/BlockInterfaceTest.php index 7361f96f198e..3c7ecb0243b9 100644 --- a/core/modules/block/src/Tests/BlockInterfaceTest.php +++ b/core/modules/block/src/Tests/BlockInterfaceTest.php @@ -39,7 +39,6 @@ public function testBlockInterface() { 'label' => 'Custom Display Message', ); $expected_configuration = array( - 'visibility' => array(), 'id' => 'test_block_instantiation', 'label' => 'Custom Display Message', 'provider' => 'block_test', diff --git a/core/modules/block/src/Tests/BlockLanguageTest.php b/core/modules/block/src/Tests/BlockLanguageTest.php index 380c65c4e2d9..6f22e68b8a8e 100644 --- a/core/modules/block/src/Tests/BlockLanguageTest.php +++ b/core/modules/block/src/Tests/BlockLanguageTest.php @@ -28,7 +28,7 @@ class BlockLanguageTest extends WebTestBase { * * @var array */ - public static $modules = array('language', 'block'); + public static $modules = array('language', 'block', 'content_translation'); protected function setUp() { parent::setUp(); @@ -53,11 +53,12 @@ public function testLanguageBlockVisibility() { $default_theme = \Drupal::config('system.theme')->get('default'); $this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme); - $this->assertField('settings[visibility][language][langcodes][en]', 'Language visibility field is visible.'); + $this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.'); + $this->assertNoField('visibility[language][context_mapping][language]', 'Language type field is not visible.'); // Enable a standard block and set the visibility setting for one language. $edit = array( - 'settings[visibility][language][langcodes][en]' => TRUE, + 'visibility[language][langcodes][en]' => TRUE, 'id' => strtolower($this->randomMachineName(8)), 'region' => 'sidebar_first', ); @@ -86,7 +87,6 @@ public function testLanguageBlockVisibilityLanguageDelete() { $edit = array( 'visibility' => array( 'language' => array( - 'language_type' => 'language_interface', 'langcodes' => array( 'fr' => 'fr', ), @@ -97,8 +97,7 @@ public function testLanguageBlockVisibilityLanguageDelete() { // Check that we have the language in config after saving the setting. $visibility = $block->getVisibility(); - $language = $visibility['language']['langcodes']['fr']; - $this->assertTrue('fr' === $language, 'Language is set in the block configuration.'); + $this->assertEqual('fr', $visibility['language']['langcodes']['fr'], 'Language is set in the block configuration.'); // Delete the language. $this->drupalPostForm('admin/config/regional/language/delete/fr', array(), t('Delete')); @@ -108,6 +107,73 @@ public function testLanguageBlockVisibilityLanguageDelete() { $block = Block::load($block->id()); $visibility = $block->getVisibility(); $this->assertTrue(empty($visibility['language']['langcodes']['fr']), 'Language is no longer not set in the block configuration after deleting the block.'); + + // Ensure that the block visibility for language is gone from the UI. + $this->drupalGet('admin/structure/block'); + $this->clickLink('Configure'); + $elements = $this->xpath('//details[@id="edit-visibility-language"]'); + $this->assertTrue(empty($elements)); + } + + /** + * Tests block language visibility with different language types. + */ + public function testMultipleLanguageTypes() { + // Customize content language settings from their defaults. + $edit = [ + 'language_content[configurable]' => TRUE, + 'language_interface[enabled][language-url]' => FALSE, + 'language_interface[enabled][language-session]' => TRUE, + ]; + $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + + // Check if the visibility setting is available with a type setting. + $default_theme = \Drupal::config('system.theme')->get('default'); + $this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme); + $this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.'); + $this->assertField('visibility[language][context_mapping][language]', 'Language type field is visible.'); + + // Enable a standard block and set visibility to French only. + $block_id = strtolower($this->randomMachineName(8)); + $edit = [ + 'visibility[language][context_mapping][language]' => 'language.language_interface', + 'visibility[language][langcodes][fr]' => TRUE, + 'id' => $block_id, + 'region' => 'sidebar_first', + ]; + $this->drupalPostForm('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block')); + + // Interface negotiation depends on request arguments. + $this->drupalGet('node', ['query' => ['language' => 'en']]); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('node', ['query' => ['language' => 'fr']]); + $this->assertText('Powered by Drupal', 'The body of the block appears on the page.'); + + // Content language does not depend on session/request arguments. + // It will fall back on English (site default) and not display the block. + $this->drupalGet('en'); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('fr'); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + + // Change visibility to now depend on content language for this block. + $edit = [ + 'visibility[language][context_mapping][language]' => 'language.language_content' + ]; + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, $edit, t('Save block')); + + // Content language negotiation does not depend on request arguments. + // It will fall back on English (site default) and not display the block. + $this->drupalGet('node', ['query' => ['language' => 'en']]); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('node', ['query' => ['language' => 'fr']]); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + + // Content language negotiation depends on path prefix. + $this->drupalGet('en'); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('fr'); + $this->assertText('Powered by Drupal', 'The body of the block appears on the page.'); } } diff --git a/core/modules/block/src/Tests/BlockStorageUnitTest.php b/core/modules/block/src/Tests/BlockStorageUnitTest.php index 5fb4e8bedef2..6d0c9285fb2a 100644 --- a/core/modules/block/src/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/src/Tests/BlockStorageUnitTest.php @@ -26,7 +26,7 @@ class BlockStorageUnitTest extends DrupalUnitTestBase { * * @var array */ - public static $modules = array('block', 'block_test', 'system'); + public static $modules = array('block', 'block_test'); /** * The block storage. @@ -94,7 +94,6 @@ protected function createTests() { 'provider' => NULL, 'plugin' => 'test_html', 'settings' => array( - 'visibility' => array(), 'id' => 'test_html', 'label' => '', 'provider' => 'block_test', @@ -104,6 +103,7 @@ protected function createTests() { 'contexts' => array(), ), ), + 'visibility' => array(), ); $this->assertIdentical($actual_properties, $expected_properties); diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php index 881cd2bad1fb..3d9a348c2743 100644 --- a/core/modules/block/src/Tests/BlockTest.php +++ b/core/modules/block/src/Tests/BlockTest.php @@ -35,9 +35,9 @@ function testBlockVisibility() { ); // Set the block to be hidden on any user path, and to be shown only to // authenticated users. - $edit['settings[visibility][request_path][pages]'] = 'user*'; - $edit['settings[visibility][request_path][negate]'] = TRUE; - $edit['settings[visibility][user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; + $edit['visibility[request_path][pages]'] = 'user*'; + $edit['visibility[request_path][negate]'] = TRUE; + $edit['visibility[user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block')); $this->assertText('The block configuration has been saved.', 'Block was saved'); @@ -57,6 +57,42 @@ function testBlockVisibility() { $this->assertNoRaw('sidebar-first', 'Empty sidebar-first region is not displayed.'); } + /** + * Tests that visibility can be properly toggled. + */ + public function testBlockToggleVisibility() { + $block_name = 'system_powered_by_block'; + // Create a random title for the block. + $title = $this->randomMachineName(8); + // Enable a standard block. + $default_theme = \Drupal::config('system.theme')->get('default'); + $edit = array( + 'id' => strtolower($this->randomMachineName(8)), + 'region' => 'sidebar_first', + 'settings[label]' => $title, + ); + $block_id = $edit['id']; + // Set the block to be shown only to authenticated users. + $edit['visibility[user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; + $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block')); + $this->clickLink('Configure'); + $this->assertFieldChecked('edit-visibility-user-role-roles-authenticated'); + + $edit = [ + 'visibility[user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']' => FALSE, + ]; + $this->drupalPostForm(NULL, $edit, 'Save block'); + $this->clickLink('Configure'); + $this->assertNoFieldChecked('edit-visibility-user-role-roles-authenticated'); + + // Ensure that no visibility is configured. + /** @var \Drupal\block\BlockInterface $block */ + $block = Block::load($block_id); + $visibility_config = $block->getVisibilityConditions()->getConfiguration(); + $this->assertIdentical([], $visibility_config); + $this->assertIdentical([], $block->get('visibility')); + } + /** * Test block visibility when leaving "pages" textarea empty. */ @@ -70,7 +106,7 @@ function testBlockVisibilityListedEmpty() { 'id' => strtolower($this->randomMachineName(8)), 'region' => 'sidebar_first', 'settings[label]' => $title, - 'settings[visibility][request_path][negate]' => TRUE, + 'visibility[request_path][negate]' => TRUE, ); // Set the block to be hidden on any user path, and to be shown only to // authenticated users. diff --git a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml index 72510d029a02..f52790fcc7d0 100644 --- a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml +++ b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml @@ -6,7 +6,6 @@ langcode: en region: '-1' plugin: test_html settings: - visibility: { } label: 'Test HTML block' provider: block_test label_display: 'hidden' @@ -15,3 +14,4 @@ dependencies: - block_test theme: - stark +visibility: { } diff --git a/core/modules/block/tests/src/Unit/BlockFormTest.php b/core/modules/block/tests/src/Unit/BlockFormTest.php index 0860f9355f1e..4f563dbf3b42 100644 --- a/core/modules/block/tests/src/Unit/BlockFormTest.php +++ b/core/modules/block/tests/src/Unit/BlockFormTest.php @@ -16,6 +16,59 @@ */ class BlockFormTest extends UnitTestCase { + /** + * The condition plugin manager. + * + * @var \Drupal\Core\Executable\ExecutableManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $conditionManager; + + /** + * The block storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storage; + + /** + * The event dispatcher service. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dispatcher; + + /** + * The language manager service. + * + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $language; + + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $entityManager; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->conditionManager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface'); + $this->language = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + + $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $this->storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); + $this->entityManager->expects($this->any()) + ->method('getStorage') + ->will($this->returnValue($this->storage)); + + } + /** * Tests the unique machine name generator. * @@ -38,22 +91,11 @@ public function testGetUniqueMachineName() { ->method('execute') ->will($this->returnValue(array('test', 'other_test', 'other_test_1', 'other_test_2'))); - $block_storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); - $block_storage->expects($this->exactly(5)) + $this->storage->expects($this->exactly(5)) ->method('getQuery') ->will($this->returnValue($query)); - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); - - $entity_manager->expects($this->any()) - ->method('getStorage') - ->will($this->returnValue($block_storage)); - - $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); - - $config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface'); - - $block_form_controller = new BlockForm($entity_manager, $language_manager, $config_factory); + $block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->dispatcher, $this->language); // Ensure that the block with just one other instance gets the next available // name suggestion. diff --git a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php index 7f7aec1e9c1e..0c7e0b931436 100644 --- a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php +++ b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php @@ -7,6 +7,8 @@ namespace Drupal\Tests\block\Unit; +use Drupal\Component\Plugin\ContextAwarePluginInterface; +use Drupal\Core\Block\BlockPluginInterface; use Drupal\Tests\UnitTestCase; /** @@ -16,46 +18,77 @@ class BlockRepositoryTest extends UnitTestCase { /** - * Tests the retrieval of block entities. - * - * @covers ::getVisibleBlocksPerRegion - * - * @dataProvider providerBlocksConfig + * @var \Drupal\block\BlockRepository + */ + protected $blockRepository; + + /** + * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $blockStorage; + + /** + * @var string */ - function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) { - $theme = $this->randomMachineName(); + protected $theme; + + /** + * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $contextHandler; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); $active_theme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme') ->disableOriginalConstructor() ->getMock(); + $this->theme = $this->randomMachineName(); $active_theme->expects($this->atLeastOnce()) ->method('getName') - ->willReturn($theme); + ->willReturn($this->theme); + $theme_manager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface'); $theme_manager->expects($this->once()) ->method('getActiveTheme') ->will($this->returnValue($active_theme)); - $block_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $this->contextHandler = $this->getMock('Drupal\Core\Plugin\Context\ContextHandlerInterface'); + $this->blockStorage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); $entity_manager->expects($this->any()) ->method('getStorage') - ->willReturn($block_storage); + ->willReturn($this->blockStorage); - $block_repository = $this->getMockBuilder('Drupal\block\BlockRepository') - ->setConstructorArgs([$entity_manager, $theme_manager]) + $this->blockRepository = $this->getMockBuilder('Drupal\block\BlockRepository') + ->setConstructorArgs([$entity_manager, $theme_manager, $this->contextHandler]) ->setMethods(['getRegionNames']) ->getMock(); - $block_repository->expects($this->once()) + $this->blockRepository->expects($this->once()) ->method('getRegionNames') ->willReturn([ 'top' => 'Top', 'center' => 'Center', 'bottom' => 'Bottom', ]); + } + /** + * Tests the retrieval of block entities. + * + * @covers ::getVisibleBlocksPerRegion + * + * @dataProvider providerBlocksConfig + */ + public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) { $blocks = []; foreach ($blocks_config as $block_id => $block_config) { $block = $this->getMock('Drupal\block\BlockInterface'); + $block->expects($this->once()) + ->method('setContexts') + ->willReturnSelf(); $block->expects($this->once()) ->method('access') ->will($this->returnValue($block_config[0])); @@ -69,12 +102,12 @@ function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blo $blocks[$block_id] = $block; } - $block_storage->expects($this->once()) + $this->blockStorage->expects($this->once()) ->method('loadByProperties') - ->with(['theme' => $theme]) + ->with(['theme' => $this->theme]) ->willReturn($blocks); $result = []; - foreach ($block_repository->getVisibleBlocksPerRegion() as $region => $resulting_blocks) { + foreach ($this->blockRepository->getVisibleBlocksPerRegion([]) as $region => $resulting_blocks) { $result[$region] = []; foreach ($resulting_blocks as $plugin_id => $block) { $result[$region][] = $plugin_id; @@ -83,7 +116,6 @@ function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blo $this->assertSame($result, $expected_blocks); } - public function providerBlocksConfig() { $blocks_config = array( 'block1' => array( @@ -113,4 +145,48 @@ public function providerBlocksConfig() { return $test_cases; } + /** + * Tests the retrieval of block entities that are context-aware. + * + * @covers ::getVisibleBlocksPerRegion + */ + public function testGetVisibleBlocksPerRegionWithContext() { + $block = $this->getMock('Drupal\block\BlockInterface'); + $block->expects($this->once()) + ->method('setContexts') + ->willReturnSelf(); + $block->expects($this->once()) + ->method('access') + ->willReturn(TRUE); + $block->expects($this->once()) + ->method('get') + ->with('region') + ->willReturn('top'); + $blocks['block_id'] = $block; + + $contexts = []; + $this->blockStorage->expects($this->once()) + ->method('loadByProperties') + ->with(['theme' => $this->theme]) + ->willReturn($blocks); + $result = []; + foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $resulting_blocks) { + $result[$region] = []; + foreach ($resulting_blocks as $plugin_id => $block) { + $result[$region][] = $plugin_id; + } + } + $expected = [ + 'top' => [ + 'block_id', + ], + 'center' => [], + 'bottom' => [], + ]; + $this->assertSame($expected, $result); + } + +} + +interface TestContextAwareBlockInterface extends BlockPluginInterface, ContextAwarePluginInterface { } diff --git a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php index e16d133533f9..0ae38d4f7df8 100644 --- a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php +++ b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php @@ -30,18 +30,18 @@ class BlockPageVariantTest extends UnitTestCase { protected $blockViewBuilder; /** - * The current route match. + * The event dispatcher. * - * @var \Drupal\Core\Routing\RouteMatchInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $routeMatch; + protected $dispatcher; /** - * The theme negotiator. + * The plugin context handler. * - * @var \Drupal\Core\Theme\ThemeNegotiatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $themeNegotiator; + protected $contextHandler; /** * Sets up a display variant plugin for testing. @@ -57,10 +57,13 @@ class BlockPageVariantTest extends UnitTestCase { public function setUpDisplayVariant($configuration = array(), $definition = array()) { $this->blockRepository = $this->getMock('Drupal\block\BlockRepositoryInterface'); $this->blockViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface'); - $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); - $this->themeNegotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface'); + $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->dispatcher->expects($this->any()) + ->method('dispatch') + ->willReturnArgument(1); + $this->contextHandler = $this->getMock('Drupal\Core\Plugin\Context\ContextHandlerInterface'); return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant') - ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->routeMatch, $this->themeNegotiator)) + ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, $this->contextHandler)) ->setMethods(array('getRegionNames')) ->getMock(); } diff --git a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml index b8b7827fa1e7..a39088a4b6d2 100644 --- a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml +++ b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml @@ -12,11 +12,6 @@ weight: null provider: null plugin: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e' settings: - visibility: - request_path: - id: request_path - pages: '' - negate: false id: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e' label: 'Foobar Gorilla' provider: block_content @@ -27,3 +22,8 @@ settings: status: true info: '' view_mode: default +visibility: + request_path: + id: request_path + pages: '' + negate: false diff --git a/core/modules/image/src/Form/ImageStyleEditForm.php b/core/modules/image/src/Form/ImageStyleEditForm.php index 4edd31c79457..9c4887c06ec4 100644 --- a/core/modules/image/src/Form/ImageStyleEditForm.php +++ b/core/modules/image/src/Form/ImageStyleEditForm.php @@ -7,7 +7,6 @@ namespace Drupal\image\Form; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -286,16 +285,4 @@ protected function updateEffectWeights(array $effects) { } } - /** - * {@inheritdoc} - */ - protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { - foreach ($form_state->getValues() as $key => $value) { - // Do not copy effects here, see self::updateEffectWeights(). - if ($key != 'effects') { - $entity->set($key, $value); - } - } - } - } diff --git a/core/modules/language/src/Plugin/Condition/Language.php b/core/modules/language/src/Plugin/Condition/Language.php index 0f46dd5d2459..fa09f3b6adfb 100644 --- a/core/modules/language/src/Plugin/Condition/Language.php +++ b/core/modules/language/src/Plugin/Condition/Language.php @@ -31,23 +31,23 @@ class Language extends ConditionPluginBase { public function buildConfigurationForm(array $form, FormStateInterface $form_state) { if (\Drupal::languageManager()->isMultilingual()) { // Fetch languages. - $languages = language_list(LanguageInterface::STATE_CONFIGURABLE); + $languages = \Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE); $langcodes_options = array(); foreach ($languages as $language) { $langcodes_options[$language->getId()] = $language->getName(); } $form['langcodes'] = array( '#type' => 'checkboxes', - '#title' => t('Language selection'), + '#title' => $this->t('Language selection'), '#default_value' => $this->configuration['langcodes'], '#options' => $langcodes_options, - '#description' => t('Select languages to enforce. If none are selected, all languages will be allowed.'), + '#description' => $this->t('Select languages to enforce. If none are selected, all languages will be allowed.'), ); } else { $form['langcodes'] = array( '#type' => 'value', - '#value' => $this->configuration['langcodes'], + '#default_value' => $this->configuration['langcodes'], ); } return parent::buildConfigurationForm($form, $form_state); diff --git a/core/modules/language/src/Tests/LanguageBlockSettingsVisibilityTest.php b/core/modules/language/src/Tests/LanguageBlockSettingsVisibilityTest.php index e1f9cb9d3231..cac5b511df28 100644 --- a/core/modules/language/src/Tests/LanguageBlockSettingsVisibilityTest.php +++ b/core/modules/language/src/Tests/LanguageBlockSettingsVisibilityTest.php @@ -23,9 +23,9 @@ public function testUnnecessaryLanguageSettingsVisibility() { $this->drupalLogin($admin_user); $this->drupalPostForm('admin/config/regional/language/add', array('predefined_langcode' => 'hu'), t('Add language')); $this->drupalGet('admin/structure/block/add/system_menu_block:admin/stark'); - $this->assertNoFieldByXPath('//input[@id="edit-settings-visibility-language-langcodes-und"]', NULL, '\'Not specified\' option does not appear at block config, language settings section.'); - $this->assertNoFieldByXpath('//input[@id="edit-settings-visibility-language-langcodes-zxx"]', NULL, '\'Not applicable\' option does not appear at block config, language settings section.'); - $this->assertFieldByXPath('//input[@id="edit-settings-visibility-language-langcodes-en"]', NULL, '\'English\' option appears at block config, language settings section.'); - $this->assertFieldByXpath('//input[@id="edit-settings-visibility-language-langcodes-hu"]', NULL, '\'Hungarian\' option appears at block config, language settings section.'); + $this->assertNoFieldByXPath('//input[@id="edit-visibility-language-langcodes-und"]', NULL, '\'Not specified\' option does not appear at block config, language settings section.'); + $this->assertNoFieldByXpath('//input[@id="edit-visibility-language-langcodes-zxx"]', NULL, '\'Not applicable\' option does not appear at block config, language settings section.'); + $this->assertFieldByXPath('//input[@id="edit-visibility-language-langcodes-en"]', NULL, '\'English\' option appears at block config, language settings section.'); + $this->assertFieldByXpath('//input[@id="edit-visibility-language-langcodes-hu"]', NULL, '\'Hungarian\' option appears at block config, language settings section.'); } } diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php index 4b15b05e6aa9..fb7f6d9c5a5d 100644 --- a/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php +++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php @@ -91,8 +91,7 @@ public function testBlockMigration() { $this->assertEqual('sidebar_first', $test_block_user->get('region')); $this->assertEqual('bartik', $test_block_user->get('theme')); $visibility = $test_block_user->getVisibility(); - $this->assertEqual(TRUE, $visibility['request_path']['negate']); - $this->assertEqual('', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(0, $test_block_user->weight); $test_block_user_1 = $blocks['user_1']; @@ -100,8 +99,7 @@ public function testBlockMigration() { $this->assertEqual('sidebar_first', $test_block_user_1->get('region')); $this->assertEqual('bartik', $test_block_user_1->get('theme')); $visibility = $test_block_user_1->getVisibility(); - $this->assertEqual(TRUE, $visibility['request_path']['negate']); - $this->assertEqual('', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(0, $test_block_user_1->weight); // Check system block @@ -110,8 +108,7 @@ public function testBlockMigration() { $this->assertEqual('footer', $test_block_system->get('region')); $this->assertEqual('bartik', $test_block_system->get('theme')); $visibility = $test_block_system->getVisibility(); - $this->assertEqual(TRUE, $visibility['request_path']['negate']); - $this->assertEqual('', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(-5, $test_block_system->weight); // Check menu blocks @@ -120,8 +117,7 @@ public function testBlockMigration() { $this->assertEqual('header', $test_block_menu->get('region')); $this->assertEqual('bartik', $test_block_menu->get('theme')); $visibility = $test_block_menu->getVisibility(); - $this->assertEqual(TRUE, $visibility['request_path']['negate']); - $this->assertEqual('', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(-5, $test_block_menu->weight); // Check custom blocks @@ -130,8 +126,7 @@ public function testBlockMigration() { $this->assertEqual('content', $test_block_block->get('region')); $this->assertEqual('bartik', $test_block_block->get('theme')); $visibility = $test_block_block->getVisibility(); - $this->assertEqual(FALSE, $visibility['request_path']['negate']); - $this->assertEqual('<front>', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(0, $test_block_block->weight); $test_block_block_1 = $blocks['block_1']; @@ -139,8 +134,7 @@ public function testBlockMigration() { $this->assertEqual('right', $test_block_block_1->get('region')); $this->assertEqual('bluemarine', $test_block_block_1->get('theme')); $visibility = $test_block_block_1->getVisibility(); - $this->assertEqual(FALSE, $visibility['request_path']['negate']); - $this->assertEqual('node', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(-4, $test_block_block_1->weight); $test_block_block_2 = $blocks['block_2']; @@ -148,8 +142,7 @@ public function testBlockMigration() { $this->assertEqual('right', $test_block_block_2->get('region')); $this->assertEqual('test_theme', $test_block_block_2->get('theme')); $visibility = $test_block_block_2->getVisibility(); - $this->assertEqual(TRUE, $visibility['request_path']['negate']); - $this->assertEqual('', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(-7, $test_block_block_2->weight); $test_block_block_3 = $blocks['block_3']; @@ -157,8 +150,7 @@ public function testBlockMigration() { $this->assertEqual('left', $test_block_block_3->get('region')); $this->assertEqual('test_theme', $test_block_block_3->get('theme')); $visibility = $test_block_block_3->getVisibility(); - $this->assertEqual(TRUE, $visibility['request_path']['negate']); - $this->assertEqual('', $visibility['request_path']['pages']); + $this->assertTrue(empty($visibility['request_path'])); $this->assertEqual(-2, $test_block_block_3->weight); } } diff --git a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php index 9bc50544aac7..2ff5981e9b99 100644 --- a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php +++ b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php @@ -117,6 +117,9 @@ public function testRecentNodeBlock() { $block = $this->drupalPlaceBlock('system_powered_by_block', array( 'visibility' => array( 'node_type' => array( + 'context_mapping' => array( + 'node' => 'node.node', + ), 'bundles' => array( 'article' => 'article', ), diff --git a/core/modules/search/src/Tests/SearchBlockTest.php b/core/modules/search/src/Tests/SearchBlockTest.php index cdc79057aa76..9aca60fe1261 100644 --- a/core/modules/search/src/Tests/SearchBlockTest.php +++ b/core/modules/search/src/Tests/SearchBlockTest.php @@ -59,7 +59,7 @@ protected function testSearchFormBlock() { $visibility = $block->getVisibility(); $visibility['request_path']['pages'] = 'search'; - $block->getPlugin()->setVisibilityConfig('request_path', $visibility['request_path']); + $block->setVisibilityConfig('request_path', $visibility['request_path']); $this->submitGetForm('', $terms, t('Search')); $this->assertResponse(200); diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 9eda0a0900c9..809a145b00c5 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -382,13 +382,14 @@ protected function drupalPlaceBlock($plugin_id, array $settings = array()) { 'max_age' => 0, ), ); - foreach (array('region', 'id', 'theme', 'plugin', 'weight') as $key) { + $values = []; + foreach (array('region', 'id', 'theme', 'plugin', 'weight', 'visibility') as $key) { $values[$key] = $settings[$key]; // Remove extra values that do not belong in the settings array. unset($settings[$key]); } - foreach ($settings['visibility'] as $id => $visibility) { - $settings['visibility'][$id]['id'] = $id; + foreach ($values['visibility'] as $id => $visibility) { + $values['visibility'][$id]['id'] = $id; } $values['settings'] = $settings; $block = entity_create('block', $values); diff --git a/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php b/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php index c26ee664760e..2c03f23680ad 100644 --- a/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php @@ -126,10 +126,6 @@ public function testBuild() { $definition['provider'] = 'views'; $plugin = new ViewsBlock($config, $block_id, $definition, $this->executableFactory, $this->storage, $this->account); - $reflector = new \ReflectionClass($plugin); - $property = $reflector->getProperty('conditionPluginManager'); - $property->setAccessible(TRUE); - $property->setValue($plugin, $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface')); $this->assertEquals($build, $plugin->build()); } @@ -152,10 +148,6 @@ public function testBuildFailed() { $definition['provider'] = 'views'; $plugin = new ViewsBlock($config, $block_id, $definition, $this->executableFactory, $this->storage, $this->account); - $reflector = new \ReflectionClass($plugin); - $property = $reflector->getProperty('conditionPluginManager'); - $property->setAccessible(TRUE); - $property->setValue($plugin, $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface')); $this->assertEquals(array(), $plugin->build()); } diff --git a/core/profiles/minimal/config/install/block.block.stark_admin.yml b/core/profiles/minimal/config/install/block.block.stark_admin.yml index 0f3fdd5e335b..ffbc2fb463c2 100644 --- a/core/profiles/minimal/config/install/block.block.stark_admin.yml +++ b/core/profiles/minimal/config/install/block.block.stark_admin.yml @@ -6,7 +6,6 @@ langcode: en region: sidebar_first plugin: 'system_menu_block:admin' settings: - visibility: { } label: Administration provider: system label_display: visible @@ -22,3 +21,4 @@ dependencies: - system theme: - stark +visibility: { } diff --git a/core/profiles/minimal/config/install/block.block.stark_login.yml b/core/profiles/minimal/config/install/block.block.stark_login.yml index 9081e1afde07..41eba3ff30c6 100644 --- a/core/profiles/minimal/config/install/block.block.stark_login.yml +++ b/core/profiles/minimal/config/install/block.block.stark_login.yml @@ -6,7 +6,6 @@ langcode: en region: sidebar_first plugin: user_login_block settings: - visibility: { } label: 'User login' provider: user label_display: visible @@ -15,3 +14,4 @@ dependencies: - user theme: - stark +visibility: { } diff --git a/core/profiles/minimal/config/install/block.block.stark_tools.yml b/core/profiles/minimal/config/install/block.block.stark_tools.yml index ef8442b08aef..aa82311faea4 100644 --- a/core/profiles/minimal/config/install/block.block.stark_tools.yml +++ b/core/profiles/minimal/config/install/block.block.stark_tools.yml @@ -6,7 +6,6 @@ langcode: en region: sidebar_first plugin: 'system_menu_block:tools' settings: - visibility: { } label: Tools provider: system label_display: visible @@ -22,3 +21,4 @@ dependencies: - system theme: - stark +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_account_menu.yml b/core/profiles/standard/config/install/block.block.bartik_account_menu.yml index 14637599b0a3..db8f8ec97f4c 100644 --- a/core/profiles/standard/config/install/block.block.bartik_account_menu.yml +++ b/core/profiles/standard/config/install/block.block.bartik_account_menu.yml @@ -6,7 +6,6 @@ langcode: en region: secondary_menu plugin: 'system_menu_block:account' settings: - visibility: { } id: 'system_menu_block:account' label: 'User account menu' provider: system @@ -23,3 +22,4 @@ dependencies: - system theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml b/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml index 2128a6149be2..c89b7280d0ec 100644 --- a/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml +++ b/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml @@ -6,7 +6,6 @@ langcode: en region: '-1' plugin: system_breadcrumb_block settings: - visibility: { } id: system_breadcrumb_block label: Breadcrumbs provider: system @@ -16,3 +15,4 @@ dependencies: - system theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_content.yml b/core/profiles/standard/config/install/block.block.bartik_content.yml index d30a7f30cf52..87d320faf379 100644 --- a/core/profiles/standard/config/install/block.block.bartik_content.yml +++ b/core/profiles/standard/config/install/block.block.bartik_content.yml @@ -6,7 +6,6 @@ langcode: en region: content plugin: system_main_block settings: - visibility: { } id: system_main_block label: 'Main page content' provider: system @@ -16,3 +15,4 @@ dependencies: - system theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_footer.yml b/core/profiles/standard/config/install/block.block.bartik_footer.yml index 41327360fb57..43817fda1e44 100644 --- a/core/profiles/standard/config/install/block.block.bartik_footer.yml +++ b/core/profiles/standard/config/install/block.block.bartik_footer.yml @@ -6,7 +6,6 @@ langcode: en region: footer plugin: 'system_menu_block:footer' settings: - visibility: { } id: 'system_menu_block:footer' label: 'Footer menu' provider: system @@ -23,3 +22,4 @@ dependencies: - system theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_help.yml b/core/profiles/standard/config/install/block.block.bartik_help.yml index 3a2f48a5f703..941fa1bf8399 100644 --- a/core/profiles/standard/config/install/block.block.bartik_help.yml +++ b/core/profiles/standard/config/install/block.block.bartik_help.yml @@ -6,7 +6,6 @@ langcode: en region: help plugin: system_help_block settings: - visibility: { } id: system_help_block label: 'System Help' provider: system @@ -16,3 +15,4 @@ dependencies: - system theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_login.yml b/core/profiles/standard/config/install/block.block.bartik_login.yml index 6d58c23bcea4..3a5f5433fd6d 100644 --- a/core/profiles/standard/config/install/block.block.bartik_login.yml +++ b/core/profiles/standard/config/install/block.block.bartik_login.yml @@ -6,7 +6,6 @@ langcode: en region: sidebar_first plugin: user_login_block settings: - visibility: { } id: user_login_block label: 'User login' provider: user @@ -16,3 +15,4 @@ dependencies: - user theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_main_menu.yml b/core/profiles/standard/config/install/block.block.bartik_main_menu.yml index 7f847c285184..3da0571481b0 100644 --- a/core/profiles/standard/config/install/block.block.bartik_main_menu.yml +++ b/core/profiles/standard/config/install/block.block.bartik_main_menu.yml @@ -6,7 +6,6 @@ langcode: en region: primary_menu plugin: 'system_menu_block:main' settings: - visibility: {} id: 'system_menu_block:main' label: 'Main navigation' provider: system @@ -23,3 +22,4 @@ dependencies: - system theme: - bartik +visibility: {} diff --git a/core/profiles/standard/config/install/block.block.bartik_powered.yml b/core/profiles/standard/config/install/block.block.bartik_powered.yml index 106c2791a78f..5a9881f1562a 100644 --- a/core/profiles/standard/config/install/block.block.bartik_powered.yml +++ b/core/profiles/standard/config/install/block.block.bartik_powered.yml @@ -6,7 +6,6 @@ langcode: en region: footer plugin: system_powered_by_block settings: - visibility: { } id: system_powered_by_block label: 'Powered by Drupal' provider: system @@ -16,3 +15,4 @@ dependencies: - system theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_search.yml b/core/profiles/standard/config/install/block.block.bartik_search.yml index ac4130a32bc7..af99dea99c14 100644 --- a/core/profiles/standard/config/install/block.block.bartik_search.yml +++ b/core/profiles/standard/config/install/block.block.bartik_search.yml @@ -6,7 +6,6 @@ langcode: en region: sidebar_first plugin: search_form_block settings: - visibility: { } id: search_form_block label: Search provider: search @@ -16,3 +15,4 @@ dependencies: - search theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_tools.yml b/core/profiles/standard/config/install/block.block.bartik_tools.yml index 4b102ebe5076..aa24e61d28c0 100644 --- a/core/profiles/standard/config/install/block.block.bartik_tools.yml +++ b/core/profiles/standard/config/install/block.block.bartik_tools.yml @@ -6,7 +6,6 @@ langcode: en region: sidebar_first plugin: 'system_menu_block:tools' settings: - visibility: { } id: 'system_menu_block:tools' label: Tools provider: system @@ -23,3 +22,4 @@ dependencies: - system theme: - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml b/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml index cd7d7acbf8c0..8ce13d8e990e 100644 --- a/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml +++ b/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml @@ -6,7 +6,6 @@ langcode: en region: '-1' plugin: system_breadcrumb_block settings: - visibility: { } id: system_breadcrumb_block label: Breadcrumbs provider: system @@ -16,3 +15,4 @@ dependencies: - system theme: - seven +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.seven_content.yml b/core/profiles/standard/config/install/block.block.seven_content.yml index 12c2c5fa0d03..dd2bfbec584b 100644 --- a/core/profiles/standard/config/install/block.block.seven_content.yml +++ b/core/profiles/standard/config/install/block.block.seven_content.yml @@ -6,7 +6,6 @@ langcode: en region: content plugin: system_main_block settings: - visibility: { } id: system_main_block label: 'Main page content' provider: system @@ -16,3 +15,4 @@ dependencies: - system theme: - seven +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.seven_help.yml b/core/profiles/standard/config/install/block.block.seven_help.yml index 1cf9fa315cd4..168e7f00374e 100644 --- a/core/profiles/standard/config/install/block.block.seven_help.yml +++ b/core/profiles/standard/config/install/block.block.seven_help.yml @@ -6,7 +6,6 @@ langcode: en region: help plugin: system_help_block settings: - visibility: { } id: system_help_block label: 'System Help' provider: system @@ -16,3 +15,4 @@ dependencies: - system theme: - seven +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.seven_login.yml b/core/profiles/standard/config/install/block.block.seven_login.yml index 321d92b4d7b8..0a488a798f90 100644 --- a/core/profiles/standard/config/install/block.block.seven_login.yml +++ b/core/profiles/standard/config/install/block.block.seven_login.yml @@ -6,7 +6,6 @@ langcode: en region: content plugin: user_login_block settings: - visibility: { } id: user_login_block label: 'User login' provider: user @@ -16,3 +15,4 @@ dependencies: - user theme: - seven +visibility: { } diff --git a/core/tests/Drupal/Tests/Core/Block/BlockBaseTest.php b/core/tests/Drupal/Tests/Core/Block/BlockBaseTest.php index 028ea737ca7b..88be05008fef 100644 --- a/core/tests/Drupal/Tests/Core/Block/BlockBaseTest.php +++ b/core/tests/Drupal/Tests/Core/Block/BlockBaseTest.php @@ -8,7 +8,6 @@ namespace Drupal\Tests\Core\Block; use Drupal\block_test\Plugin\Block\TestBlockInstantiation; -use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Tests\UnitTestCase; /** @@ -28,14 +27,6 @@ public function testGetMachineNameSuggestion() { ->setMethods(array('readLanguageOverrides')) ->getMock(); - $condition_plugin_manager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface'); - $condition_plugin_manager->expects($this->atLeastOnce()) - ->method('getDefinitions') - ->will($this->returnValue(array())); - $container = new ContainerBuilder(); - $container->set('plugin.manager.condition', $condition_plugin_manager); - \Drupal::setContainer($container); - $config = array(); $definition = array( 'admin_label' => 'Admin label', @@ -55,52 +46,4 @@ public function testGetMachineNameSuggestion() { $this->assertEquals('uberawesome', $block_base->getMachineNameSuggestion()); } - /** - * Tests initializing the condition plugins initialization. - */ - public function testConditionsBagInitialization() { - $plugin_manager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface'); - $plugin_manager->expects($this->once()) - ->method('getDefinitions') - ->will($this->returnValue(array( - 'request_path' => array( - 'id' => 'request_path', - ), - 'user_role' => array( - 'id' => 'user_role', - ), - 'node_type' => array( - 'id' => 'node_type', - ), - 'language' => array( - 'id' => 'language', - ), - ))); - $container = new ContainerBuilder(); - $container->set('plugin.manager.condition', $plugin_manager); - \Drupal::setContainer($container); - $config = array(); - $definition = array( - 'admin_label' => 'Admin label', - 'provider' => 'block_test', - ); - - $block_base = new TestBlockInstantiation($config, 'test_block_instantiation', $definition); - $conditions_collection = $block_base->getVisibilityConditions(); - - $this->assertEquals(4, $conditions_collection->count(), "There are 4 condition plugins"); - - $instance_id = $this->randomMachineName(); - $pages = 'node/1'; - $condition_config = array('id' => 'request_path', 'pages' => $pages); - $block_base->setVisibilityConfig($instance_id, $condition_config); - - $plugin_manager->expects($this->once())->method('createInstance') - ->withAnyParameters()->will($this->returnValue('test')); - - $condition = $block_base->getVisibilityCondition($instance_id); - - $this->assertEquals('test', $condition, "The correct condition is returned."); - } - } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php index 35f891378f87..3586df971a94 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\Core\Entity; use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Form\FormState; use Drupal\Tests\UnitTestCase; /** @@ -94,4 +95,32 @@ public function providerTestFormIds() { ); } + /** + * @covers ::copyFormValuesToEntity + */ + public function testCopyFormValuesToEntity() { + $entity_id = 'test_config_entity_id'; + $values = ['id' => $entity_id]; + $entity = $this->getMockBuilder('\Drupal\Tests\Core\Config\Entity\Fixtures\ConfigEntityBaseWithPluginCollections') + ->setConstructorArgs([$values, 'test_config_entity']) + ->setMethods(['getPluginCollections']) + ->getMock(); + $entity->expects($this->atLeastOnce()) + ->method('getPluginCollections') + ->willReturn(['key_controlled_by_plugin_collection' => NULL]); + $this->entityForm->setEntity($entity); + + $form_state = (new FormState())->setValues([ + 'regular_key' => 'foo', + 'key_controlled_by_plugin_collection' => 'bar', + ]); + $result = $this->entityForm->buildEntity([], $form_state); + + $this->assertSame($entity_id, $result->id()); + // The regular key should have a value, but the one controlled by a plugin + // collection should not have been set. + $this->assertSame('foo', $result->get('regular_key')); + $this->assertNull($result->get('key_controlled_by_plugin_collection')); + } + } diff --git a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php index 0122f495741d..b586c0250695 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php @@ -243,6 +243,9 @@ public function testApplyContextMapping() { ); $plugin = $this->getMock('Drupal\Component\Plugin\ContextAwarePluginInterface'); + $plugin->expects($this->once()) + ->method('getContextMapping') + ->willReturn([]); $plugin->expects($this->once()) ->method('getContextDefinitions') ->will($this->returnValue(array('hit' => 'hit'))); @@ -266,6 +269,9 @@ public function testApplyContextMappingConfigurable() { ); $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface'); + $plugin->expects($this->once()) + ->method('getContextMapping') + ->willReturn([]); $plugin->expects($this->once()) ->method('getContextDefinitions') ->will($this->returnValue(array('hit' => 'hit'))); @@ -289,6 +295,9 @@ public function testApplyContextMappingConfigurableAssigned() { ); $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface'); + $plugin->expects($this->once()) + ->method('getContextMapping') + ->willReturn([]); $plugin->expects($this->once()) ->method('getContextDefinitions') ->will($this->returnValue(array('hit' => 'hit'))); @@ -315,6 +324,9 @@ public function testApplyContextMappingConfigurableAssignedMiss() { ); $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface'); + $plugin->expects($this->once()) + ->method('getContextMapping') + ->willReturn([]); $plugin->expects($this->once()) ->method('getContextDefinitions') ->will($this->returnValue(array('hit' => 'hit'))); -- GitLab