Chris@0: hasPermission($permission))->addCacheContexts(['user.permissions']); Chris@0: Chris@0: if ($access_result instanceof AccessResultReasonInterface) { Chris@0: $access_result->setReason("The '$permission' permission is required."); Chris@0: } Chris@0: return $access_result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates an allowed access result if the permissions are present, neutral otherwise. Chris@0: * Chris@0: * Checks the permission and adds a 'user.permissions' cache contexts. Chris@0: * Chris@0: * @param \Drupal\Core\Session\AccountInterface $account Chris@0: * The account for which to check permissions. Chris@0: * @param array $permissions Chris@0: * The permissions to check. Chris@0: * @param string $conjunction Chris@0: * (optional) 'AND' if all permissions are required, 'OR' in case just one. Chris@0: * Defaults to 'AND' Chris@0: * Chris@0: * @return \Drupal\Core\Access\AccessResult Chris@0: * If the account has the permissions, isAllowed() will be TRUE, otherwise Chris@0: * isNeutral() will be TRUE. Chris@0: */ Chris@0: public static function allowedIfHasPermissions(AccountInterface $account, array $permissions, $conjunction = 'AND') { Chris@0: $access = FALSE; Chris@0: Chris@0: if ($conjunction == 'AND' && !empty($permissions)) { Chris@0: $access = TRUE; Chris@0: foreach ($permissions as $permission) { Chris@0: if (!$permission_access = $account->hasPermission($permission)) { Chris@0: $access = FALSE; Chris@0: break; Chris@0: } Chris@0: } Chris@0: } Chris@0: else { Chris@0: foreach ($permissions as $permission) { Chris@0: if ($permission_access = $account->hasPermission($permission)) { Chris@0: $access = TRUE; Chris@0: break; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: $access_result = static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']); Chris@0: Chris@0: if ($access_result instanceof AccessResultReasonInterface) { Chris@0: if (count($permissions) === 1) { Chris@0: $access_result->setReason("The '$permission' permission is required."); Chris@0: } Chris@0: elseif (count($permissions) > 1) { Chris@0: $quote = function ($s) { Chris@0: return "'$s'"; Chris@0: }; Chris@0: $access_result->setReason(sprintf("The following permissions are required: %s.", implode(" $conjunction ", array_map($quote, $permissions)))); Chris@0: } Chris@0: } Chris@0: Chris@0: return $access_result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @see \Drupal\Core\Access\AccessResultAllowed Chris@0: */ Chris@0: public function isAllowed() { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @see \Drupal\Core\Access\AccessResultForbidden Chris@0: */ Chris@0: public function isForbidden() { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @see \Drupal\Core\Access\AccessResultNeutral Chris@0: */ Chris@0: public function isNeutral() { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheContexts() { Chris@0: return $this->cacheContexts; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheTags() { Chris@0: return $this->cacheTags; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheMaxAge() { Chris@0: return $this->cacheMaxAge; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Resets cache contexts (to the empty array). Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function resetCacheContexts() { Chris@0: $this->cacheContexts = []; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Resets cache tags (to the empty array). Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function resetCacheTags() { Chris@0: $this->cacheTags = []; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the maximum age for which this access result may be cached. Chris@0: * Chris@0: * @param int $max_age Chris@0: * The maximum time in seconds that this access result may be cached. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setCacheMaxAge($max_age) { Chris@0: $this->cacheMaxAge = $max_age; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Convenience method, adds the "user.permissions" cache context. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function cachePerPermissions() { Chris@0: $this->addCacheContexts(['user.permissions']); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Convenience method, adds the "user" cache context. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function cachePerUser() { Chris@0: $this->addCacheContexts(['user']); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Convenience method, adds the entity's cache tag. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity whose cache tag to set on the access result. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use Chris@0: * ::addCacheableDependency() instead. Chris@0: */ Chris@0: public function cacheUntilEntityChanges(EntityInterface $entity) { Chris@0: return $this->addCacheableDependency($entity); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Convenience method, adds the configuration object's cache tag. Chris@0: * Chris@0: * @param \Drupal\Core\Config\ConfigBase $configuration Chris@0: * The configuration object whose cache tag to set on the access result. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use Chris@0: * ::addCacheableDependency() instead. Chris@0: */ Chris@0: public function cacheUntilConfigurationChanges(ConfigBase $configuration) { Chris@0: return $this->addCacheableDependency($configuration); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function orIf(AccessResultInterface $other) { Chris@0: $merge_other = FALSE; Chris@0: // $other's cacheability metadata is merged if $merge_other gets set to TRUE Chris@0: // and this happens in three cases: Chris@0: // 1. $other's access result is the one that determines the combined access Chris@0: // result. Chris@0: // 2. This access result is not cacheable and $other's access result is the Chris@0: // same. i.e. attempt to return a cacheable access result. Chris@0: // 3. Neither access result is 'forbidden' and both are cacheable: inherit Chris@0: // the other's cacheability metadata because it may turn into a Chris@0: // 'forbidden' for another value of the cache contexts in the Chris@0: // cacheability metadata. In other words: this is necessary to respect Chris@0: // the contagious nature of the 'forbidden' access result. Chris@0: // e.g. we have two access results A and B. Neither is forbidden. A is Chris@0: // globally cacheable (no cache contexts). B is cacheable per role. If we Chris@0: // don't have merging case 3, then A->orIf(B) will be globally cacheable, Chris@0: // which means that even if a user of a different role logs in, the Chris@0: // cached access result will be used, even though for that other role, B Chris@0: // is forbidden! Chris@0: if ($this->isForbidden() || $other->isForbidden()) { Chris@0: $result = static::forbidden(); Chris@0: if (!$this->isForbidden() || ($this->getCacheMaxAge() === 0 && $other->isForbidden())) { Chris@0: $merge_other = TRUE; Chris@0: } Chris@0: Chris@16: if ($this->isForbidden() && $this instanceof AccessResultReasonInterface && !is_null($this->getReason())) { Chris@0: $result->setReason($this->getReason()); Chris@0: } Chris@16: elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface && !is_null($other->getReason())) { Chris@0: $result->setReason($other->getReason()); Chris@0: } Chris@0: } Chris@0: elseif ($this->isAllowed() || $other->isAllowed()) { Chris@0: $result = static::allowed(); Chris@0: if (!$this->isAllowed() || ($this->getCacheMaxAge() === 0 && $other->isAllowed()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) { Chris@0: $merge_other = TRUE; Chris@0: } Chris@0: } Chris@0: else { Chris@0: $result = static::neutral(); Chris@0: if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) { Chris@0: $merge_other = TRUE; Chris@0: } Chris@16: Chris@16: if ($this instanceof AccessResultReasonInterface && !is_null($this->getReason())) { Chris@16: $result->setReason($this->getReason()); Chris@16: } Chris@16: elseif ($other instanceof AccessResultReasonInterface && !is_null($other->getReason())) { Chris@16: $result->setReason($other->getReason()); Chris@0: } Chris@0: } Chris@0: $result->inheritCacheability($this); Chris@0: if ($merge_other) { Chris@0: $result->inheritCacheability($other); Chris@0: } Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function andIf(AccessResultInterface $other) { Chris@0: // The other access result's cacheability metadata is merged if $merge_other Chris@0: // gets set to TRUE. It gets set to TRUE in one case: if the other access Chris@0: // result is used. Chris@0: $merge_other = FALSE; Chris@0: if ($this->isForbidden() || $other->isForbidden()) { Chris@0: $result = static::forbidden(); Chris@0: if (!$this->isForbidden()) { Chris@0: if ($other instanceof AccessResultReasonInterface) { Chris@0: $result->setReason($other->getReason()); Chris@0: } Chris@0: $merge_other = TRUE; Chris@0: } Chris@0: else { Chris@0: if ($this instanceof AccessResultReasonInterface) { Chris@0: $result->setReason($this->getReason()); Chris@0: } Chris@0: } Chris@0: } Chris@0: elseif ($this->isAllowed() && $other->isAllowed()) { Chris@0: $result = static::allowed(); Chris@0: $merge_other = TRUE; Chris@0: } Chris@0: else { Chris@0: $result = static::neutral(); Chris@0: if (!$this->isNeutral()) { Chris@0: $merge_other = TRUE; Chris@0: if ($other instanceof AccessResultReasonInterface) { Chris@0: $result->setReason($other->getReason()); Chris@0: } Chris@0: } Chris@0: else { Chris@0: if ($this instanceof AccessResultReasonInterface) { Chris@0: $result->setReason($this->getReason()); Chris@0: } Chris@0: } Chris@0: } Chris@0: $result->inheritCacheability($this); Chris@0: if ($merge_other) { Chris@0: $result->inheritCacheability($other); Chris@0: // If this access result is not cacheable, then an AND with another access Chris@0: // result must also not be cacheable, except if the other access result Chris@0: // has isForbidden() === TRUE. isForbidden() access results are contagious Chris@0: // in that they propagate regardless of the other value. Chris@0: if ($this->getCacheMaxAge() === 0 && !$result->isForbidden()) { Chris@0: $result->setCacheMaxAge(0); Chris@0: } Chris@0: } Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Inherits the cacheability of the other access result, if any. Chris@0: * Chris@17: * This method differs from addCacheableDependency() in how it handles Chris@17: * max-age, because it is designed to inherit the cacheability of the second Chris@17: * operand in the andIf() and orIf() operations. There, the situation Chris@0: * "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000 Chris@0: * as the end result. Chris@0: * Chris@0: * @param \Drupal\Core\Access\AccessResultInterface $other Chris@0: * The other access result, whose cacheability (if any) to inherit. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function inheritCacheability(AccessResultInterface $other) { Chris@0: $this->addCacheableDependency($other); Chris@0: if ($other instanceof CacheableDependencyInterface) { Chris@0: if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) { Chris@0: $this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge())); Chris@0: } Chris@0: else { Chris@0: $this->setCacheMaxAge($other->getCacheMaxAge()); Chris@0: } Chris@0: } Chris@0: return $this; Chris@0: } Chris@0: Chris@0: }