Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
21 / 21 |
CRAP | |
100.00% |
73 / 73 |
AccessResult | |
100.00% |
1 / 1 |
|
100.00% |
23 / 23 |
64 | |
100.00% |
73 / 73 |
neutral | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
allowed | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
forbidden | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
allowedIf | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
forbiddenIf | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
allowedIfHasPermission | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
allowedIfHasPermissions | |
100.00% |
1 / 1 |
8 | |
100.00% |
12 / 12 |
|||
isAllowed | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isForbidden | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isNeutral | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getCacheContexts | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getCacheTags | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getCacheMaxAge | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
resetCacheContexts | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
resetCacheTags | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setCacheMaxAge | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
cachePerPermissions | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
cachePerUser | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
cacheUntilEntityChanges | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
cacheUntilConfigurationChanges | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
orIf | |
100.00% |
1 / 1 |
21 | |
100.00% |
16 / 16 |
|||
andIf | |
100.00% |
1 / 1 |
10 | |
100.00% |
17 / 17 |
|||
inheritCacheability | |
100.00% |
1 / 1 |
4 | |
100.00% |
6 / 6 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Access\AccessResult. | |
*/ | |
namespace Drupal\Core\Access; | |
use Drupal\Core\Cache\Cache; | |
use Drupal\Core\Cache\CacheableDependencyInterface; | |
use Drupal\Core\Cache\RefinableCacheableDependencyInterface; | |
use Drupal\Core\Cache\RefinableCacheableDependencyTrait; | |
use Drupal\Core\Config\ConfigBase; | |
use Drupal\Core\Entity\EntityInterface; | |
use Drupal\Core\Session\AccountInterface; | |
/** | |
* Value object for passing an access result with cacheability metadata. | |
* | |
* The access result itself — excluding the cacheability metadata — is | |
* immutable. There are subclasses for each of the three possible access results | |
* themselves: | |
* | |
* @see \Drupal\Core\Access\AccessResultAllowed | |
* @see \Drupal\Core\Access\AccessResultForbidden | |
* @see \Drupal\Core\Access\AccessResultNeutral | |
* | |
* When using ::orIf() and ::andIf(), cacheability metadata will be merged | |
* accordingly as well. | |
*/ | |
abstract class AccessResult implements AccessResultInterface, RefinableCacheableDependencyInterface { | |
use RefinableCacheableDependencyTrait; | |
/** | |
* Creates an AccessResultInterface object with isNeutral() === TRUE. | |
* | |
* @return \Drupal\Core\Access\AccessResult | |
* isNeutral() will be TRUE. | |
*/ | |
public static function neutral() { | |
return new AccessResultNeutral(); | |
} | |
/** | |
* Creates an AccessResultInterface object with isAllowed() === TRUE. | |
* | |
* @return \Drupal\Core\Access\AccessResult | |
* isAllowed() will be TRUE. | |
*/ | |
public static function allowed() { | |
return new AccessResultAllowed(); | |
} | |
/** | |
* Creates an AccessResultInterface object with isForbidden() === TRUE. | |
* | |
* @return \Drupal\Core\Access\AccessResult | |
* isForbidden() will be TRUE. | |
*/ | |
public static function forbidden() { | |
return new AccessResultForbidden(); | |
} | |
/** | |
* Creates an allowed or neutral access result. | |
* | |
* @param bool $condition | |
* The condition to evaluate. | |
* | |
* @return \Drupal\Core\Access\AccessResult | |
* If $condition is TRUE, isAllowed() will be TRUE, otherwise isNeutral() | |
* will be TRUE. | |
*/ | |
public static function allowedIf($condition) { | |
return $condition ? static::allowed() : static::neutral(); | |
} | |
/** | |
* Creates a forbidden or neutral access result. | |
* | |
* @param bool $condition | |
* The condition to evaluate. | |
* | |
* @return \Drupal\Core\Access\AccessResult | |
* If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral() | |
* will be TRUE. | |
*/ | |
public static function forbiddenIf($condition) { | |
return $condition ? static::forbidden(): static::neutral(); | |
} | |
/** | |
* Creates an allowed access result if the permission is present, neutral otherwise. | |
* | |
* Checks the permission and adds a 'user.permissions' cache context. | |
* | |
* @param \Drupal\Core\Session\AccountInterface $account | |
* The account for which to check a permission. | |
* @param string $permission | |
* The permission to check for. | |
* | |
* @return \Drupal\Core\Access\AccessResult | |
* If the account has the permission, isAllowed() will be TRUE, otherwise | |
* isNeutral() will be TRUE. | |
*/ | |
public static function allowedIfHasPermission(AccountInterface $account, $permission) { | |
return static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']); | |
} | |
/** | |
* Creates an allowed access result if the permissions are present, neutral otherwise. | |
* | |
* Checks the permission and adds a 'user.permissions' cache contexts. | |
* | |
* @param \Drupal\Core\Session\AccountInterface $account | |
* The account for which to check permissions. | |
* @param array $permissions | |
* The permissions to check. | |
* @param string $conjunction | |
* (optional) 'AND' if all permissions are required, 'OR' in case just one. | |
* Defaults to 'AND' | |
* | |
* @return \Drupal\Core\Access\AccessResult | |
* If the account has the permissions, isAllowed() will be TRUE, otherwise | |
* isNeutral() will be TRUE. | |
*/ | |
public static function allowedIfHasPermissions(AccountInterface $account, array $permissions, $conjunction = 'AND') { | |
$access = FALSE; | |
if ($conjunction == 'AND' && !empty($permissions)) { | |
$access = TRUE; | |
foreach ($permissions as $permission) { | |
if (!$permission_access = $account->hasPermission($permission)) { | |
$access = FALSE; | |
break; | |
} | |
} | |
} | |
else { | |
foreach ($permissions as $permission) { | |
if ($permission_access = $account->hasPermission($permission)) { | |
$access = TRUE; | |
break; | |
} | |
} | |
} | |
return static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']); | |
} | |
/** | |
* {@inheritdoc} | |
* | |
* @see \Drupal\Core\Access\AccessResultAllowed | |
*/ | |
public function isAllowed() { | |
return FALSE; | |
} | |
/** | |
* {@inheritdoc} | |
* | |
* @see \Drupal\Core\Access\AccessResultForbidden | |
*/ | |
public function isForbidden() { | |
return FALSE; | |
} | |
/** | |
* {@inheritdoc} | |
* | |
* @see \Drupal\Core\Access\AccessResultNeutral | |
*/ | |
public function isNeutral() { | |
return FALSE; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getCacheContexts() { | |
return $this->cacheContexts; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getCacheTags() { | |
return $this->cacheTags; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getCacheMaxAge() { | |
return $this->cacheMaxAge; | |
} | |
/** | |
* Resets cache contexts (to the empty array). | |
* | |
* @return $this | |
*/ | |
public function resetCacheContexts() { | |
$this->cacheContexts = []; | |
return $this; | |
} | |
/** | |
* Resets cache tags (to the empty array). | |
* | |
* @return $this | |
*/ | |
public function resetCacheTags() { | |
$this->cacheTags = []; | |
return $this; | |
} | |
/** | |
* Sets the maximum age for which this access result may be cached. | |
* | |
* @param int $max_age | |
* The maximum time in seconds that this access result may be cached. | |
* | |
* @return $this | |
*/ | |
public function setCacheMaxAge($max_age) { | |
$this->cacheMaxAge = $max_age; | |
return $this; | |
} | |
/** | |
* Convenience method, adds the "user.permissions" cache context. | |
* | |
* @return $this | |
*/ | |
public function cachePerPermissions() { | |
$this->addCacheContexts(array('user.permissions')); | |
return $this; | |
} | |
/** | |
* Convenience method, adds the "user" cache context. | |
* | |
* @return $this | |
*/ | |
public function cachePerUser() { | |
$this->addCacheContexts(array('user')); | |
return $this; | |
} | |
/** | |
* Convenience method, adds the entity's cache tag. | |
* | |
* @param \Drupal\Core\Entity\EntityInterface $entity | |
* The entity whose cache tag to set on the access result. | |
* | |
* @return $this | |
* | |
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use | |
* ::addCacheableDependency() instead. | |
*/ | |
public function cacheUntilEntityChanges(EntityInterface $entity) { | |
return $this->addCacheableDependency($entity); | |
} | |
/** | |
* Convenience method, adds the configuration object's cache tag. | |
* | |
* @param \Drupal\Core\Config\ConfigBase $configuration | |
* The configuration object whose cache tag to set on the access result. | |
* | |
* @return $this | |
* | |
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use | |
* ::addCacheableDependency() instead. | |
*/ | |
public function cacheUntilConfigurationChanges(ConfigBase $configuration) { | |
return $this->addCacheableDependency($configuration); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function orIf(AccessResultInterface $other) { | |
$merge_other = FALSE; | |
// $other's cacheability metadata is merged if $merge_other gets set to TRUE | |
// and this happens in three cases: | |
// 1. $other's access result is the one that determines the combined access | |
// result. | |
// 2. This access result is not cacheable and $other's access result is the | |
// same. i.e. attempt to return a cacheable access result. | |
// 3. Neither access result is 'forbidden' and both are cacheable: inherit | |
// the other's cacheability metadata because it may turn into a | |
// 'forbidden' for another value of the cache contexts in the | |
// cacheability metadata. In other words: this is necessary to respect | |
// the contagious nature of the 'forbidden' access result. | |
// e.g. we have two access results A and B. Neither is forbidden. A is | |
// globally cacheable (no cache contexts). B is cacheable per role. If we | |
// don't have merging case 3, then A->orIf(B) will be globally cacheable, | |
// which means that even if a user of a different role logs in, the | |
// cached access result will be used, even though for that other role, B | |
// is forbidden! | |
if ($this->isForbidden() || $other->isForbidden()) { | |
$result = static::forbidden(); | |
if (!$this->isForbidden() || ($this->getCacheMaxAge() === 0 && $other->isForbidden())) { | |
$merge_other = TRUE; | |
} | |
} | |
elseif ($this->isAllowed() || $other->isAllowed()) { | |
$result = static::allowed(); | |
if (!$this->isAllowed() || ($this->getCacheMaxAge() === 0 && $other->isAllowed()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) { | |
$merge_other = TRUE; | |
} | |
} | |
else { | |
$result = static::neutral(); | |
if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) { | |
$merge_other = TRUE; | |
} | |
} | |
$result->inheritCacheability($this); | |
if ($merge_other) { | |
$result->inheritCacheability($other); | |
} | |
return $result; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function andIf(AccessResultInterface $other) { | |
// The other access result's cacheability metadata is merged if $merge_other | |
// gets set to TRUE. It gets set to TRUE in one case: if the other access | |
// result is used. | |
$merge_other = FALSE; | |
if ($this->isForbidden() || $other->isForbidden()) { | |
$result = static::forbidden(); | |
if (!$this->isForbidden()) { | |
$merge_other = TRUE; | |
} | |
} | |
elseif ($this->isAllowed() && $other->isAllowed()) { | |
$result = static::allowed(); | |
$merge_other = TRUE; | |
} | |
else { | |
$result = static::neutral(); | |
if (!$this->isNeutral()) { | |
$merge_other = TRUE; | |
} | |
} | |
$result->inheritCacheability($this); | |
if ($merge_other) { | |
$result->inheritCacheability($other); | |
// If this access result is not cacheable, then an AND with another access | |
// result must also not be cacheable, except if the other access result | |
// has isForbidden() === TRUE. isForbidden() access results are contagious | |
// in that they propagate regardless of the other value. | |
if ($this->getCacheMaxAge() === 0 && !$result->isForbidden()) { | |
$result->setCacheMaxAge(0); | |
} | |
} | |
return $result; | |
} | |
/** | |
* Inherits the cacheability of the other access result, if any. | |
* | |
* inheritCacheability() differs from addCacheableDependency() in how it | |
* handles max-age, because it is designed to inherit the cacheability of the | |
* second operand in the andIf() and orIf() operations. There, the situation | |
* "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000 | |
* as the end result. | |
* | |
* @param \Drupal\Core\Access\AccessResultInterface $other | |
* The other access result, whose cacheability (if any) to inherit. | |
* | |
* @return $this | |
*/ | |
public function inheritCacheability(AccessResultInterface $other) { | |
$this->addCacheableDependency($other); | |
if ($other instanceof CacheableDependencyInterface) { | |
if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) { | |
$this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge())); | |
} | |
else { | |
$this->setCacheMaxAge($other->getCacheMaxAge()); | |
} | |
} | |
return $this; | |
} | |
} |