3 namespace Drupal\block;
5 use Drupal\Component\Plugin\Exception\ContextException;
6 use Drupal\Core\Access\AccessResult;
7 use Drupal\Core\Cache\Cache;
8 use Drupal\Core\Cache\CacheableDependencyInterface;
9 use Drupal\Core\Condition\ConditionAccessResolverTrait;
10 use Drupal\Core\Entity\EntityAccessControlHandler;
11 use Drupal\Core\Entity\EntityHandlerInterface;
12 use Drupal\Core\Entity\EntityInterface;
13 use Drupal\Core\Entity\EntityTypeInterface;
14 use Drupal\Core\Plugin\Context\ContextHandlerInterface;
15 use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
16 use Drupal\Core\Plugin\ContextAwarePluginInterface;
17 use Drupal\Core\Session\AccountInterface;
18 use Symfony\Component\DependencyInjection\ContainerInterface;
21 * Defines the access control handler for the block entity type.
23 * @see \Drupal\block\Entity\Block
25 class BlockAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
27 use ConditionAccessResolverTrait;
30 * The plugin context handler.
32 * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
34 protected $contextHandler;
37 * The context manager service.
39 * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
41 protected $contextRepository;
46 public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
49 $container->get('context.handler'),
50 $container->get('context.repository')
55 * Constructs the block access control handler instance
57 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
58 * The entity type definition.
59 * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
60 * The ContextHandler for applying contexts to conditions properly.
61 * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
62 * The lazy context repository service.
64 public function __construct(EntityTypeInterface $entity_type, ContextHandlerInterface $context_handler, ContextRepositoryInterface $context_repository) {
65 parent::__construct($entity_type);
66 $this->contextHandler = $context_handler;
67 $this->contextRepository = $context_repository;
74 protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
75 /** @var \Drupal\block\BlockInterface $entity */
76 if ($operation != 'view') {
77 return parent::checkAccess($entity, $operation, $account);
80 // Don't grant access to disabled blocks.
81 if (!$entity->status()) {
82 return AccessResult::forbidden()->addCacheableDependency($entity);
86 $missing_context = FALSE;
87 foreach ($entity->getVisibilityConditions() as $condition_id => $condition) {
88 if ($condition instanceof ContextAwarePluginInterface) {
90 $contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping()));
91 $this->contextHandler->applyContextMapping($condition, $contexts);
93 catch (ContextException $e) {
94 $missing_context = TRUE;
97 $conditions[$condition_id] = $condition;
100 if ($missing_context) {
101 // If any context is missing then we might be missing cacheable
102 // metadata, and don't know based on what conditions the block is
103 // accessible or not. For example, blocks that have a node type
104 // condition will have a missing context on any non-node route like the
106 // @todo Avoid setting max-age 0 for some or all cases, for example by
107 // treating available contexts without value differently in
108 // https://www.drupal.org/node/2521956.
109 $access = AccessResult::forbidden()->setCacheMaxAge(0);
111 elseif ($this->resolveConditions($conditions, 'and') !== FALSE) {
112 // Delegate to the plugin.
113 $block_plugin = $entity->getPlugin();
115 if ($block_plugin instanceof ContextAwarePluginInterface) {
116 $contexts = $this->contextRepository->getRuntimeContexts(array_values($block_plugin->getContextMapping()));
117 $this->contextHandler->applyContextMapping($block_plugin, $contexts);
119 $access = $block_plugin->access($account, TRUE);
121 catch (ContextException $e) {
122 // Setting access to forbidden if any context is missing for the same
123 // reasons as with conditions (described in the comment above).
124 // @todo Avoid setting max-age 0 for some or all cases, for example by
125 // treating available contexts without value differently in
126 // https://www.drupal.org/node/2521956.
127 $access = AccessResult::forbidden()->setCacheMaxAge(0);
131 $access = AccessResult::forbidden();
134 $this->mergeCacheabilityFromConditions($access, $conditions);
136 // Ensure that access is evaluated again when the block changes.
137 return $access->addCacheableDependency($entity);
142 * Merges cacheable metadata from conditions onto the access result object.
144 * @param \Drupal\Core\Access\AccessResult $access
145 * The access result object.
146 * @param \Drupal\Core\Condition\ConditionInterface[] $conditions
147 * List of visibility conditions.
149 protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions) {
150 foreach ($conditions as $condition) {
151 if ($condition instanceof CacheableDependencyInterface) {
152 $access->addCacheTags($condition->getCacheTags());
153 $access->addCacheContexts($condition->getCacheContexts());
154 $access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge()));