trait ContextHandlerTrait

Provides methods for handling context based on the plugin configuration.

The trait requires the plugin to use configuration as defined by the ContextConfig class.

Hierarchy

See also

\Drupal\rules\Context\ContextConfig

1 file declares its use of ContextHandlerTrait
ContextHandlerTraitTest.php in tests/src/Unit/ContextHandlerTraitTest.php

File

src/Context/ContextHandlerTrait.php, line 18

Namespace

Drupal\rules\Context
View source
trait ContextHandlerTrait {
  
  /**
   * The data processor plugin manager used to process context variables.
   *
   * @var \Drupal\rules\Context\DataProcessorManager
   */
  protected $processorManager;
  
  /**
   * Prepares plugin context based upon the set context configuration.
   *
   * The plugin is prepared for execution by mapping the variables from the
   * execution state into the plugin context and applying data processors.
   * In addition, it is ensured that all required context is basically
   * available as defined. This include the following checks:
   *  - Required context must have a value set.
   *  - Context may not have NULL values unless the plugin allows it.
   *
   * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
   *   The plugin that is populated with context values.
   * @param \Drupal\rules\Context\ExecutionStateInterface $state
   *   The execution state containing available variables.
   *
   * @throws \Drupal\rules\Exception\EvaluationException
   *   Thrown if some context is not satisfied; e.g. a required context is
   *   missing.
   *
   * @see ::prepareContextWithMetadata()
   */
  protected function prepareContext(CoreContextAwarePluginInterface $plugin, ExecutionStateInterface $state) {
    if (isset($this->configuration['context_values'])) {
      foreach ($this->configuration['context_values'] as $name => $value) {
        $plugin->setContextValue($name, $value);
      }
    }
    $selected_data = [];
    // Map context by applying data selectors and collected the definitions of
    // selected data for refining context definitions later. Note, that we must
    // refine context definitions on execution time also, such that provided
    // context gets the right metadata attached.
    if (isset($this->configuration['context_mapping'])) {
      foreach ($this->configuration['context_mapping'] as $name => $selector) {
        $typed_data = $state->fetchDataByPropertyPath($selector);
        $plugin->setContextValue($name, $typed_data);
        $selected_data[$name] = $typed_data->getDataDefinition();
      }
    }
    if ($plugin instanceof ContextAwarePluginInterface) {
      // Getting context values may lead to undocumented exceptions if context
      // is not set right now. So catch those exceptions.
      // @todo Remove once https://www.drupal.org/node/2677162 is fixed in core.
      try {
        $plugin->refineContextDefinitions($selected_data);
      } catch (ContextException $e) {
        if (strpos($e->getMessage(), 'context is required') === FALSE) {
          throw new EvaluationException($e->getMessage());
        }
      }
    }
    // Apply data processors.
    $this->processData($plugin, $state);
    // Finally, ensure all contexts are set as expected now.
    foreach ($plugin->getContextDefinitions() as $name => $definition) {
      if ($plugin->getContextValue($name) === NULL && $definition->isRequired()) {
        // If a context mapping has been specified, the value might end up NULL
        // but valid (e.g. a reference on an empty property). In that case
        // isAllowedNull determines whether the context is conform.
        if (!isset($this->configuration['context_mapping'][$name])) {
          throw new EvaluationException("Required context '{$name}' is missing for plugin '" . $plugin->getPluginId() . "'.");
        }
        elseif (!$definition->isAllowedNull()) {
          throw new EvaluationException("The context for '{$name}' is NULL, but the context '{$name}' in '" . $plugin->getPluginId() . "' requires a value.");
        }
      }
    }
  }
  
  /**
   * Prepares plugin context based upon the set context configuration.
   *
   * The configuration is applied as far as possible without having execution
   * time data. That means, the configured context values are set and context is
   * refined while leveraging the definitions of selected data.
   *
   * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
   *   The plugin that is prepared.
   * @param \Drupal\rules\Context\ExecutionMetadataStateInterface $metadata_state
   *   The metadata state, prepared for the current expression.
   *
   * @throws \Drupal\Component\Plugin\Exception\ContextException
   *   Thrown if the plugin tries to access some not-defined context. As this is
   *   a developer error, this should not be caught.
   *
   * @see ::prepareContext()
   */
  protected function prepareContextWithMetadata(CoreContextAwarePluginInterface $plugin, ExecutionMetadataStateInterface $metadata_state) {
    if (isset($this->configuration['context_values'])) {
      foreach ($this->configuration['context_values'] as $name => $value) {
        $plugin->setContextValue($name, $value);
      }
    }
    if ($plugin instanceof ContextAwarePluginInterface) {
      $selected_data = $this->getSelectedData($metadata_state);
      // Getting context values may lead to undocumented exceptions if context
      // is not set right now. So catch those exceptions.
      // @todo Remove once https://www.drupal.org/node/2677162 is fixed in core.
      try {
        $plugin->refineContextDefinitions($selected_data);
      } catch (ContextException $e) {
        if (strpos($e->getMessage(), 'context is required') === FALSE) {
          throw $e;
        }
      }
    }
  }
  
  /**
   * Gets definitions of all selected data at configuration time.
   *
   * @param \Drupal\rules\Context\ExecutionMetadataStateInterface $metadata_state
   *   The metadata state.
   *
   * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
   *   An array of data definitions for context that is mapped using a data
   *   selector, keyed by context name.
   */
  protected function getSelectedData(ExecutionMetadataStateInterface $metadata_state) {
    $selected_data = [];
    // Collected the definitions of selected data for refining context
    // definitions.
    if (isset($this->configuration['context_mapping'])) {
      // If no state is available, we need to fetch at least the definitions of
      // selected data for refining context.
      foreach ($this->configuration['context_mapping'] as $name => $selector) {
        try {
          $selected_data[$name] = $this->getMappedDefinition($name, $metadata_state);
        } catch (IntegrityException $e) {
          // Ignore invalid data selectors here, such that context gets refined
          // as far as possible still and can be respected by the UI when fixing
          // broken selectors.
        }
      }
    }
    return $selected_data;
  }
  
  /**
   * Gets the definition of the data that is mapped to the given context.
   *
   * @param string $context_name
   *   The name of the context.
   * @param \Drupal\rules\Context\ExecutionMetadataStateInterface $metadata_state
   *   The metadata state containing metadata about available variables.
   *
   * @return \Drupal\Core\TypedData\DataDefinitionInterface|null
   *   A data definition if the property path could be applied, or NULL if the
   *   context is not mapped.
   *
   * @throws \Drupal\rules\Exception\IntegrityException
   *   Thrown if the data selector that is configured for the context is
   *   invalid.
   */
  protected function getMappedDefinition($context_name, ExecutionMetadataStateInterface $metadata_state) {
    if (isset($this->configuration['context_mapping'][$context_name])) {
      return $metadata_state->fetchDefinitionByPropertyPath($this->configuration['context_mapping'][$context_name]);
    }
  }
  
  /**
   * Adds provided context values from the plugin to the execution state.
   *
   * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
   *   The context aware plugin of which to add provided context.
   * @param \Drupal\rules\Context\ExecutionStateInterface $state
   *   The Rules state where the context variables are added.
   */
  protected function addProvidedContext(CoreContextAwarePluginInterface $plugin, ExecutionStateInterface $state) {
    // If the plugin does not support providing context, there is nothing to do.
    if (!$plugin instanceof ContextProviderInterface) {
      return;
    }
    $provides = $plugin->getProvidedContextDefinitions();
    foreach ($provides as $name => $provided_definition) {
      // Avoid name collisions in the rules state: provided variables can be
      // renamed.
      if (isset($this->configuration['provides_mapping'][$name])) {
        $state->setVariableData($this->configuration['provides_mapping'][$name], $plugin->getProvidedContext($name)
          ->getContextData());
      }
      else {
        $state->setVariableData($name, $plugin->getProvidedContext($name)
          ->getContextData());
      }
    }
  }
  
  /**
   * Adds the definitions of provided context to the execution metadata state.
   *
   * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
   *   The context aware plugin of which to add provided context.
   * @param \Drupal\rules\Context\ExecutionMetadataStateInterface $metadata_state
   *   The execution metadata state to add variables to.
   */
  protected function addProvidedContextDefinitions(CoreContextAwarePluginInterface $plugin, ExecutionMetadataStateInterface $metadata_state) {
    // If the plugin does not support providing context, there is nothing to do.
    if (!$plugin instanceof ContextProviderInterface) {
      return;
    }
    foreach ($plugin->getProvidedContextDefinitions() as $name => $context_definition) {
      if (isset($this->configuration['provides_mapping'][$name])) {
        // Populate the state with the new variable that is provided by this
        // plugin. That is necessary so that the integrity check in subsequent
        // actions knows about the variable and does not throw violations.
        $metadata_state->setDataDefinition($this->configuration['provides_mapping'][$name], $context_definition->getDataDefinition());
      }
      else {
        $metadata_state->setDataDefinition($name, $context_definition->getDataDefinition());
      }
    }
  }
  
  /**
   * Asserts additional metadata.
   *
   * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
   *   The context aware plugin.
   * @param \Drupal\rules\Context\ExecutionMetadataStateInterface $metadata_state
   *   The execution metadata state.
   */
  protected function assertMetadata(CoreContextAwarePluginInterface $plugin, ExecutionMetadataStateInterface $metadata_state) {
    // If the plugin does not implement the Rules-enhanced interface, skip this.
    if (!$plugin instanceof ContextAwarePluginInterface) {
      return;
    }
    $changed_definitions = $plugin->assertMetadata($this->getSelectedData($metadata_state));
    // Reverse the mapping and apply the changes.
    foreach ($changed_definitions as $context_name => $definition) {
      $selector = $this->configuration['context_mapping'][$context_name];
      // @todo Deal with selectors matching not a context name.
      if (strpos($selector, '.') === FALSE) {
        $metadata_state->setDataDefinition($selector, $definition);
      }
    }
  }
  
  /**
   * Process data context on the plugin, usually before it gets executed.
   *
   * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
   *   The plugin to process the context data on.
   * @param \Drupal\rules\Context\ExecutionStateInterface $rules_state
   *   The current Rules execution state with context variables.
   */
  protected function processData(CoreContextAwarePluginInterface $plugin, ExecutionStateInterface $rules_state) {
    if (isset($this->configuration['context_processors'])) {
      foreach ($this->configuration['context_processors'] as $context_name => $processors) {
        $definition = $plugin->getContextDefinition($context_name);
        $value = $plugin->getContextValue($context_name);
        if ($definition->isMultiple()) {
          foreach ($value as &$current) {
            $current = $this->processValue($current, $processors, $rules_state);
          }
        }
        else {
          $value = $this->processValue($value, $processors, $rules_state);
        }
        $plugin->setContextValue($context_name, $value);
      }
    }
  }
  
  /**
   * Processes a single value.
   *
   * @param mixed $value
   *   The current value.
   * @param array $processors
   *   An array mapping processor plugin IDs to their configuration.
   * @param \Drupal\rules\Context\ExecutionStateInterface $rules_state
   *   The current Rules execution state with context variables.
   *
   * @return mixed
   *   THe processed value.
   */
  protected function processValue($value, array $processors, ExecutionStateInterface $rules_state) {
    foreach ($processors as $processor_plugin_id => $configuration) {
      $data_processor = $this->processorManager
        ->createInstance($processor_plugin_id, $configuration);
      $value = $data_processor->process($value, $rules_state);
    }
    return $value;
  }

}

Members

Title Sort descending Modifiers Object type Summary
ContextHandlerTrait::$processorManager protected property The data processor plugin manager used to process context variables.
ContextHandlerTrait::addProvidedContext protected function Adds provided context values from the plugin to the execution state.
ContextHandlerTrait::addProvidedContextDefinitions protected function Adds the definitions of provided context to the execution metadata state.
ContextHandlerTrait::assertMetadata protected function Asserts additional metadata.
ContextHandlerTrait::getMappedDefinition protected function Gets the definition of the data that is mapped to the given context.
ContextHandlerTrait::getSelectedData protected function Gets definitions of all selected data at configuration time.
ContextHandlerTrait::prepareContext protected function Prepares plugin context based upon the set context configuration.
ContextHandlerTrait::prepareContextWithMetadata protected function Prepares plugin context based upon the set context configuration.
ContextHandlerTrait::processData protected function Process data context on the plugin, usually before it gets executed.
ContextHandlerTrait::processValue protected function Processes a single value.