comparison core/lib/Drupal/Core/Access/AccessResult.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\Core\Access;
4
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Cache\CacheableDependencyInterface;
7 use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
8 use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
9 use Drupal\Core\Config\ConfigBase;
10 use Drupal\Core\Entity\EntityInterface;
11 use Drupal\Core\Session\AccountInterface;
12
13 /**
14 * Value object for passing an access result with cacheability metadata.
15 *
16 * The access result itself — excluding the cacheability metadata — is
17 * immutable. There are subclasses for each of the three possible access results
18 * themselves:
19 *
20 * @see \Drupal\Core\Access\AccessResultAllowed
21 * @see \Drupal\Core\Access\AccessResultForbidden
22 * @see \Drupal\Core\Access\AccessResultNeutral
23 *
24 * When using ::orIf() and ::andIf(), cacheability metadata will be merged
25 * accordingly as well.
26 */
27 abstract class AccessResult implements AccessResultInterface, RefinableCacheableDependencyInterface {
28
29 use RefinableCacheableDependencyTrait;
30
31 /**
32 * Creates an AccessResultInterface object with isNeutral() === TRUE.
33 *
34 * @param string|null $reason
35 * (optional) The reason why access is forbidden. Intended for developers,
36 * hence not translatable.
37 *
38 * @return \Drupal\Core\Access\AccessResultNeutral
39 * isNeutral() will be TRUE.
40 */
41 public static function neutral($reason = NULL) {
42 assert('is_string($reason) || is_null($reason)');
43 return new AccessResultNeutral($reason);
44 }
45
46 /**
47 * Creates an AccessResultInterface object with isAllowed() === TRUE.
48 *
49 * @return \Drupal\Core\Access\AccessResultAllowed
50 * isAllowed() will be TRUE.
51 */
52 public static function allowed() {
53 return new AccessResultAllowed();
54 }
55
56 /**
57 * Creates an AccessResultInterface object with isForbidden() === TRUE.
58 *
59 * @param string|null $reason
60 * (optional) The reason why access is forbidden. Intended for developers,
61 * hence not translatable.
62 *
63 * @return \Drupal\Core\Access\AccessResultForbidden
64 * isForbidden() will be TRUE.
65 */
66 public static function forbidden($reason = NULL) {
67 assert('is_string($reason) || is_null($reason)');
68 return new AccessResultForbidden($reason);
69 }
70
71 /**
72 * Creates an allowed or neutral access result.
73 *
74 * @param bool $condition
75 * The condition to evaluate.
76 *
77 * @return \Drupal\Core\Access\AccessResult
78 * If $condition is TRUE, isAllowed() will be TRUE, otherwise isNeutral()
79 * will be TRUE.
80 */
81 public static function allowedIf($condition) {
82 return $condition ? static::allowed() : static::neutral();
83 }
84
85 /**
86 * Creates a forbidden or neutral access result.
87 *
88 * @param bool $condition
89 * The condition to evaluate.
90 *
91 * @return \Drupal\Core\Access\AccessResult
92 * If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral()
93 * will be TRUE.
94 */
95 public static function forbiddenIf($condition) {
96 return $condition ? static::forbidden() : static::neutral();
97 }
98
99 /**
100 * Creates an allowed access result if the permission is present, neutral otherwise.
101 *
102 * Checks the permission and adds a 'user.permissions' cache context.
103 *
104 * @param \Drupal\Core\Session\AccountInterface $account
105 * The account for which to check a permission.
106 * @param string $permission
107 * The permission to check for.
108 *
109 * @return \Drupal\Core\Access\AccessResult
110 * If the account has the permission, isAllowed() will be TRUE, otherwise
111 * isNeutral() will be TRUE.
112 */
113 public static function allowedIfHasPermission(AccountInterface $account, $permission) {
114 $access_result = static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']);
115
116 if ($access_result instanceof AccessResultReasonInterface) {
117 $access_result->setReason("The '$permission' permission is required.");
118 }
119 return $access_result;
120 }
121
122 /**
123 * Creates an allowed access result if the permissions are present, neutral otherwise.
124 *
125 * Checks the permission and adds a 'user.permissions' cache contexts.
126 *
127 * @param \Drupal\Core\Session\AccountInterface $account
128 * The account for which to check permissions.
129 * @param array $permissions
130 * The permissions to check.
131 * @param string $conjunction
132 * (optional) 'AND' if all permissions are required, 'OR' in case just one.
133 * Defaults to 'AND'
134 *
135 * @return \Drupal\Core\Access\AccessResult
136 * If the account has the permissions, isAllowed() will be TRUE, otherwise
137 * isNeutral() will be TRUE.
138 */
139 public static function allowedIfHasPermissions(AccountInterface $account, array $permissions, $conjunction = 'AND') {
140 $access = FALSE;
141
142 if ($conjunction == 'AND' && !empty($permissions)) {
143 $access = TRUE;
144 foreach ($permissions as $permission) {
145 if (!$permission_access = $account->hasPermission($permission)) {
146 $access = FALSE;
147 break;
148 }
149 }
150 }
151 else {
152 foreach ($permissions as $permission) {
153 if ($permission_access = $account->hasPermission($permission)) {
154 $access = TRUE;
155 break;
156 }
157 }
158 }
159
160 $access_result = static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']);
161
162 if ($access_result instanceof AccessResultReasonInterface) {
163 if (count($permissions) === 1) {
164 $access_result->setReason("The '$permission' permission is required.");
165 }
166 elseif (count($permissions) > 1) {
167 $quote = function ($s) {
168 return "'$s'";
169 };
170 $access_result->setReason(sprintf("The following permissions are required: %s.", implode(" $conjunction ", array_map($quote, $permissions))));
171 }
172 }
173
174 return $access_result;
175 }
176
177 /**
178 * {@inheritdoc}
179 *
180 * @see \Drupal\Core\Access\AccessResultAllowed
181 */
182 public function isAllowed() {
183 return FALSE;
184 }
185
186 /**
187 * {@inheritdoc}
188 *
189 * @see \Drupal\Core\Access\AccessResultForbidden
190 */
191 public function isForbidden() {
192 return FALSE;
193 }
194
195 /**
196 * {@inheritdoc}
197 *
198 * @see \Drupal\Core\Access\AccessResultNeutral
199 */
200 public function isNeutral() {
201 return FALSE;
202 }
203
204 /**
205 * {@inheritdoc}
206 */
207 public function getCacheContexts() {
208 return $this->cacheContexts;
209 }
210
211 /**
212 * {@inheritdoc}
213 */
214 public function getCacheTags() {
215 return $this->cacheTags;
216 }
217
218 /**
219 * {@inheritdoc}
220 */
221 public function getCacheMaxAge() {
222 return $this->cacheMaxAge;
223 }
224
225 /**
226 * Resets cache contexts (to the empty array).
227 *
228 * @return $this
229 */
230 public function resetCacheContexts() {
231 $this->cacheContexts = [];
232 return $this;
233 }
234
235 /**
236 * Resets cache tags (to the empty array).
237 *
238 * @return $this
239 */
240 public function resetCacheTags() {
241 $this->cacheTags = [];
242 return $this;
243 }
244
245 /**
246 * Sets the maximum age for which this access result may be cached.
247 *
248 * @param int $max_age
249 * The maximum time in seconds that this access result may be cached.
250 *
251 * @return $this
252 */
253 public function setCacheMaxAge($max_age) {
254 $this->cacheMaxAge = $max_age;
255 return $this;
256 }
257
258 /**
259 * Convenience method, adds the "user.permissions" cache context.
260 *
261 * @return $this
262 */
263 public function cachePerPermissions() {
264 $this->addCacheContexts(['user.permissions']);
265 return $this;
266 }
267
268 /**
269 * Convenience method, adds the "user" cache context.
270 *
271 * @return $this
272 */
273 public function cachePerUser() {
274 $this->addCacheContexts(['user']);
275 return $this;
276 }
277
278 /**
279 * Convenience method, adds the entity's cache tag.
280 *
281 * @param \Drupal\Core\Entity\EntityInterface $entity
282 * The entity whose cache tag to set on the access result.
283 *
284 * @return $this
285 *
286 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
287 * ::addCacheableDependency() instead.
288 */
289 public function cacheUntilEntityChanges(EntityInterface $entity) {
290 return $this->addCacheableDependency($entity);
291 }
292
293 /**
294 * Convenience method, adds the configuration object's cache tag.
295 *
296 * @param \Drupal\Core\Config\ConfigBase $configuration
297 * The configuration object whose cache tag to set on the access result.
298 *
299 * @return $this
300 *
301 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
302 * ::addCacheableDependency() instead.
303 */
304 public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
305 return $this->addCacheableDependency($configuration);
306 }
307
308 /**
309 * {@inheritdoc}
310 */
311 public function orIf(AccessResultInterface $other) {
312 $merge_other = FALSE;
313 // $other's cacheability metadata is merged if $merge_other gets set to TRUE
314 // and this happens in three cases:
315 // 1. $other's access result is the one that determines the combined access
316 // result.
317 // 2. This access result is not cacheable and $other's access result is the
318 // same. i.e. attempt to return a cacheable access result.
319 // 3. Neither access result is 'forbidden' and both are cacheable: inherit
320 // the other's cacheability metadata because it may turn into a
321 // 'forbidden' for another value of the cache contexts in the
322 // cacheability metadata. In other words: this is necessary to respect
323 // the contagious nature of the 'forbidden' access result.
324 // e.g. we have two access results A and B. Neither is forbidden. A is
325 // globally cacheable (no cache contexts). B is cacheable per role. If we
326 // don't have merging case 3, then A->orIf(B) will be globally cacheable,
327 // which means that even if a user of a different role logs in, the
328 // cached access result will be used, even though for that other role, B
329 // is forbidden!
330 if ($this->isForbidden() || $other->isForbidden()) {
331 $result = static::forbidden();
332 if (!$this->isForbidden() || ($this->getCacheMaxAge() === 0 && $other->isForbidden())) {
333 $merge_other = TRUE;
334 }
335
336 if ($this->isForbidden() && $this instanceof AccessResultReasonInterface) {
337 $result->setReason($this->getReason());
338 }
339 elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface) {
340 $result->setReason($other->getReason());
341 }
342 }
343 elseif ($this->isAllowed() || $other->isAllowed()) {
344 $result = static::allowed();
345 if (!$this->isAllowed() || ($this->getCacheMaxAge() === 0 && $other->isAllowed()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
346 $merge_other = TRUE;
347 }
348 }
349 else {
350 $result = static::neutral();
351 if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
352 $merge_other = TRUE;
353 if ($other instanceof AccessResultReasonInterface) {
354 $result->setReason($other->getReason());
355 }
356 }
357 else {
358 if ($this instanceof AccessResultReasonInterface) {
359 $result->setReason($this->getReason());
360 }
361 }
362 }
363 $result->inheritCacheability($this);
364 if ($merge_other) {
365 $result->inheritCacheability($other);
366 }
367 return $result;
368 }
369
370 /**
371 * {@inheritdoc}
372 */
373 public function andIf(AccessResultInterface $other) {
374 // The other access result's cacheability metadata is merged if $merge_other
375 // gets set to TRUE. It gets set to TRUE in one case: if the other access
376 // result is used.
377 $merge_other = FALSE;
378 if ($this->isForbidden() || $other->isForbidden()) {
379 $result = static::forbidden();
380 if (!$this->isForbidden()) {
381 if ($other instanceof AccessResultReasonInterface) {
382 $result->setReason($other->getReason());
383 }
384 $merge_other = TRUE;
385 }
386 else {
387 if ($this instanceof AccessResultReasonInterface) {
388 $result->setReason($this->getReason());
389 }
390 }
391 }
392 elseif ($this->isAllowed() && $other->isAllowed()) {
393 $result = static::allowed();
394 $merge_other = TRUE;
395 }
396 else {
397 $result = static::neutral();
398 if (!$this->isNeutral()) {
399 $merge_other = TRUE;
400 if ($other instanceof AccessResultReasonInterface) {
401 $result->setReason($other->getReason());
402 }
403 }
404 else {
405 if ($this instanceof AccessResultReasonInterface) {
406 $result->setReason($this->getReason());
407 }
408 }
409 }
410 $result->inheritCacheability($this);
411 if ($merge_other) {
412 $result->inheritCacheability($other);
413 // If this access result is not cacheable, then an AND with another access
414 // result must also not be cacheable, except if the other access result
415 // has isForbidden() === TRUE. isForbidden() access results are contagious
416 // in that they propagate regardless of the other value.
417 if ($this->getCacheMaxAge() === 0 && !$result->isForbidden()) {
418 $result->setCacheMaxAge(0);
419 }
420 }
421 return $result;
422 }
423
424 /**
425 * Inherits the cacheability of the other access result, if any.
426 *
427 * inheritCacheability() differs from addCacheableDependency() in how it
428 * handles max-age, because it is designed to inherit the cacheability of the
429 * second operand in the andIf() and orIf() operations. There, the situation
430 * "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000
431 * as the end result.
432 *
433 * @param \Drupal\Core\Access\AccessResultInterface $other
434 * The other access result, whose cacheability (if any) to inherit.
435 *
436 * @return $this
437 */
438 public function inheritCacheability(AccessResultInterface $other) {
439 $this->addCacheableDependency($other);
440 if ($other instanceof CacheableDependencyInterface) {
441 if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) {
442 $this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge()));
443 }
444 else {
445 $this->setCacheMaxAge($other->getCacheMaxAge());
446 }
447 }
448 return $this;
449 }
450
451 }