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