ContextHandler.php 4.54 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
<?php

/**
 * @file
 * Contains \Drupal\Core\Plugin\Context\ContextHandler.
 */

namespace Drupal\Core\Plugin\Context;

use Drupal\Component\Plugin\Exception\ContextException;
11
use Drupal\Component\Utility\SafeMarkup;
12
use Drupal\Core\Cache\CacheableDependencyInterface;
13
use Drupal\Core\Plugin\ContextAwarePluginInterface;
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

/**
 * Provides methods to handle sets of contexts.
 */
class ContextHandler implements ContextHandlerInterface {

  /**
   * {@inheritdoc}
   */
  public function filterPluginDefinitionsByContexts(array $contexts, array $definitions) {
    return array_filter($definitions, function ($plugin_definition) use ($contexts) {
      // If this plugin doesn't need any context, it is available to use.
      if (!isset($plugin_definition['context'])) {
        return TRUE;
      }

      // Check the set of contexts against the requirements.
31
      return $this->checkRequirements($contexts, $plugin_definition['context']);
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
    });
  }

  /**
   * {@inheritdoc}
   */
  public function checkRequirements(array $contexts, array $requirements) {
    foreach ($requirements as $requirement) {
      if ($requirement->isRequired() && !$this->getMatchingContexts($contexts, $requirement)) {
        return FALSE;
      }
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
50
  public function getMatchingContexts(array $contexts, ContextDefinitionInterface $definition) {
51
    return array_filter($contexts, function (ContextInterface $context) use ($definition) {
52
      $context_definition = $context->getContextDefinition();
53

54 55 56
      // If the data types do not match, this context is invalid unless the
      // expected data type is any, which means all data types are supported.
      if ($definition->getDataType() != 'any' && $definition->getDataType() != $context_definition->getDataType()) {
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
        return FALSE;
      }

      // If any constraint does not match, this context is invalid.
      foreach ($definition->getConstraints() as $constraint_name => $constraint) {
        if ($context_definition->getConstraint($constraint_name) != $constraint) {
          return FALSE;
        }
      }

      // All contexts with matching data type and contexts are valid.
      return TRUE;
    });
  }

  /**
   * {@inheritdoc}
   */
  public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) {
76
    $mappings += $plugin->getContextMapping();
77
    // Loop through each of the expected contexts.
78 79 80 81

    $missing_value = [];

    foreach ($plugin->getContextDefinitions() as $plugin_context_id => $plugin_context_definition) {
82
      // If this context was given a specific name, use that.
83 84
      $context_id = isset($mappings[$plugin_context_id]) ? $mappings[$plugin_context_id] : $plugin_context_id;
      if (!empty($contexts[$context_id])) {
85
        // This assignment has been used, remove it.
86
        unset($mappings[$plugin_context_id]);
87

88 89 90 91 92 93 94 95
        // Plugins have their on context objects, only the value is applied.
        // They also need to know about the cacheable metadata of where that
        // value is coming from, so pass them through to those objects.
        $plugin_context = $plugin->getContext($plugin_context_id);
        if ($plugin_context instanceof ContextInterface && $contexts[$context_id] instanceof CacheableDependencyInterface) {
          $plugin_context->addCacheableDependency($contexts[$context_id]);
        }

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
        // Pass the value to the plugin if there is one.
        if ($contexts[$context_id]->hasContextValue()) {
          $plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextValue());
        }
        elseif ($plugin_context_definition->isRequired()) {
          // Collect required contexts that exist but are missing a value.
          $missing_value[] = $plugin_context_id;
        }
      }
      elseif ($plugin_context_definition->isRequired()) {
        // Collect required contexts that are missing.
        $missing_value[] = $plugin_context_id;
      }
      else {
        // Ignore mappings for optional missing context.
        unset($mappings[$plugin_context_id]);
112 113 114
      }
    }

115 116 117 118 119
    // If there are any required contexts without a value, throw an exception.
    if ($missing_value) {
      throw new ContextException(sprintf('Required contexts without a value: %s.', implode(', ', $missing_value)));
    }

120 121
    // If there are any mappings that were not satisfied, throw an exception.
    if (!empty($mappings)) {
122
      throw new ContextException(SafeMarkup::format('Assigned contexts were not satisfied: @mappings', ['@mappings' => implode(',', array_keys($mappings))]));
123 124 125 126
    }
  }

}