core/lib/Drupal/Core/Access/AccessCheckResult.php | 44 +++++++ .../lib/Drupal/Core/Access/AccessibleInterface.php | 4 +- core/lib/Drupal/Core/Access/CsrfAccessCheck.php | 33 ++--- core/lib/Drupal/Core/Access/DefaultAccessCheck.php | 33 ++--- core/lib/Drupal/Core/Cache/Cacheability.php | 134 ++++++++++++++++++++ .../Core/Cache/CacheabilityAffectorInterface.php | 52 -------- core/lib/Drupal/Core/Cache/CacheableInterface.php | 2 +- core/lib/Drupal/Core/Entity/EntityAccessCheck.php | 33 ++--- .../Drupal/Core/Entity/EntityAccessController.php | 139 ++++++++++++++------- .../Entity/EntityAccessControllerInterface.php | 9 +- .../Drupal/Core/Entity/EntityCreateAccessCheck.php | 28 ++--- core/lib/Drupal/Core/Field/FieldItemList.php | 6 +- .../Drupal/Core/Field/FieldItemListInterface.php | 4 +- core/lib/Drupal/Core/Routing/Access/AccessBase.php | 52 -------- .../Drupal/Core/Routing/Access/AccessInterface.php | 7 +- core/lib/Drupal/Core/Theme/ThemeAccessCheck.php | 31 ++--- .../src/Access/BookNodeIsRemovableAccessCheck.php | 29 ++--- .../src/Access/ConfigTranslationFormAccess.php | 31 ++--- .../src/Access/ConfigTranslationOverviewAccess.php | 36 +++--- .../contact/src/Access/ContactPageAccess.php | 54 ++++---- .../Access/ContentTranslationManageAccessCheck.php | 47 ++++--- .../Access/ContentTranslationOverviewAccess.php | 43 +++---- .../field_ui/src/Access/FormModeAccessCheck.php | 46 +++---- .../field_ui/src/Access/ViewModeAccessCheck.php | 51 +++----- .../modules/node/src/Access/NodeAddAccessCheck.php | 26 ++-- core/modules/node/src/NodeAccessController.php | 56 +++++++-- core/modules/node/src/NodeGrantDatabaseStorage.php | 7 +- .../node/src/NodeGrantDatabaseStorageInterface.php | 6 +- core/modules/node/src/Plugin/Search/NodeSearch.php | 7 +- .../Plugin/Field/FieldType/PathFieldItemList.php | 13 +- core/modules/rest/src/Access/CSRFAccessCheck.php | 31 ++--- .../src/Access/ShortcutSetSwitchAccessCheck.php | 48 +++---- core/modules/system/entity.api.php | 70 +++++++---- core/modules/system/src/Access/CronAccessCheck.php | 37 ++---- .../src/Access/DefinedTestAccessCheck.php | 34 ++--- .../src/Access/TestAccessCheck.php | 25 +--- .../src/Access/ViewOwnTrackerAccessCheck.php | 29 ++--- .../update/src/Access/UpdateManagerAccessCheck.php | 26 +--- core/modules/user/src/Access/LoginStatusCheck.php | 29 ++--- .../user/src/Access/PermissionAccessCheck.php | 29 ++--- .../user/src/Access/RegisterAccessCheck.php | 29 ++--- core/modules/user/src/Access/RoleAccessCheck.php | 36 +++--- core/modules/user/src/Plugin/Search/UserSearch.php | 7 +- core/modules/views/src/ViewsAccessCheck.php | 29 ++--- 44 files changed, 750 insertions(+), 772 deletions(-) diff --git a/core/lib/Drupal/Core/Access/AccessCheckResult.php b/core/lib/Drupal/Core/Access/AccessCheckResult.php new file mode 100644 index 0000000..9b592d3 --- /dev/null +++ b/core/lib/Drupal/Core/Access/AccessCheckResult.php @@ -0,0 +1,44 @@ +cacheability = new Cacheability($is_cacheable); + } + +} diff --git a/core/lib/Drupal/Core/Access/AccessibleInterface.php b/core/lib/Drupal/Core/Access/AccessibleInterface.php index faa5a6f..4337f1d 100644 --- a/core/lib/Drupal/Core/Access/AccessibleInterface.php +++ b/core/lib/Drupal/Core/Access/AccessibleInterface.php @@ -23,8 +23,8 @@ * (optional) The user for which to check access, or NULL to check access * for the current user. Defaults to NULL. * - * @return bool|null - * self::ALLOW, self::DENY, or self::KILL. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access($operation, AccountInterface $account = NULL); diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php index 0b8e13f..0be99ac 100644 --- a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface; use Symfony\Component\Routing\Route; use Symfony\Component\HttpFoundation\Request; @@ -18,7 +18,7 @@ * a token generated by \Drupal::csrfToken()->get() using the same value as the * "_csrf_token" parameter in the route. */ -class CsrfAccessCheck extends AccessBase { +class CsrfAccessCheck implements RoutingAccessInterface { /** * The CSRF token generator. @@ -45,13 +45,16 @@ function __construct(CsrfTokenGenerator $csrf_token) { * @param \Symfony\Component\HttpFoundation\Request $request * The request object. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, Request $request) { + $access = new AccessCheckResult(FALSE); + // If this is the controller request, check CSRF access as normal. if ($request->attributes->get('_controller_request')) { - return $this->csrfToken->validate($request->query->get('token'), $request->attributes->get('_system_path')) ? static::ALLOW : static::KILL; + $access->value = $this->csrfToken->validate($request->query->get('token'), $request->attributes->get('_system_path')) ? static::ALLOW : static::KILL; + return $access; } // Otherwise, this could be another requested access check that we don't @@ -59,27 +62,15 @@ public function access(Route $route, Request $request) { $conjunction = $route->getOption('_access_mode') ?: 'ANY'; // Return ALLOW if all access checks are needed. if ($conjunction == 'ALL') { - return static::ALLOW; + $access->value = static::ALLOW; + return $access; } // Return DENY otherwise, as another access checker should grant access // for the route. else { - return static::DENY; + $access->value = static::DENY; + return $access; } } - /** - * {@inheritdoc} - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); - } - } diff --git a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php index 25e045f..6620cd7 100644 --- a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php +++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php @@ -7,13 +7,13 @@ namespace Drupal\Core\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface; use Symfony\Component\Routing\Route; /** * Allows access to routes to be controlled by an '_access' boolean parameter. */ -class DefaultAccessCheck extends AccessBase { +class DefaultAccessCheck implements RoutingAccessInterface { /** * Checks access to the route based on the _access parameter. @@ -21,35 +21,22 @@ class DefaultAccessCheck extends AccessBase { * @param \Symfony\Component\Routing\Route $route * The route to check against. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route) { + $access = new AccessCheckResult(TRUE); + if ($route->getRequirement('_access') === 'TRUE') { - return static::ALLOW; + $access->value = static::ALLOW; } elseif ($route->getRequirement('_access') === 'FALSE') { - return static::KILL; + $access->value = static::KILL; } else { - return static::DENY; + $access->value = static::DENY; } - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - * - * This is globally cacheable. - */ - public function getCacheContexts() { - return array(); + return $access; } } diff --git a/core/lib/Drupal/Core/Cache/Cacheability.php b/core/lib/Drupal/Core/Cache/Cacheability.php new file mode 100644 index 0000000..1d96f93 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/Cacheability.php @@ -0,0 +1,134 @@ +setCacheable($is_cacheable); + // Typically, cache items are invalidated via associated cache tags, not via + // a maximum age. + $this->setMaxAge(Cache::PERMANENT); + } + + public function merge(Cacheability $other) { + $this->isCacheable = $this->isCacheable && $other->isCacheable; + $this->addContexts($other->contexts); + $this->addTags($other->tags); + // Use the lowest max-age. + if ($this->maxAge === Cache::PERMANENT) { + // The other max-age is either lower or equal. + $this->setMaxAge($other->maxAge); + } + else { + $this->setMaxAge(min($this->maxAge, $other->maxAge)); + } + return $this; + } + + public function setCacheable($is_cacheable) { + $this->isCacheable = $is_cacheable; + return $this; + } + + public function setKeys($keys) { + $this->keys = $keys; + return $this; + } + + public function setContexts($contexts) { + $this->contexts = $contexts; + return $this; + } + + public function addContexts($contexts) { + $this->contexts = array_unique(array_merge($this->contexts, $contexts)); + return $this; + } + + public function setTags($tags) { + $this->tags = $tags; + return $this; + } + + public function addTags($tags) { + $this->tags = NestedArray::mergeDeep($this->tags, $tags); + return $this; + } + + public function setMaxAge($max_age) { + $this->maxAge = $max_age; + return $this; + } + + public function setBin($bin) { + $this->bin = $bin; + return $this; + } + +} diff --git a/core/lib/Drupal/Core/Cache/CacheabilityAffectorInterface.php b/core/lib/Drupal/Core/Cache/CacheabilityAffectorInterface.php deleted file mode 100644 index 093d278..0000000 --- a/core/lib/Drupal/Core/Cache/CacheabilityAffectorInterface.php +++ /dev/null @@ -1,52 +0,0 @@ -attributes->has($entity_type)) { $entity = $request->attributes->get($entity_type); if ($entity instanceof EntityInterface) { - return $entity->access($operation, $account) ? static::ALLOW : static::DENY; + return $entity->access($operation, $account); } } + // No opinion, so other access checks should decide if access should be // allowed or not. - return static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable per user if we could associate the cache tag - * of the entity being accessed. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $access = new AccessCheckResult(FALSE); + $access->value = static::DENY; + return $access; } } diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php index f20ef9c..f8a1fe8 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessController.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php @@ -7,6 +7,9 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Access\AccessInterface; +use Drupal\Core\Cache\Cacheability; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -71,17 +74,17 @@ public function access(EntityInterface $entity, $operation, $langcode = Language // We grant access to the entity if both of these conditions are met: // - No modules say to deny access. // - At least one module says to grant access. - $access = array_merge( + $all_access = array_merge( $this->moduleHandler()->invokeAll('entity_access', array($entity, $operation, $account, $langcode)), $this->moduleHandler()->invokeAll($entity->getEntityTypeId() . '_access', array($entity, $operation, $account, $langcode)) ); - if (($return = $this->processAccessHookResults($access)) === NULL) { + if (($access = $this->processAccessHookResults($all_access)) === AccessInterface::DENY) { // No module had an opinion about the access, so let's the access - // controller check create access. - $return = (bool) $this->checkAccess($entity, $operation, $langcode, $account); + // controller check access. + $access = $this->checkAccess($entity, $operation, $langcode, $account); } - return $this->setCache($return, $entity->uuid(), $operation, $langcode, $account); + return $this->setCache($access, $entity->uuid(), $operation, $langcode, $account); } /** @@ -89,23 +92,37 @@ public function access(EntityInterface $entity, $operation, $langcode = Language * - No modules say to deny access. * - At least one module says to grant access. * - * @param array $access + * @param \Drupal\Core\Access\AccessCheckResult[] $access * An array of access results of the fired access hook. * - * @return bool|null - * Returns FALSE if access should be denied, TRUE if access should be - * granted and NULL if no module denied access. + * @return \Drupal\Core\Access\AccessCheckResult + * The combined result of the various access checks' results. All their + * cacheability metadata is merged as well. */ protected function processAccessHookResults(array $access) { - if (in_array(FALSE, $access, TRUE)) { - return FALSE; + // Calculate the combined result, starting with the merged cacheability + // metadata. + $result = new AccessCheckResult(TRUE); + $merge_cacheability = function(Cacheability $carry, AccessCheckResult $current) { + return $carry->merge($current->cacheability); + }; + $result->cacheability = array_reduce($access, $merge_cacheability, $result->cacheability); + + $get_access = function(AccessCheckResult $access_check_result) { + return $access_check_result->value; + }; + $access_check_values = array_map($get_access, $access); + + if (in_array(AccessInterface::KILL, $access_check_values, TRUE)) { + $result->value = AccessInterface::KILL; } - elseif (in_array(TRUE, $access, TRUE)) { - return TRUE; + elseif (in_array(AccessInterface::ALLOW, $access_check_values, TRUE)) { + $result->value = AccessInterface::ALLOW; } else { - return; + $result->value = AccessInterface::DENY; } + return $result; } /** @@ -124,19 +141,28 @@ protected function processAccessHookResults(array $access) { * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * - * @return bool|null - * TRUE if access was granted, FALSE if access was denied and NULL if access - * could not be determined. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { + $access = new AccessCheckResult(TRUE); + if ($operation == 'delete' && $entity->isNew()) { - return FALSE; + $access->value = AccessInterface::KILL; + // Cacheable until entity is modified. + $access->cacheability->addTags($entity->getCacheTag()); + return $access; } + if ($admin_permission = $this->entityType->getAdminPermission()) { - return $account->hasPermission($admin_permission); + // Cacheable per role. + $access->cacheability->addContexts('cache_context.user.roles'); + $access->value = $account->hasPermission($admin_permission) ? AccessInterface::ALLOW : AccessInterface::KILL; + return $access; } else { - return NULL; + $access->value = AccessInterface::DENY; + return $access; } } @@ -154,10 +180,9 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * - * @return bool|null - * TRUE if access was granted, FALSE if access was denied and NULL if there - * is no record for the given user, operation, langcode and entity in the - * cache. + * @return \Drupal\Core\Access\AccessCheckResult|null + * The cached AccessCheckResult, or NULL if there is no record for the given + * user, operation, langcode and entity in the cache. */ protected function getCache($cid, $operation, $langcode, AccountInterface $account) { // Return from cache if a value has been set for it previously. @@ -169,8 +194,8 @@ protected function getCache($cid, $operation, $langcode, AccountInterface $accou /** * Statically caches whether the given user has access. * - * @param bool $access - * TRUE if the user has access, FALSE otherwise. + * @param \Drupal\Core\Access\AccessCheckResult $access + * Whether the user has access, plus cacheability metadata. * @param string $cid * Unique string identifier for the entity/operation, for example the * entity UUID or a custom string. @@ -182,12 +207,12 @@ protected function getCache($cid, $operation, $langcode, AccountInterface $accou * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * - * @return bool - * TRUE if access was granted, FALSE otherwise. + * @return \Drupal\Core\Access\AccessCheckResult + * Whether the user has access, plus cacheability metadata. */ protected function setCache($access, $cid, $operation, $langcode, AccountInterface $account) { // Save the given value in the static cache and directly return it. - return $this->accessCache[$account->id()][$cid][$langcode][$operation] = (bool) $access; + return $this->accessCache[$account->id()][$cid][$langcode][$operation] = $access; } /** @@ -221,17 +246,17 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account = // We grant access to the entity if both of these conditions are met: // - No modules say to deny access. // - At least one module says to grant access. - $access = array_merge( + $all_access = array_merge( $this->moduleHandler()->invokeAll('entity_create_access', array($account, $context['langcode'])), $this->moduleHandler()->invokeAll($this->entityTypeId . '_create_access', array($account, $context['langcode'])) ); - if (($return = $this->processAccessHookResults($access)) === NULL) { + if (($access = $this->processAccessHookResults($all_access)) === AccessInterface::DENY) { // No module had an opinion about the access, so let's the access // controller check create access. - $return = (bool) $this->checkCreateAccess($account, $context, $entity_bundle); + $access = (bool) $this->checkCreateAccess($account, $context, $entity_bundle); } - return $this->setCache($return, $cid, 'create', $context['langcode'], $account); + return $this->setCache($access, $cid, 'create', $context['langcode'], $account); } /** @@ -248,17 +273,20 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account = * (optional) The bundle of the entity. Required if the entity supports * bundles, defaults to NULL otherwise. * - * @return bool|null - * TRUE if access was granted, FALSE if access was denied and NULL if access - * could not be determined. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + $access = new AccessCheckResult(TRUE); + // Cache per role. + $access->cacheability->addContexts(array('cache_context.user.roles')); if ($admin_permission = $this->entityType->getAdminPermission()) { - return $account->hasPermission($admin_permission); + $access->value = $account->hasPermission($admin_permission) ? AccessInterface::ALLOW : AccessInterface::KILL; } else { - return NULL; + $access->value = AccessInterface::DENY; } + return $access; } /** @@ -284,7 +312,13 @@ public function fieldAccess($operation, FieldDefinitionInterface $field_definiti $account = $this->prepareUser($account); // Get the default access restriction that lives within this field. - $default = $items ? $items->defaultAccess($operation, $account) : TRUE; + if ($items) { + $default = $items->defaultAccess($operation, $account); + } + else { + $default = new AccessCheckResult(TRUE); + $default->value = AccessInterface::ALLOW; + } // Invoke hook and collect grants/denies for field access from other // modules. Our default access flag is masked under the ':default' key. @@ -303,16 +337,33 @@ public function fieldAccess($operation, FieldDefinitionInterface $field_definiti ); $this->moduleHandler()->alter('entity_field_access', $grants, $context); + + // Calculate the combined result, starting with the merged cacheability + // metadata. + $access = new AccessCheckResult(TRUE); + $merge_cacheability = function(Cacheability $carry, AccessCheckResult $current) { + return $carry->merge($current->cacheability); + }; + $access->cacheability = array_reduce($grants, $merge_cacheability, $access->cacheability); + + $get_access = function(AccessCheckResult $access_check_result) { + return $access_check_result->value; + }; + $access_check_values = array_map($get_access, $grants); + // One grant being FALSE is enough to deny access immediately. - if (in_array(FALSE, $grants, TRUE)) { - return FALSE; + if (in_array(AccessInterface::KILL, $access_check_values, TRUE)) { + $access->value = AccessInterface::KILL; } // At least one grant has the explicit opinion to allow access. - if (in_array(TRUE, $grants, TRUE)) { - return TRUE; + elseif (in_array(AccessInterface::ALLOW, $access_check_values, TRUE)) { + $access->value = AccessInterface::ALLOW; } // All grants are NULL and have no opinion - deny access in that case. - return FALSE; + else { + $access->value = AccessInterface::KILL; + } + return $access; } } diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php index 2a5851f..f9cd754 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php @@ -36,8 +36,8 @@ * (optional) The user session for which to check access, or NULL to check * access for the current user. Defaults to NULL. * - * @return bool - * TRUE if access was granted, FALSE otherwise. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(EntityInterface $entity, $operation, $langcode = LanguageInterface::LANGCODE_DEFAULT, AccountInterface $account = NULL); @@ -53,10 +53,13 @@ public function access(EntityInterface $entity, $operation, $langcode = Language * @param array $context * (optional) An array of key-value pairs to pass additional context when * needed. + * + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array()); - /** + /** * Clears all cached access checks. */ public function resetCache(); diff --git a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php index fabb013..ac52363 100644 --- a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php +++ b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php @@ -7,7 +7,8 @@ namespace Drupal\Core\Entity; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; @@ -15,7 +16,7 @@ /** * Defines an access checker for entity creation. */ -class EntityCreateAccessCheck extends AccessBase { +class EntityCreateAccessCheck implements AccessInterface { /** * The entity manager. @@ -51,8 +52,8 @@ public function __construct(EntityManagerInterface $entity_manager) { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, Request $request, AccountInterface $account) { list($entity_type, $bundle) = explode(':', $route->getRequirement($this->requirementsKey) . ':'); @@ -66,24 +67,13 @@ public function access(Route $route, Request $request, AccountInterface $account } // If we were unable to replace all placeholders, deny access. if (strpos($bundle, '{') !== FALSE) { - return static::DENY; + $access = new AccessCheckResult(FALSE); + $access->value = static::DENY; + return $access; } } - return $this->entityManager->getAccessController($entity_type)->createAccess($bundle, $account) ? static::ALLOW : static::DENY; - } - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user'); + return $this->entityManager->getAccessController($entity_type)->createAccess($bundle, $account); } } diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index 2e0b442..53ef463 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Field; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\Core\TypedData\TypedDataInterface; @@ -195,7 +197,9 @@ public function access($operation = 'view', AccountInterface $account = NULL) { */ public function defaultAccess($operation = 'view', AccountInterface $account = NULL) { // Grant access per default. - return TRUE; + $access = new AccessCheckResult(TRUE); + $access->value = AccessInterface::ALLOW; + return $access; } /** diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php index 651f1e4..4e87b64 100644 --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php @@ -85,8 +85,8 @@ public function getSetting($setting_name); * See \Drupal\Core\Entity\EntityAccessControllerInterface::fieldAccess() for * the parameter documentation. * - * @return bool - * TRUE if access to this field is allowed per default, FALSE otherwise. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function defaultAccess($operation = 'view', AccountInterface $account = NULL); diff --git a/core/lib/Drupal/Core/Routing/Access/AccessBase.php b/core/lib/Drupal/Core/Routing/Access/AccessBase.php deleted file mode 100644 index 1dc3e30..0000000 --- a/core/lib/Drupal/Core/Routing/Access/AccessBase.php +++ /dev/null @@ -1,52 +0,0 @@ -checkAccess($theme) ? static::ALLOW : static::DENY; + $access = new AccessCheckResult(TRUE); + // Cacheable until the theme is modified. + $access->cacheability->setTags(array('theme' => $theme)); + $access->value = $this->checkAccess($theme) ? static::ALLOW : static::DENY; + return $access; } /** @@ -41,20 +46,4 @@ public function checkAccess($theme) { return !empty($themes[$theme]->status); } - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - * - * This is globally cacheable. - */ - public function getCacheContexts() { - return array(); - } - } diff --git a/core/modules/book/src/Access/BookNodeIsRemovableAccessCheck.php b/core/modules/book/src/Access/BookNodeIsRemovableAccessCheck.php index 1f8e73d..aa80e00 100644 --- a/core/modules/book/src/Access/BookNodeIsRemovableAccessCheck.php +++ b/core/modules/book/src/Access/BookNodeIsRemovableAccessCheck.php @@ -8,6 +8,8 @@ namespace Drupal\book\Access; use Drupal\book\BookManagerInterface; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Cache\Cache; use Drupal\Core\Routing\Access\AccessBase; use Drupal\node\NodeInterface; @@ -39,28 +41,15 @@ public function __construct(BookManagerInterface $book_manager) { * @param \Drupal\node\NodeInterface $node * The node requested to be removed from its book. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(NodeInterface $node) { - return $this->bookManager->checkNodeIsRemovable($node) ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable globally if we could associate the cache tag - * of the book node being checked. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $access = new AccessCheckResult(TRUE); + // Cacheable until the book node is modified. + $access->cacheability->setTags($node->getCacheTag()); + $access->value = $this->bookManager->checkNodeIsRemovable($node) ? static::ALLOW : static::DENY; + return $access; } } diff --git a/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php b/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php index 5cf9c91..b50472e 100644 --- a/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php +++ b/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php @@ -8,6 +8,7 @@ namespace Drupal\config_translation\Access; use Drupal\Core\Session\AccountInterface; +use Drupal\language\Entity\Language; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; @@ -22,8 +23,8 @@ class ConfigTranslationFormAccess extends ConfigTranslationOverviewAccess { public function access(Route $route, Request $request, AccountInterface $account) { // For the translation forms we have a target language, so we need some // checks in addition to the checks performed for the translation overview. - $base_access = parent::access($route, $request, $account); - if ($base_access === static::ALLOW) { + $result = parent::access($route, $request, $account); + if ($result->value === static::ALLOW) { $target_language = language_load($request->attributes->get('langcode')); // Make sure that the target language is not locked, and that the target @@ -35,26 +36,14 @@ public function access(Route $route, Request $request, AccountInterface $account !$target_language->locked && $target_language->id != $this->sourceLanguage->id; - return $access ? static::ALLOW : static::DENY; + $result->value = $access ? static::ALLOW : static::DENY; + // Retain the same cacheability as the parent access check's result, but + // also make it dependent on the target language. + $result->cacheability->addTags(Language::load($target_language->getId())->getCacheTag()); + return $result; } - return static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable per role if we could associate the cache tag - * of the target language. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $result->value = static::DENY; + return $result; } } diff --git a/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php b/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php index 700f9d8..7d64cf2 100644 --- a/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php +++ b/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php @@ -8,15 +8,18 @@ namespace Drupal\config_translation\Access; use Drupal\config_translation\ConfigMapperManagerInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\language\Entity\Language; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; /** * Checks access for displaying the configuration translation overview. */ -class ConfigTranslationOverviewAccess extends AccessBase { +class ConfigTranslationOverviewAccess implements AccessInterface { /** * The mapper plugin discovery service. @@ -52,10 +55,12 @@ public function __construct(ConfigMapperManagerInterface $config_mapper_manager) * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, Request $request, AccountInterface $account) { + $result = new AccessCheckResult(TRUE); + /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($route->getDefault('plugin_id')); $mapper->populateFromRequest($request); @@ -71,24 +76,13 @@ public function access(Route $route, Request $request, AccountInterface $account $mapper->hasTranslatable() && !$this->sourceLanguage->locked; - return $access ? static::ALLOW : static::DENY; - } + $result->value = $access ? static::ALLOW : static::DENY; + // Cacheable per role until the language entity is modified. + $result->cacheability + ->setTags(Language::load($this->sourceLanguage->getId())->getCacheTag()) + ->setContexts('cache_context.user.roles'); - /** - * {@inheritdoc} - * - * @todo This would be cacheable per role if we could associate the cache tag - * of the config mapper's and that of its source language. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + return $result; } } diff --git a/core/modules/contact/src/Access/ContactPageAccess.php b/core/modules/contact/src/Access/ContactPageAccess.php index b890d9d..b68441e 100644 --- a/core/modules/contact/src/Access/ContactPageAccess.php +++ b/core/modules/contact/src/Access/ContactPageAccess.php @@ -7,8 +7,10 @@ namespace Drupal\contact\Access; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Drupal\user\UserDataInterface; use Drupal\user\UserInterface; @@ -16,7 +18,7 @@ /** * Access check for contact_personal_page route. */ -class ContactPageAccess extends AccessBase { +class ContactPageAccess implements AccessInterface { /** * The contact settings config object. @@ -53,62 +55,58 @@ public function __construct(ConfigFactoryInterface $config_factory, UserDataInte * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(UserInterface $user, AccountInterface $account) { $contact_account = $user; + $access = new AccessCheckResult(TRUE); + + // Cacheable per user and role until the contacted user is modified. + $access->cacheability + ->setContexts(array('cache_context.user', 'cache_context.user.roles')) + ->setTags($user->getCacheTag()); // Anonymous users cannot have contact forms. if ($contact_account->isAnonymous()) { - return static::DENY; + $access->value = static::DENY; + return $access; } // Users may not contact themselves. if ($account->id() == $contact_account->id()) { - return static::DENY; + $access->value = static::DENY; + return $access; } // User administrators should always have access to personal contact forms. if ($account->hasPermission('administer users')) { - return static::ALLOW; + $access->value = static::ALLOW; + return $access; } // If requested user has been blocked, do not allow users to contact them. if ($contact_account->isBlocked()) { - return static::DENY; + $access->value = static::DENY; + return $access; } // If the requested user has disabled their contact form, do not allow users // to contact them. $account_data = $this->userData->get('contact', $contact_account->id(), 'enabled'); if (isset($account_data) && empty($account_data)) { - return static::DENY; + $access->value = static::DENY; + return $access; } // If the requested user did not save a preference yet, deny access if the // configured default is disabled. else if (!$this->configFactory->get('contact.settings')->get('user_default_enabled')) { - return static::DENY; + $access->value = static::DENY; + return $access; } - return $account->hasPermission('access user contact forms') ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable per user if we could associate the cache tag - * of the user being contacted. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $access->value = $account->hasPermission('access user contact forms') ? static::ALLOW : static::DENY; + return $access; } } diff --git a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php index 9fee9cd..9cd143e 100644 --- a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php +++ b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php @@ -7,17 +7,20 @@ namespace Drupal\content_translation\Access; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\language\Entity\Language; use Symfony\Component\Routing\Route; use Symfony\Component\HttpFoundation\Request; /** * Access check for entity translation CRUD operation. */ -class ContentTranslationManageAccessCheck extends AccessBase { +class ContentTranslationManageAccessCheck implements AccessInterface { /** * The entity type manager. @@ -53,16 +56,26 @@ public function __construct(EntityManagerInterface $manager) { * (optional) For an update or delete operation, the language code of the * translation being updated or deleted. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, Request $request, AccountInterface $account, $source = NULL, $target = NULL, $language = NULL) { + $access = new AccessCheckResult(FALSE); + $entity_type = $request->attributes->get('_entity_type_id'); /** @var $entity \Drupal\Core\Entity\EntityInterface */ if ($entity = $request->attributes->get($entity_type)) { $operation = $route->getRequirement('_access_content_translation_manage'); $controller = content_translation_controller($entity_type, $account); + // Cacheable per role until the entity or the list of languages are + // modified. + $access->cacheability + ->setCacheable(TRUE) + ->setContexts(array('cache_context.user.roles')) + ->addTags($entity->getCacheTag()) + ->addTags(Language::load($source)->getListCacheTags()); + // Load translation. $translations = $entity->getTranslationLanguages(); $languages = language_list(); @@ -71,41 +84,27 @@ public function access(Route $route, Request $request, AccountInterface $account case 'create': $source = language_load($source) ?: $entity->language(); $target = language_load($target) ?: \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT); - return ($source->id != $target->id + $access->value = ($source->id != $target->id && isset($languages[$source->id]) && isset($languages[$target->id]) && !isset($translations[$target->id]) && $controller->getTranslationAccess($entity, $operation)) ? static::ALLOW : static::DENY; + return $access; case 'update': case 'delete': $language = language_load($language) ?: \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT); - return isset($languages[$language->id]) + $access->value = isset($languages[$language->id]) && $language->id != $entity->getUntranslated()->language()->id && isset($translations[$language->id]) && $controller->getTranslationAccess($entity, $operation) ? static::ALLOW : static::DENY; + return $access; } } - return static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable per user if we could associate the cache tag - * of the entity being translated. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $access->value = static::DENY; + return $access; } } diff --git a/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php b/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php index 0a835c6..377b710 100644 --- a/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php +++ b/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php @@ -7,15 +7,16 @@ namespace Drupal\content_translation\Access; +use Drupal\Core\Access\AccessCheckResult; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\HttpFoundation\Request; /** * Access check for entity translation overview. */ -class ContentTranslationOverviewAccess extends AccessBase { +class ContentTranslationOverviewAccess implements AccessInterface { /** * The entity type manager. @@ -42,21 +43,30 @@ public function __construct(EntityManagerInterface $manager) { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Request $request, AccountInterface $account) { + $result = new AccessCheckResult(FALSE); + $entity_type = $request->attributes->get('_entity_type_id'); if ($entity = $request->attributes->get($entity_type)) { // Get entity base info. $bundle = $entity->bundle(); + // Cacheable per user until the entity is modified. + $result->cacheability + ->setCacheable(TRUE) + ->setContexts(array('cache_context.user')) + ->addTags($entity->getCacheTag()); + // Get entity access callback. $definition = $this->entityManager->getDefinition($entity_type); $translation = $definition->get('translation'); $access_callback = $translation['content_translation']['access_callback']; if (call_user_func($access_callback, $entity)) { - return static::ALLOW; + $result->value = static::ALLOW; + return $result; } // Check per entity permission. @@ -65,28 +75,13 @@ public function access(Request $request, AccountInterface $account) { $permission = "translate {$bundle} {$entity_type}"; } if ($account->hasPermission($permission)) { - return static::ALLOW; + $result->value = static::ALLOW; + return $result; } } - return static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable per user if we could associate the cache tag - * of the entity being translated. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $result->value = static::DENY; + return $result; } } diff --git a/core/modules/field_ui/src/Access/FormModeAccessCheck.php b/core/modules/field_ui/src/Access/FormModeAccessCheck.php index 0d1a79a..198c44d 100644 --- a/core/modules/field_ui/src/Access/FormModeAccessCheck.php +++ b/core/modules/field_ui/src/Access/FormModeAccessCheck.php @@ -7,8 +7,9 @@ namespace Drupal\field_ui\Access; +use Drupal\Core\Access\AccessCheckResult; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; use Symfony\Component\HttpFoundation\Request; @@ -18,7 +19,7 @@ * * @see \Drupal\entity\Entity\EntityFormMode */ -class FormModeAccessCheck extends AccessBase { +class FormModeAccessCheck implements AccessInterface { /** * The entity manager. @@ -57,45 +58,36 @@ public function __construct(EntityManagerInterface $entity_manager) { * available via the {node_type} parameter rather than a {bundle} * parameter. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, Request $request, AccountInterface $account, $form_mode_name = 'default', $bundle = NULL) { + $access = new AccessCheckResult(FALSE); + if ($entity_type_id = $route->getDefault('entity_type_id')) { if (!isset($bundle)) { $entity_type = $this->entityManager->getDefinition($entity_type_id); $bundle = $request->attributes->get('_raw_variables')->get($entity_type->getBundleEntityType()); } + $entity_display = $this->entityManager->getStorage('entity_form_display')->load($entity_type_id . '.' . $bundle . '.' . $form_mode_name); + } - $visibility = FALSE; - if ($form_mode_name == 'default') { - $visibility = TRUE; - } - elseif ($entity_display = $this->entityManager->getStorage('entity_form_display')->load($entity_type_id . '.' . $bundle . '.' . $form_mode_name)) { - $visibility = $entity_display->status(); - } + if ($entity_display) { + // Cacheable per role, until the form display is modified. + $access->cacheability + ->setCacheable(TRUE) + ->setContexts(array('cache_context.user.roles')) + ->setTags($entity_display->getCacheTag()); + $visibility = ($form_mode_name == 'default') ? TRUE : $entity_display->status(); if ($visibility) { $permission = $route->getRequirement('_field_ui_form_mode_access'); - return $account->hasPermission($permission) ? static::ALLOW : static::DENY; + $access->value = $account->hasPermission($permission) ? static::ALLOW : static::DENY; } } - return static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user.roles'); + $access->value = static::DENY; + return $access; } } diff --git a/core/modules/field_ui/src/Access/ViewModeAccessCheck.php b/core/modules/field_ui/src/Access/ViewModeAccessCheck.php index 301350d..432ec26 100644 --- a/core/modules/field_ui/src/Access/ViewModeAccessCheck.php +++ b/core/modules/field_ui/src/Access/ViewModeAccessCheck.php @@ -7,8 +7,9 @@ namespace Drupal\field_ui\Access; +use Drupal\Core\Access\AccessCheckResult; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; use Symfony\Component\HttpFoundation\Request; @@ -18,7 +19,7 @@ * * @see \Drupal\entity\Entity\EntityViewMode */ -class ViewModeAccessCheck extends AccessBase { +class ViewModeAccessCheck implements AccessInterface { /** * The entity manager. @@ -57,49 +58,37 @@ public function __construct(EntityManagerInterface $entity_manager) { * available via the {node_type} parameter rather than a {bundle} * parameter. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, Request $request, AccountInterface $account, $view_mode_name = 'default', $bundle = NULL) { + $access = new AccessCheckResult(FALSE); + if ($entity_type_id = $route->getDefault('entity_type_id')) { if (!isset($bundle)) { $entity_type = $this->entityManager->getDefinition($entity_type_id); $bundle = $request->attributes->get('_raw_variables')->get($entity_type->getBundleEntityType()); } + $entity_display = $this->entityManager->getStorage('entity_view_display')->load($entity_type_id . '.' . $bundle . '.' . $view_mode_name); + } - $visibility = FALSE; - if ($view_mode_name == 'default') { - $visibility = TRUE; - } - elseif ($entity_display = $this->entityManager->getStorage('entity_view_display')->load($entity_type_id . '.' . $bundle . '.' . $view_mode_name)) { - $visibility = $entity_display->status(); - } + if ($entity_display) { + // Cacheable per role until the view display is modified. + $access->cacheability + ->setCacheable(TRUE) + ->setContexts(array('cache_context.user.roles')) + ->setTags($entity_display->getCacheTag()); + $visibility = ($view_mode_name == 'default') ? TRUE : $entity_display->status(); if ($visibility) { $permission = $route->getRequirement('_field_ui_view_mode_access'); - return $account->hasPermission($permission) ? static::ALLOW : static::DENY; + $access->value = $account->hasPermission($permission) ? static::ALLOW : static::DENY; + return $access; } } - return static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable per user if we could associate the cache tag - * of the entity being translated. - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user.roles'); + $access->value = static::DENY; + return $access; } - } diff --git a/core/modules/node/src/Access/NodeAddAccessCheck.php b/core/modules/node/src/Access/NodeAddAccessCheck.php index 90ec739..7832bff 100644 --- a/core/modules/node/src/Access/NodeAddAccessCheck.php +++ b/core/modules/node/src/Access/NodeAddAccessCheck.php @@ -7,15 +7,16 @@ namespace Drupal\node\Access; +use Drupal\Core\Access\AccessCheckResult; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Drupal\node\NodeTypeInterface; /** * Determines access to for node add pages. */ -class NodeAddAccessCheck extends AccessBase { +class NodeAddAccessCheck implements AccessInterface { /** * The entity manager. @@ -54,25 +55,14 @@ public function access(AccountInterface $account, NodeTypeInterface $node_type = } // If checking whether a node of any type may be created. foreach (node_permissions_get_configured_types() as $node_type) { - if ($access_controller->createAccess($node_type->id(), $account)) { - return static::ALLOW; + if (($access = $access_controller->createAccess($node_type->id(), $account)) && $access->value === static::ALLOW) { + return $access; } } - return static::DENY; - } - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user'); + $access = new AccessCheckResult(TRUE); + $access->value = static::DENY; + return $access; } } diff --git a/core/modules/node/src/NodeAccessController.php b/core/modules/node/src/NodeAccessController.php index 6c058ad..03dbb94 100644 --- a/core/modules/node/src/NodeAccessController.php +++ b/core/modules/node/src/NodeAccessController.php @@ -7,6 +7,8 @@ namespace Drupal\node; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Access\AccessInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Entity\EntityControllerInterface; @@ -27,7 +29,7 @@ class NodeAccessController extends EntityAccessController implements NodeAccessC /** * The node grant storage. * - * @var \Drupal\node\NodeGrantStorageInterface + * @var \Drupal\node\NodeGrantDatabaseStorageInterface */ protected $grantStorage; @@ -59,12 +61,19 @@ public static function createInstance(ContainerInterface $container, EntityTypeI * {@inheritdoc} */ public function access(EntityInterface $entity, $operation, $langcode = LanguageInterface::LANGCODE_DEFAULT, AccountInterface $account = NULL) { + // The two high-level access pre-requisites are cacheable per role. + $access = new AccessCheckResult(TRUE); + $access->cacheability->setContexts(array('cache_context.user.roles')); + if (user_access('bypass node access', $account)) { - return TRUE; + $access->value = AccessInterface::ALLOW; + return $access; } if (!user_access('access content', $account)) { - return FALSE; + $access->value = AccessInterface::KILL; + return $access; } + return parent::access($entity, $operation, $langcode, $account); } @@ -74,11 +83,18 @@ public function access(EntityInterface $entity, $operation, $langcode = Language public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array()) { $account = $this->prepareUser($account); + // The two high-level access pre-requisites are cacheable per role. + $access = new AccessCheckResult(TRUE); + $access->cacheability->setContexts(array('cache_context.user.roles')); + + if (user_access('bypass node access', $account)) { - return TRUE; + $access->value = AccessInterface::ALLOW; + return $access; } if (!user_access('access content', $account)) { - return FALSE; + $access->value = AccessInterface::KILL; + return $access; } return parent::createAccess($entity_bundle, $account, $context); @@ -95,15 +111,21 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc $status = $translation->isPublished(); $uid = $translation->getOwnerId(); + $access = new AccessCheckResult(TRUE); + // Check if authors can view their own unpublished nodes. if ($operation === 'view' && !$status && user_access('view own unpublished content', $account)) { - + // Cacheable per user until the node is modified. + $access->cacheability + ->addContexts(array('cache_context.user')) + ->addTags($node->getCacheTag()); if ($account->id() != 0 && $account->id() == $uid) { - return TRUE; + $access->value = AccessInterface::ALLOW; + return $access; } } - // If no module specified either allow or deny, we fall back to the + // If no module specified either ALLOW or KILL, we fall back to the // node_access table. if (($grants = $this->grantStorage->access($node, $operation, $langcode, $account)) !== NULL) { return $grants; @@ -112,18 +134,32 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc // If no modules implement hook_node_grants(), the default behavior is to // allow all users to view published nodes, so reflect that here. if ($operation === 'view') { - return $status; + $access->value = $status ? AccessInterface::ALLOW : AccessInterface::KILL; + // Cacheable until the node is modified. + $access->cacheability->addTags($node->getCacheTag()); + return $access; } + + $access->value = AccessInterface::DENY; + return $access; } /** * {@inheritdoc} */ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + $access = new AccessCheckResult(TRUE); $configured_types = node_permissions_get_configured_types(); if (isset($configured_types[$entity_bundle])) { - return user_access('create ' . $entity_bundle . ' content', $account); + $access->value = user_access('create ' . $entity_bundle . ' content', $account) ? AccessInterface::ALLOW : AccessInterface::DENY; + // Cacheable per role. + $access->cacheability->addContexts(array('cache_context.user.roles')); + return $access; + } + else { + $access->value = AccessInterface::DENY; } + return $access; } /** diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php index 9503713..03631eb 100644 --- a/core/modules/node/src/NodeGrantDatabaseStorage.php +++ b/core/modules/node/src/NodeGrantDatabaseStorage.php @@ -7,6 +7,7 @@ namespace Drupal\node; +use Drupal\Core\Access\AccessCheckResult; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Database\Query\Condition; @@ -57,7 +58,7 @@ public function access(NodeInterface $node, $operation, $langcode, AccountInterf // If no module implements the hook or the node does not have an id there is // no point in querying the database for access grants. if (!$this->moduleHandler->getImplementations('node_grants') || !$node->id()) { - return; + return NULL; } // Check the database for potential access grants. @@ -86,7 +87,9 @@ public function access(NodeInterface $node, $operation, $langcode, AccountInterf $query->condition($grants); } - return $query->execute()->fetchField(); + $access = new AccessCheckResult(FALSE); + $access->value = (bool) $query->execute()->fetchField(); + return $access; } /** diff --git a/core/modules/node/src/NodeGrantDatabaseStorageInterface.php b/core/modules/node/src/NodeGrantDatabaseStorageInterface.php index c67025b..8b046f1 100644 --- a/core/modules/node/src/NodeGrantDatabaseStorageInterface.php +++ b/core/modules/node/src/NodeGrantDatabaseStorageInterface.php @@ -105,8 +105,10 @@ public function writeDefault(); * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * - * @return bool|null - * TRUE if access was granted, FALSE if access was denied or NULL if no + * @return \Drupal\Core\Access\AccessCheckResult|null + * The access check result, with cacheability metadata. The access check + * result's value is AccessInterface::ALLOW if access was granted, + * AccessInterface::KILL if access was denied. NULL is returned when no * module implements hook_node_grants(), the node does not (yet) have an id * or none of the implementing modules explicitly granted or denied access. */ diff --git a/core/modules/node/src/Plugin/Search/NodeSearch.php b/core/modules/node/src/Plugin/Search/NodeSearch.php index 78cdedf..c56f8fb 100644 --- a/core/modules/node/src/Plugin/Search/NodeSearch.php +++ b/core/modules/node/src/Plugin/Search/NodeSearch.php @@ -8,6 +8,7 @@ namespace Drupal\node\Plugin\Search; use Drupal\Component\Utility\String; +use Drupal\Core\Access\AccessCheckResult; use Drupal\Core\Config\Config; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\SelectExtender; @@ -156,7 +157,11 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * {@inheritdoc} */ public function access($operation = 'view', AccountInterface $account = NULL) { - return !empty($account) && $account->hasPermission('access content'); + $access = new AccessCheckResult(TRUE); + $access->value = !empty($account) && $account->hasPermission('access content'); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + return $access; } /** diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php b/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php index cf7ec2b..5851d6b 100644 --- a/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php +++ b/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php @@ -7,6 +7,8 @@ namespace Drupal\path\Plugin\Field\FieldType; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Access\AccessInterface; use Drupal\Core\Field\FieldItemList; use Drupal\Core\Session\AccountInterface; @@ -19,10 +21,17 @@ class PathFieldItemList extends FieldItemList { * {@inheritdoc} */ public function defaultAccess($operation = 'view', AccountInterface $account = NULL) { + $access = new AccessCheckResult(TRUE); if ($operation == 'view') { - return TRUE; + $access->value = AccessInterface::ALLOW; + return $access; } - return $account->hasPermission('create url aliases') || $account->hasPermission('administer url aliases'); + + // The above access check result depends solely on the operation; from here + // on, it also depends on the user role. Hence vary per role. + $access->cacheability->addContexts('cache_context.user.roles'); + $access->value = ($account->hasPermission('create url aliases') || $account->hasPermission('administer url aliases')) ? AccessInterface::ALLOW : AccessInterface::DENY; + return $access; } } diff --git a/core/modules/rest/src/Access/CSRFAccessCheck.php b/core/modules/rest/src/Access/CSRFAccessCheck.php index e0ae189..86357a0 100644 --- a/core/modules/rest/src/Access/CSRFAccessCheck.php +++ b/core/modules/rest/src/Access/CSRFAccessCheck.php @@ -8,7 +8,8 @@ namespace Drupal\rest\Access; use Drupal\Core\Access\AccessCheckInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; use Symfony\Component\HttpFoundation\Request; @@ -16,7 +17,7 @@ /** * Access protection against CSRF attacks. */ -class CSRFAccessCheck extends AccessBase implements AccessCheckInterface { +class CSRFAccessCheck implements AccessInterface, AccessCheckInterface { /** * Implements AccessCheckInterface::applies(). @@ -49,10 +50,12 @@ public function applies(Route $route) { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Request $request, AccountInterface $account) { + $access = new AccessCheckResult(FALSE); + $method = $request->getMethod(); $cookie = $request->attributes->get('_authentication_provider') == 'cookie'; @@ -66,25 +69,13 @@ public function access(Request $request, AccountInterface $account) { ) { $csrf_token = $request->headers->get('X-CSRF-Token'); if (!drupal_valid_token($csrf_token, 'rest')) { - return static::KILL; + $access->value = static::KILL; + return $access; } } // Let other access checkers decide if the request is legit. - return static::ALLOW; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $access->value = static::ALLOW; + return $access; } } diff --git a/core/modules/shortcut/src/Access/ShortcutSetSwitchAccessCheck.php b/core/modules/shortcut/src/Access/ShortcutSetSwitchAccessCheck.php index 1d6656a..5999f8b 100644 --- a/core/modules/shortcut/src/Access/ShortcutSetSwitchAccessCheck.php +++ b/core/modules/shortcut/src/Access/ShortcutSetSwitchAccessCheck.php @@ -7,14 +7,15 @@ namespace Drupal\shortcut\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Drupal\user\UserInterface; /** * Checks access to switch a user's shortcut set. */ -class ShortcutSetSwitchAccessCheck extends AccessBase { +class ShortcutSetSwitchAccessCheck implements AccessInterface { /** * Checks access. @@ -24,45 +25,48 @@ class ShortcutSetSwitchAccessCheck extends AccessBase { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(UserInterface $user, AccountInterface $account) { + $access = new AccessCheckResult(TRUE); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + if ($account->hasPermission('administer shortcuts')) { // Administrators can switch anyone's shortcut set. - return static::ALLOW; + $access->value = static::ALLOW; + return $access; } if (!$account->hasPermission('access shortcuts')) { // The user has no permission to use shortcuts. - return static::DENY; + $access->value = static::DENY; + return $access; } if (!$account->hasPermission('switch shortcut sets')) { // The user has no permission to switch anyone's shortcut set. - return static::DENY; + $access->value = static::DENY; + return $access; } + // Any of the above results depended solely on the permissions associated + // with the role; from here on, it also depends on the user. Hence vary per + // role and invalidate when the shortcut set owner user is modified. + $access->cacheability + ->addContexts(array('cache_context.user')) + ->setTags($user->getCacheTag()); + if ($user->id() == $account->id()) { // Users with the 'switch shortcut sets' permission can switch their own // shortcuts sets. - return static::ALLOW; + $access->value = static::ALLOW; + return $access; } - return static::DENY; - } - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user'); + $access->value = static::DENY; + return $access; } } diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php index ddc302a..cbb852d 100644 --- a/core/modules/system/entity.api.php +++ b/core/modules/system/entity.api.php @@ -6,6 +6,8 @@ */ use Drupal\Component\Utility\String; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Access\AccessInterface use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinition; use Drupal\Core\Render\Element; @@ -27,14 +29,17 @@ * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + $access = new AccessCheckResult(TRUE); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')) + $access->value = NULL; + return $access; } /** @@ -49,14 +54,17 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + $access = new AccessCheckResult(TRUE); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')) + $access->value = NULL; + return $access; } /** @@ -67,14 +75,17 @@ function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $o * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + $access = new AccessCheckResult(TRUE); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')) + $access->value = NULL; + return $access; } /** @@ -85,14 +96,17 @@ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $accoun * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + $access = new AccessCheckResult(TRUE); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')) + $access->value = NULL; + return $access; } /** @@ -917,13 +931,16 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent * (optional) The entity field object on which the operation is to be * performed. * - * @return bool|null - * TRUE if access should be allowed, FALSE if access should be denied and NULL - * if the implementation has no opinion. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) { if ($field_definition->getName() == 'field_of_interest' && $operation == 'edit') { - return user_access('update field of interest', $account); + $access = new AccessCheckResult(TRUE); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + $access->value = user_access('update field of interest', $account); + return $access; } } @@ -933,10 +950,10 @@ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinition * Use this hook to override access grants from another module. Note that the * original default access flag is masked under the ':default' key. * - * @param array $grants + * @param \Drupal\Core\Access\AccessCheckResult[] $grants * An array of grants gathered by hook_entity_field_access(). The array is * keyed by the module that defines the field's access control; the values are - * grant responses for each module (Boolean or NULL). + * grant responses for each module (\Drupal\Core\Access\AccessCheckResult). * @param array $context * Context array on the performed operation with the following keys: * - operation: The operation to be performed (string). @@ -950,13 +967,14 @@ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinition function hook_entity_field_access_alter(array &$grants, array $context) { /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */ $field_definition = $context['field_definition']; - if ($field_definition->getName() == 'field_of_interest' && $grants['node'] === FALSE) { + if ($field_definition->getName() == 'field_of_interest' && $grants['node']->value === AccessInterface::KILL) { // Override node module's restriction to no opinion. We don't want to // provide our own access hook, we only want to take out node module's part // in the access handling of this field. We also don't want to switch node - // module's grant to TRUE, because the grants of other modules should still - // decide on their own if this field is accessible or not. - $grants['node'] = NULL; + // module's grant to AccessInterface::ALLOW, because the grants of other + // modules should still decide on their own if this field is accessible or + // not. + $grants['node']->value = AccessInterface::DENY; } } diff --git a/core/modules/system/src/Access/CronAccessCheck.php b/core/modules/system/src/Access/CronAccessCheck.php index 423ee45..6b76552 100644 --- a/core/modules/system/src/Access/CronAccessCheck.php +++ b/core/modules/system/src/Access/CronAccessCheck.php @@ -7,12 +7,13 @@ namespace Drupal\system\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; /** * Access check for cron routes. */ -class CronAccessCheck extends AccessBase { +class CronAccessCheck implements AccessInterface { /** * Checks access. @@ -20,36 +21,24 @@ class CronAccessCheck extends AccessBase { * @param string $key * The cron key. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access($key) { + $result = new AccessCheckResult(FALSE); + if ($key != \Drupal::state()->get('system.cron_key')) { watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE); - return static::KILL; + $result->value = static::KILL; + return $result; } elseif (\Drupal::state()->get('system.maintenance_mode')) { watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE); - return static::KILL; + $result->value = static::KILL; + return $result; } - return static::ALLOW; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable per user if we could associate the cache tag - * of the entity being translated. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $result->value = static::ALLOW; + return $result; } } diff --git a/core/modules/system/tests/modules/router_test_directory/src/Access/DefinedTestAccessCheck.php b/core/modules/system/tests/modules/router_test_directory/src/Access/DefinedTestAccessCheck.php index dd66276..d16eff9 100644 --- a/core/modules/system/tests/modules/router_test_directory/src/Access/DefinedTestAccessCheck.php +++ b/core/modules/system/tests/modules/router_test_directory/src/Access/DefinedTestAccessCheck.php @@ -7,13 +7,14 @@ namespace Drupal\router_test\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Symfony\Component\Routing\Route; /** * Defines an access checker similar to DefaultAccessCheck */ -class DefinedTestAccessCheck extends AccessBase { +class DefinedTestAccessCheck implements AccessInterface { /** * Checks access. @@ -21,35 +22,22 @@ class DefinedTestAccessCheck extends AccessBase { * @param \Symfony\Component\Routing\Route $route * The route to check against. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route) { + $access = new AccessCheckResult(TRUE); + if ($route->getRequirement('_test_access') === 'TRUE') { - return static::ALLOW; + $access->value = static::ALLOW; } elseif ($route->getRequirement('_test_access') === 'FALSE') { - return static::KILL; + $access->value = static::KILL; } else { - return static::DENY; + $access->value = static::DENY; } - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - * - * This is globally cacheable. - */ - public function getCacheContexts() { - return array(); + return $access; } } diff --git a/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php b/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php index 73be18c..ae41ceb 100644 --- a/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php +++ b/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php @@ -7,12 +7,13 @@ namespace Drupal\router_test\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; /** * Access check for test routes. */ -class TestAccessCheck extends AccessBase { +class TestAccessCheck implements AccessInterface { /** * Checks access. @@ -21,25 +22,11 @@ class TestAccessCheck extends AccessBase { * A \Drupal\Core\Access\AccessInterface constant value. */ public function access() { + $access = new AccessCheckResult(TRUE); // No opinion, so other access checks should decide if access should be // allowed or not. - return static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - * - * This is globally cacheable. - */ - public function getCacheContexts() { - return array(); + $access->value = static::DENY; + return $access; } } diff --git a/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php b/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php index 68cdebb..8877317 100644 --- a/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php +++ b/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php @@ -7,14 +7,15 @@ namespace Drupal\tracker\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Drupal\user\UserInterface; /** * Access check for user tracker routes. */ -class ViewOwnTrackerAccessCheck extends AccessBase { +class ViewOwnTrackerAccessCheck implements AccessInterface { /** * Checks access. @@ -24,25 +25,15 @@ class ViewOwnTrackerAccessCheck extends AccessBase { * @param \Drupal\user\UserInterface $user * The user whose tracker page is being accessed. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(AccountInterface $account, UserInterface $user) { - return ($user && $account->isAuthenticated() && ($user->id() == $account->id())) ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user'); + $access = new AccessCheckResult(TRUE); + $access->value = ($user && $account->isAuthenticated() && ($user->id() == $account->id())) ? static::ALLOW : static::DENY; + // Cacheable per user. + $access->cacheability->setContexts(array('cache_context.user')); + return $access; } } diff --git a/core/modules/update/src/Access/UpdateManagerAccessCheck.php b/core/modules/update/src/Access/UpdateManagerAccessCheck.php index 9967bd3..9ca6fc2 100644 --- a/core/modules/update/src/Access/UpdateManagerAccessCheck.php +++ b/core/modules/update/src/Access/UpdateManagerAccessCheck.php @@ -7,13 +7,14 @@ namespace Drupal\update\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Site\Settings; /** * Determines whether allow authorized operations is set. */ -class UpdateManagerAccessCheck extends AccessBase { +class UpdateManagerAccessCheck implements AccessInterface { /** * Settings Service. @@ -39,24 +40,9 @@ public function __construct(Settings $settings) { * A \Drupal\Core\Access\AccessInterface constant value. */ public function access() { - return $this->settings->get('allow_authorize_operations', TRUE) ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - * - * @todo This would be cacheable globally if we could associate the cache tag - * of the Settings. - */ - public function isCacheable() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array(); + $access = new AccessCheckResult(FALSE); + $access->value = $this->settings->get('allow_authorize_operations', TRUE) ? static::ALLOW : static::DENY; + return $access; } } diff --git a/core/modules/user/src/Access/LoginStatusCheck.php b/core/modules/user/src/Access/LoginStatusCheck.php index 577b846..f4b2a41 100644 --- a/core/modules/user/src/Access/LoginStatusCheck.php +++ b/core/modules/user/src/Access/LoginStatusCheck.php @@ -7,14 +7,15 @@ namespace Drupal\user\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\HttpFoundation\Request; /** * Determines access to routes based on login status of current user. */ -class LoginStatusCheck extends AccessBase { +class LoginStatusCheck implements AccessInterface { /** * Checks access. @@ -24,25 +25,15 @@ class LoginStatusCheck extends AccessBase { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Request $request, AccountInterface $account) { - return ($request->attributes->get('_menu_admin') || $account->isAuthenticated()) ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user.roles'); + $access = new AccessCheckResult(TRUE); + $access->value = ($request->attributes->get('_menu_admin') || $account->isAuthenticated()) ? static::ALLOW : static::DENY; + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + return $access; } } diff --git a/core/modules/user/src/Access/PermissionAccessCheck.php b/core/modules/user/src/Access/PermissionAccessCheck.php index 17f88b6..d3cdf2e 100644 --- a/core/modules/user/src/Access/PermissionAccessCheck.php +++ b/core/modules/user/src/Access/PermissionAccessCheck.php @@ -7,14 +7,15 @@ namespace Drupal\user\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; /** * Determines access to routes based on permissions defined via hook_permission(). */ -class PermissionAccessCheck extends AccessBase { +class PermissionAccessCheck implements AccessInterface { /** * Checks access. @@ -24,26 +25,16 @@ class PermissionAccessCheck extends AccessBase { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, AccountInterface $account) { + $access = new AccessCheckResult(TRUE); $permission = $route->getRequirement('_permission'); - return $account->hasPermission($permission) ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user.roles'); + $access->value = $account->hasPermission($permission) ? static::ALLOW : static::DENY; + // Cacheable per role. + $access->cacheability->setContexts('cache_context.user.roles'); + return $access; } } diff --git a/core/modules/user/src/Access/RegisterAccessCheck.php b/core/modules/user/src/Access/RegisterAccessCheck.php index f199a62..c3cd08f 100644 --- a/core/modules/user/src/Access/RegisterAccessCheck.php +++ b/core/modules/user/src/Access/RegisterAccessCheck.php @@ -7,14 +7,15 @@ namespace Drupal\user\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\HttpFoundation\Request; /** * Access check for user registration routes. */ -class RegisterAccessCheck extends AccessBase { +class RegisterAccessCheck implements AccessInterface { /** * Checks access. @@ -24,25 +25,15 @@ class RegisterAccessCheck extends AccessBase { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Request $request, AccountInterface $account) { - return ($request->attributes->get('_menu_admin') || $account->isAnonymous()) && (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user.roles'); + $access = new AccessCheckResult(TRUE); + $access->value = ($request->attributes->get('_menu_admin') || $account->isAnonymous()) && (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) ? static::ALLOW : static::DENY; + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + return $access; } } diff --git a/core/modules/user/src/Access/RoleAccessCheck.php b/core/modules/user/src/Access/RoleAccessCheck.php index 25afe82..9c729ab 100644 --- a/core/modules/user/src/Access/RoleAccessCheck.php +++ b/core/modules/user/src/Access/RoleAccessCheck.php @@ -7,7 +7,8 @@ namespace Drupal\user\Access; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; @@ -18,7 +19,7 @@ * single role, users with that role with have access. If you specify multiple * ones you can conjunct them with AND by using a "+" and with OR by using ",". */ -class RoleAccessCheck extends AccessBase { +class RoleAccessCheck implements AccessInterface { /** * Checks access. @@ -28,10 +29,14 @@ class RoleAccessCheck extends AccessBase { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(Route $route, AccountInterface $account) { + $access = new AccessCheckResult(TRUE); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + // Requirements just allow strings, so this might be a comma separated list. $rid_string = $route->getRequirement('_role'); @@ -39,33 +44,22 @@ public function access(Route $route, AccountInterface $account) { if (count($explode_and) > 1) { $diff = array_diff($explode_and, $account->getRoles()); if (empty($diff)) { - return static::ALLOW; + $access->value = static::ALLOW; + return $access; } } else { $explode_or = array_filter(array_map('trim', explode(',', $rid_string))); $intersection = array_intersect($explode_or, $account->getRoles()); if (!empty($intersection)) { - return static::ALLOW; + $access->value = static::ALLOW; + return $access; } } // If there is no allowed role, return NULL to give other checks a chance. - return static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user.roles'); + $access->value = static::DENY; + return $access; } } diff --git a/core/modules/user/src/Plugin/Search/UserSearch.php b/core/modules/user/src/Plugin/Search/UserSearch.php index ce456b7..cd2d560 100644 --- a/core/modules/user/src/Plugin/Search/UserSearch.php +++ b/core/modules/user/src/Plugin/Search/UserSearch.php @@ -7,6 +7,7 @@ namespace Drupal\user\Plugin\Search; +use Drupal\Core\Access\AccessCheckResult; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -98,7 +99,11 @@ public function __construct(Connection $database, EntityManagerInterface $entity * {@inheritdoc} */ public function access($operation = 'view', AccountInterface $account = NULL) { - return !empty($account) && $account->hasPermission('access user profiles'); + $access = new AccessCheckResult(TRUE); + $access->value = !empty($account) && $account->hasPermission('access user profiles'); + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + return $access; } /** diff --git a/core/modules/views/src/ViewsAccessCheck.php b/core/modules/views/src/ViewsAccessCheck.php index afc32c9..62cb538 100644 --- a/core/modules/views/src/ViewsAccessCheck.php +++ b/core/modules/views/src/ViewsAccessCheck.php @@ -8,7 +8,8 @@ namespace Drupal\views; use Drupal\Core\Access\AccessCheckInterface; -use Drupal\Core\Routing\Access\AccessBase; +use Drupal\Core\Access\AccessCheckResult; +use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; @@ -17,7 +18,7 @@ * * @todo We could leverage the permission one as well? */ -class ViewsAccessCheck extends AccessBase implements AccessCheckInterface { +class ViewsAccessCheck implements AccessInterface, AccessCheckInterface { /** * {@inheritdoc} @@ -32,25 +33,15 @@ public function applies(Route $route) { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessCheckResult + * The access check result, with cacheability metadata. */ public function access(AccountInterface $account) { - return $account->hasPermission('access all views') ? static::ALLOW : static::DENY; - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - return array('cache_context.user.roles'); + $access = new AccessCheckResult(TRUE); + $access->value = $account->hasPermission('access all views') ? static::ALLOW : static::DENY; + // Cacheable per role. + $access->cacheability->setContexts(array('cache_context.user.roles')); + return $access; } }