Mercurial > hg > isophonics-drupal-site
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 } |