BlockAccessControlHandler.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <?php
  2. namespace Drupal\block;
  3. use Drupal\Component\Plugin\Exception\ContextException;
  4. use Drupal\Core\Access\AccessResult;
  5. use Drupal\Core\Cache\Cache;
  6. use Drupal\Core\Cache\CacheableDependencyInterface;
  7. use Drupal\Core\Condition\ConditionAccessResolverTrait;
  8. use Drupal\Core\Entity\EntityAccessControlHandler;
  9. use Drupal\Core\Entity\EntityHandlerInterface;
  10. use Drupal\Core\Entity\EntityInterface;
  11. use Drupal\Core\Entity\EntityTypeInterface;
  12. use Drupal\Core\Plugin\Context\ContextHandlerInterface;
  13. use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
  14. use Drupal\Core\Plugin\ContextAwarePluginInterface;
  15. use Drupal\Core\Session\AccountInterface;
  16. use Symfony\Component\DependencyInjection\ContainerInterface;
  17. /**
  18. * Defines the access control handler for the block entity type.
  19. *
  20. * @see \Drupal\block\Entity\Block
  21. */
  22. class BlockAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
  23. use ConditionAccessResolverTrait;
  24. /**
  25. * The plugin context handler.
  26. *
  27. * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
  28. */
  29. protected $contextHandler;
  30. /**
  31. * The context manager service.
  32. *
  33. * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
  34. */
  35. protected $contextRepository;
  36. /**
  37. * {@inheritdoc}
  38. */
  39. public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
  40. return new static(
  41. $entity_type,
  42. $container->get('context.handler'),
  43. $container->get('context.repository')
  44. );
  45. }
  46. /**
  47. * Constructs the block access control handler instance
  48. *
  49. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  50. * The entity type definition.
  51. * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
  52. * The ContextHandler for applying contexts to conditions properly.
  53. * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
  54. * The lazy context repository service.
  55. */
  56. public function __construct(EntityTypeInterface $entity_type, ContextHandlerInterface $context_handler, ContextRepositoryInterface $context_repository) {
  57. parent::__construct($entity_type);
  58. $this->contextHandler = $context_handler;
  59. $this->contextRepository = $context_repository;
  60. }
  61. /**
  62. * {@inheritdoc}
  63. */
  64. protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
  65. /** @var \Drupal\block\BlockInterface $entity */
  66. if ($operation != 'view') {
  67. return parent::checkAccess($entity, $operation, $account);
  68. }
  69. // Don't grant access to disabled blocks.
  70. if (!$entity->status()) {
  71. return AccessResult::forbidden()->addCacheableDependency($entity);
  72. }
  73. else {
  74. $conditions = [];
  75. $missing_context = FALSE;
  76. foreach ($entity->getVisibilityConditions() as $condition_id => $condition) {
  77. if ($condition instanceof ContextAwarePluginInterface) {
  78. try {
  79. $contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping()));
  80. $this->contextHandler->applyContextMapping($condition, $contexts);
  81. }
  82. catch (ContextException $e) {
  83. $missing_context = TRUE;
  84. }
  85. }
  86. $conditions[$condition_id] = $condition;
  87. }
  88. if ($missing_context) {
  89. // If any context is missing then we might be missing cacheable
  90. // metadata, and don't know based on what conditions the block is
  91. // accessible or not. For example, blocks that have a node type
  92. // condition will have a missing context on any non-node route like the
  93. // frontpage.
  94. // @todo Avoid setting max-age 0 for some or all cases, for example by
  95. // treating available contexts without value differently in
  96. // https://www.drupal.org/node/2521956.
  97. $access = AccessResult::forbidden()->setCacheMaxAge(0);
  98. }
  99. elseif ($this->resolveConditions($conditions, 'and') !== FALSE) {
  100. // Delegate to the plugin.
  101. $block_plugin = $entity->getPlugin();
  102. try {
  103. if ($block_plugin instanceof ContextAwarePluginInterface) {
  104. $contexts = $this->contextRepository->getRuntimeContexts(array_values($block_plugin->getContextMapping()));
  105. $this->contextHandler->applyContextMapping($block_plugin, $contexts);
  106. }
  107. $access = $block_plugin->access($account, TRUE);
  108. }
  109. catch (ContextException $e) {
  110. // Setting access to forbidden if any context is missing for the same
  111. // reasons as with conditions (described in the comment above).
  112. // @todo Avoid setting max-age 0 for some or all cases, for example by
  113. // treating available contexts without value differently in
  114. // https://www.drupal.org/node/2521956.
  115. $access = AccessResult::forbidden()->setCacheMaxAge(0);
  116. }
  117. }
  118. else {
  119. $access = AccessResult::forbidden();
  120. }
  121. $this->mergeCacheabilityFromConditions($access, $conditions);
  122. // Ensure that access is evaluated again when the block changes.
  123. return $access->addCacheableDependency($entity);
  124. }
  125. }
  126. /**
  127. * Merges cacheable metadata from conditions onto the access result object.
  128. *
  129. * @param \Drupal\Core\Access\AccessResult $access
  130. * The access result object.
  131. * @param \Drupal\Core\Condition\ConditionInterface[] $conditions
  132. * List of visibility conditions.
  133. */
  134. protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions) {
  135. foreach ($conditions as $condition) {
  136. if ($condition instanceof CacheableDependencyInterface) {
  137. $access->addCacheTags($condition->getCacheTags());
  138. $access->addCacheContexts($condition->getCacheContexts());
  139. $access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge()));
  140. }
  141. }
  142. }
  143. }