ContextHandler.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <?php
  2. namespace Drupal\Core\Plugin\Context;
  3. use Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface;
  4. use Drupal\Component\Plugin\Exception\ContextException;
  5. use Drupal\Component\Plugin\Exception\MissingValueContextException;
  6. use Drupal\Component\Plugin\Exception\PluginException;
  7. use Drupal\Core\Cache\CacheableDependencyInterface;
  8. use Drupal\Core\Plugin\ContextAwarePluginInterface;
  9. /**
  10. * Provides methods to handle sets of contexts.
  11. */
  12. class ContextHandler implements ContextHandlerInterface {
  13. /**
  14. * {@inheritdoc}
  15. */
  16. public function filterPluginDefinitionsByContexts(array $contexts, array $definitions) {
  17. $checked_requirements = [];
  18. return array_filter($definitions, function ($plugin_definition) use ($contexts, &$checked_requirements) {
  19. $context_definitions = $this->getContextDefinitions($plugin_definition);
  20. if ($context_definitions) {
  21. // Generate a unique key for the current context definitions. This will
  22. // allow calling checkRequirements() once for all plugins that have the
  23. // same context definitions.
  24. $context_definitions_key = hash('sha256', serialize($context_definitions));
  25. if (!isset($checked_requirements[$context_definitions_key])) {
  26. // Check the set of contexts against the requirements.
  27. $checked_requirements[$context_definitions_key] = $this->checkRequirements($contexts, $context_definitions);
  28. }
  29. return $checked_requirements[$context_definitions_key];
  30. }
  31. // If this plugin doesn't need any context, it is available to use.
  32. return TRUE;
  33. });
  34. }
  35. /**
  36. * Returns the context definitions associated with a plugin definition.
  37. *
  38. * @param array|\Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface $plugin_definition
  39. * The plugin definition.
  40. *
  41. * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]|null
  42. * The context definitions, or NULL if the plugin definition does not
  43. * support contexts.
  44. */
  45. protected function getContextDefinitions($plugin_definition) {
  46. if ($plugin_definition instanceof ContextAwarePluginDefinitionInterface) {
  47. return $plugin_definition->getContextDefinitions();
  48. }
  49. if (is_array($plugin_definition) && isset($plugin_definition['context_definitions'])) {
  50. return $plugin_definition['context_definitions'];
  51. }
  52. return NULL;
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. public function checkRequirements(array $contexts, array $requirements) {
  58. foreach ($requirements as $requirement) {
  59. if ($requirement->isRequired() && !$this->getMatchingContexts($contexts, $requirement)) {
  60. return FALSE;
  61. }
  62. }
  63. return TRUE;
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. public function getMatchingContexts(array $contexts, ContextDefinitionInterface $definition) {
  69. return array_filter($contexts, function (ContextInterface $context) use ($definition) {
  70. return $definition->isSatisfiedBy($context);
  71. });
  72. }
  73. /**
  74. * {@inheritdoc}
  75. */
  76. public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = []) {
  77. /** @var $contexts \Drupal\Core\Plugin\Context\ContextInterface[] */
  78. $mappings += $plugin->getContextMapping();
  79. // Loop through each of the expected contexts.
  80. $missing_value = [];
  81. foreach ($plugin->getContextDefinitions() as $plugin_context_id => $plugin_context_definition) {
  82. // If this context was given a specific name, use that.
  83. $context_id = isset($mappings[$plugin_context_id]) ? $mappings[$plugin_context_id] : $plugin_context_id;
  84. if (!empty($contexts[$context_id])) {
  85. // This assignment has been used, remove it.
  86. unset($mappings[$plugin_context_id]);
  87. // Plugins have their on context objects, only the value is applied.
  88. // They also need to know about the cacheability metadata of where that
  89. // value is coming from, so pass them through to those objects.
  90. $plugin_context = $plugin->getContext($plugin_context_id);
  91. if ($plugin_context instanceof ContextInterface && $contexts[$context_id] instanceof CacheableDependencyInterface) {
  92. $plugin_context->addCacheableDependency($contexts[$context_id]);
  93. }
  94. // Pass the value to the plugin if there is one.
  95. if ($contexts[$context_id]->hasContextValue()) {
  96. $plugin->setContext($plugin_context_id, $contexts[$context_id]);
  97. }
  98. elseif ($plugin_context_definition->isRequired()) {
  99. // Collect required contexts that exist but are missing a value.
  100. $missing_value[] = $plugin_context_id;
  101. }
  102. // Proceed to the next definition.
  103. continue;
  104. }
  105. try {
  106. $context = $plugin->getContext($context_id);
  107. }
  108. catch (ContextException $e) {
  109. $context = NULL;
  110. }
  111. // @todo Remove in https://www.drupal.org/project/drupal/issues/3046342.
  112. catch (PluginException $e) {
  113. $context = NULL;
  114. }
  115. if ($context && $context->hasContextValue()) {
  116. // Ignore mappings if the plugin has a value for a missing context.
  117. unset($mappings[$plugin_context_id]);
  118. continue;
  119. }
  120. if ($plugin_context_definition->isRequired()) {
  121. // Collect required contexts that are missing.
  122. $missing_value[] = $plugin_context_id;
  123. continue;
  124. }
  125. // Ignore mappings for optional missing context.
  126. unset($mappings[$plugin_context_id]);
  127. }
  128. // If there are any mappings that were not satisfied, throw an exception.
  129. // This is a more severe problem than missing values, so check and throw
  130. // this first.
  131. if (!empty($mappings)) {
  132. throw new ContextException('Assigned contexts were not satisfied: ' . implode(',', array_keys($mappings)));
  133. }
  134. // If there are any required contexts without a value, throw an exception.
  135. if ($missing_value) {
  136. throw new MissingValueContextException($missing_value);
  137. }
  138. }
  139. }