function AccessPolicyProcessor::processAccessPolicies

Same name and namespace in other branches
  1. 11.x core/lib/Drupal/Core/Session/AccessPolicyProcessor.php \Drupal\Core\Session\AccessPolicyProcessor::processAccessPolicies()

Processes the access policies for an account within a given scope.

Parameters

\Drupal\Core\Session\AccountInterface $account: The user account for which to calculate the permissions.

string $scope: (optional) The scope to calculate the permissions, defaults to 'drupal'.

Return value

\Drupal\Core\Session\CalculatedPermissionsInterface The access policies' permissions within the given scope.

Overrides AccessPolicyProcessorInterface::processAccessPolicies

File

core/lib/Drupal/Core/Session/AccessPolicyProcessor.php, line 40

Class

AccessPolicyProcessor
Processes access policies into permissions for an account.

Namespace

Drupal\Core\Session

Code

public function processAccessPolicies(AccountInterface $account, string $scope = AccessPolicyInterface::SCOPE_DRUPAL) : CalculatedPermissionsInterface {
  $persistent_cache_contexts = $this->getPersistentCacheContexts($scope);
  $initial_cacheability = (new CacheableMetadata())->addCacheContexts($persistent_cache_contexts);
  $cache_keys = [
    'access_policies',
    $scope,
  ];
  // Whether to switch the user account during cache storage and retrieval.
  //
  // This is necessary because permissions may be stored varying by the user
  // cache context or one of its child contexts. Because we may be calculating
  // permissions for an account other than the current user, we need to ensure
  // that the cache ID for said entry is set according to the passed in
  // account's data.
  //
  // Sadly, there is currently no way to reuse the cache context logic outside
  // of the caching layer. If we every get a system that allows us to process
  // cache contexts with a provided environmental value (such as the current
  // user), then we should update the logic below to use that instead.
  //
  // For the time being, we set the current user to the passed in account if
  // they differ, calculate the permissions and then immediately switch back.
  // It's the cleanest solution we could come up with that doesn't involve
  // copying half of the caching layer and that still allows us to use the
  // VariationCache for accounts other than the current user.
  $switch_account = FALSE;
  if ($this->currentUser
    ->id() !== $account->id()) {
    foreach ($persistent_cache_contexts as $cache_context) {
      [
        $cache_context_root,
      ] = explode('.', $cache_context, 2);
      if ($cache_context_root === 'user') {
        $switch_account = TRUE;
        $this->accountSwitcher
          ->switchTo($account);
        break;

      }
    }
  }
  // Wrap the whole cache retrieval or calculation in a try-finally so that we
  // always switch back to the original account after the return statement or
  // if an exception was thrown.
  try {
    // Retrieve the permissions from the static cache if available.
    if ($static_cache = $this->variationStatic
      ->get($cache_keys, $initial_cacheability)) {
      return $static_cache->data;
    }
    // Retrieve the permissions from the persistent cache if available.
    if ($cache = $this->variationCache
      ->get($cache_keys, $initial_cacheability)) {
      $calculated_permissions = $cache->data;
      $cacheability = CacheableMetadata::createFromObject($calculated_permissions);
      // Convert the calculated permissions into an immutable value object and
      // store it in the static cache so that we don't have to do the same
      // conversion every time we call for the calculated permissions from a
      // warm static cache.
      $calculated_permissions = new CalculatedPermissions($calculated_permissions);
      $this->variationStatic
        ->set($cache_keys, $calculated_permissions, $cacheability, $initial_cacheability);
      return $calculated_permissions;
    }
    // Otherwise build the permissions from scratch.
    // Build mode, allow all access policies to add initial data.
    $calculated_permissions = new RefinableCalculatedPermissions();
    foreach ($this->accessPolicies as $access_policy) {
      if (!$access_policy->applies($scope)) {
        continue;
      }
      $policy_permissions = $access_policy->calculatePermissions($account, $scope);
      if (!$this->validateScope($scope, $policy_permissions)) {
        throw new AccessPolicyScopeException(sprintf('The access policy "%s" returned permissions for scopes other than "%s".', get_class($access_policy), $scope));
      }
      $calculated_permissions = $calculated_permissions->merge($policy_permissions);
    }
    // Alter mode, allow all access policies to alter the complete build.
    foreach ($this->accessPolicies as $access_policy) {
      if (!$access_policy->applies($scope)) {
        continue;
      }
      $access_policy->alterPermissions($account, $scope, $calculated_permissions);
      if (!$this->validateScope($scope, $calculated_permissions)) {
        throw new AccessPolicyScopeException(sprintf('The access policy "%s" altered permissions in a scope other than "%s".', get_class($access_policy), $scope));
      }
    }
    // Apply a cache tag to easily flush the calculated permissions.
    $calculated_permissions->addCacheTags([
      'access_policies',
    ]);
    // First store the actual calculated permissions in the persistent cache,
    // along with the final cache contexts after all calculations have run. We
    // need to store the RefinableCalculatedPermissions in the persistent
    // cache, so we can still get the final cacheability from it for when we
    // run into a persistent cache hit but not a static one. At that point, if
    // we had stored a CalculatedPermissions object, we would no longer be
    // able to ask for its cache contexts.
    $cacheability = CacheableMetadata::createFromObject($calculated_permissions);
    $this->variationCache
      ->set($cache_keys, $calculated_permissions, $cacheability, $initial_cacheability);
    // Then convert the calculated permissions to an immutable value object
    // and store it in the static cache so that we don't have to do the same
    // conversion every time we call for the calculated permissions from a
    // warm static cache.
    $calculated_permissions = new CalculatedPermissions($calculated_permissions);
    $this->variationStatic
      ->set($cache_keys, $calculated_permissions, $cacheability, $initial_cacheability);
    // Return the permissions as an immutable value object.
    return $calculated_permissions;
  } finally {
    if ($switch_account) {
      $this->accountSwitcher
        ->switchBack();
    }
  }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.