annotate core/lib/Drupal/Core/Access/AccessResult.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Access;
Chris@0 4
Chris@0 5 use Drupal\Core\Cache\Cache;
Chris@0 6 use Drupal\Core\Cache\CacheableDependencyInterface;
Chris@0 7 use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
Chris@0 8 use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
Chris@0 9 use Drupal\Core\Config\ConfigBase;
Chris@0 10 use Drupal\Core\Entity\EntityInterface;
Chris@0 11 use Drupal\Core\Session\AccountInterface;
Chris@0 12
Chris@0 13 /**
Chris@0 14 * Value object for passing an access result with cacheability metadata.
Chris@0 15 *
Chris@0 16 * The access result itself — excluding the cacheability metadata — is
Chris@0 17 * immutable. There are subclasses for each of the three possible access results
Chris@0 18 * themselves:
Chris@0 19 *
Chris@0 20 * @see \Drupal\Core\Access\AccessResultAllowed
Chris@0 21 * @see \Drupal\Core\Access\AccessResultForbidden
Chris@0 22 * @see \Drupal\Core\Access\AccessResultNeutral
Chris@0 23 *
Chris@0 24 * When using ::orIf() and ::andIf(), cacheability metadata will be merged
Chris@0 25 * accordingly as well.
Chris@0 26 */
Chris@0 27 abstract class AccessResult implements AccessResultInterface, RefinableCacheableDependencyInterface {
Chris@0 28
Chris@0 29 use RefinableCacheableDependencyTrait;
Chris@0 30
Chris@0 31 /**
Chris@0 32 * Creates an AccessResultInterface object with isNeutral() === TRUE.
Chris@0 33 *
Chris@0 34 * @param string|null $reason
Chris@0 35 * (optional) The reason why access is forbidden. Intended for developers,
Chris@0 36 * hence not translatable.
Chris@0 37 *
Chris@0 38 * @return \Drupal\Core\Access\AccessResultNeutral
Chris@0 39 * isNeutral() will be TRUE.
Chris@0 40 */
Chris@0 41 public static function neutral($reason = NULL) {
Chris@14 42 assert(is_string($reason) || is_null($reason));
Chris@0 43 return new AccessResultNeutral($reason);
Chris@0 44 }
Chris@0 45
Chris@0 46 /**
Chris@0 47 * Creates an AccessResultInterface object with isAllowed() === TRUE.
Chris@0 48 *
Chris@0 49 * @return \Drupal\Core\Access\AccessResultAllowed
Chris@0 50 * isAllowed() will be TRUE.
Chris@0 51 */
Chris@0 52 public static function allowed() {
Chris@0 53 return new AccessResultAllowed();
Chris@0 54 }
Chris@0 55
Chris@0 56 /**
Chris@0 57 * Creates an AccessResultInterface object with isForbidden() === TRUE.
Chris@0 58 *
Chris@0 59 * @param string|null $reason
Chris@0 60 * (optional) The reason why access is forbidden. Intended for developers,
Chris@0 61 * hence not translatable.
Chris@0 62 *
Chris@0 63 * @return \Drupal\Core\Access\AccessResultForbidden
Chris@0 64 * isForbidden() will be TRUE.
Chris@0 65 */
Chris@0 66 public static function forbidden($reason = NULL) {
Chris@14 67 assert(is_string($reason) || is_null($reason));
Chris@0 68 return new AccessResultForbidden($reason);
Chris@0 69 }
Chris@0 70
Chris@0 71 /**
Chris@0 72 * Creates an allowed or neutral access result.
Chris@0 73 *
Chris@0 74 * @param bool $condition
Chris@0 75 * The condition to evaluate.
Chris@0 76 *
Chris@0 77 * @return \Drupal\Core\Access\AccessResult
Chris@0 78 * If $condition is TRUE, isAllowed() will be TRUE, otherwise isNeutral()
Chris@0 79 * will be TRUE.
Chris@0 80 */
Chris@0 81 public static function allowedIf($condition) {
Chris@0 82 return $condition ? static::allowed() : static::neutral();
Chris@0 83 }
Chris@0 84
Chris@0 85 /**
Chris@0 86 * Creates a forbidden or neutral access result.
Chris@0 87 *
Chris@0 88 * @param bool $condition
Chris@0 89 * The condition to evaluate.
Chris@14 90 * @param string|null $reason
Chris@14 91 * (optional) The reason why access is forbidden. Intended for developers,
Chris@14 92 * hence not translatable
Chris@0 93 *
Chris@0 94 * @return \Drupal\Core\Access\AccessResult
Chris@0 95 * If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral()
Chris@0 96 * will be TRUE.
Chris@0 97 */
Chris@14 98 public static function forbiddenIf($condition, $reason = NULL) {
Chris@14 99 return $condition ? static::forbidden($reason) : static::neutral();
Chris@0 100 }
Chris@0 101
Chris@0 102 /**
Chris@0 103 * Creates an allowed access result if the permission is present, neutral otherwise.
Chris@0 104 *
Chris@0 105 * Checks the permission and adds a 'user.permissions' cache context.
Chris@0 106 *
Chris@0 107 * @param \Drupal\Core\Session\AccountInterface $account
Chris@0 108 * The account for which to check a permission.
Chris@0 109 * @param string $permission
Chris@0 110 * The permission to check for.
Chris@0 111 *
Chris@0 112 * @return \Drupal\Core\Access\AccessResult
Chris@0 113 * If the account has the permission, isAllowed() will be TRUE, otherwise
Chris@0 114 * isNeutral() will be TRUE.
Chris@0 115 */
Chris@0 116 public static function allowedIfHasPermission(AccountInterface $account, $permission) {
Chris@0 117 $access_result = static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']);
Chris@0 118
Chris@0 119 if ($access_result instanceof AccessResultReasonInterface) {
Chris@0 120 $access_result->setReason("The '$permission' permission is required.");
Chris@0 121 }
Chris@0 122 return $access_result;
Chris@0 123 }
Chris@0 124
Chris@0 125 /**
Chris@0 126 * Creates an allowed access result if the permissions are present, neutral otherwise.
Chris@0 127 *
Chris@0 128 * Checks the permission and adds a 'user.permissions' cache contexts.
Chris@0 129 *
Chris@0 130 * @param \Drupal\Core\Session\AccountInterface $account
Chris@0 131 * The account for which to check permissions.
Chris@0 132 * @param array $permissions
Chris@0 133 * The permissions to check.
Chris@0 134 * @param string $conjunction
Chris@0 135 * (optional) 'AND' if all permissions are required, 'OR' in case just one.
Chris@0 136 * Defaults to 'AND'
Chris@0 137 *
Chris@0 138 * @return \Drupal\Core\Access\AccessResult
Chris@0 139 * If the account has the permissions, isAllowed() will be TRUE, otherwise
Chris@0 140 * isNeutral() will be TRUE.
Chris@0 141 */
Chris@0 142 public static function allowedIfHasPermissions(AccountInterface $account, array $permissions, $conjunction = 'AND') {
Chris@0 143 $access = FALSE;
Chris@0 144
Chris@0 145 if ($conjunction == 'AND' && !empty($permissions)) {
Chris@0 146 $access = TRUE;
Chris@0 147 foreach ($permissions as $permission) {
Chris@0 148 if (!$permission_access = $account->hasPermission($permission)) {
Chris@0 149 $access = FALSE;
Chris@0 150 break;
Chris@0 151 }
Chris@0 152 }
Chris@0 153 }
Chris@0 154 else {
Chris@0 155 foreach ($permissions as $permission) {
Chris@0 156 if ($permission_access = $account->hasPermission($permission)) {
Chris@0 157 $access = TRUE;
Chris@0 158 break;
Chris@0 159 }
Chris@0 160 }
Chris@0 161 }
Chris@0 162
Chris@0 163 $access_result = static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']);
Chris@0 164
Chris@0 165 if ($access_result instanceof AccessResultReasonInterface) {
Chris@0 166 if (count($permissions) === 1) {
Chris@0 167 $access_result->setReason("The '$permission' permission is required.");
Chris@0 168 }
Chris@0 169 elseif (count($permissions) > 1) {
Chris@0 170 $quote = function ($s) {
Chris@0 171 return "'$s'";
Chris@0 172 };
Chris@0 173 $access_result->setReason(sprintf("The following permissions are required: %s.", implode(" $conjunction ", array_map($quote, $permissions))));
Chris@0 174 }
Chris@0 175 }
Chris@0 176
Chris@0 177 return $access_result;
Chris@0 178 }
Chris@0 179
Chris@0 180 /**
Chris@0 181 * {@inheritdoc}
Chris@0 182 *
Chris@0 183 * @see \Drupal\Core\Access\AccessResultAllowed
Chris@0 184 */
Chris@0 185 public function isAllowed() {
Chris@0 186 return FALSE;
Chris@0 187 }
Chris@0 188
Chris@0 189 /**
Chris@0 190 * {@inheritdoc}
Chris@0 191 *
Chris@0 192 * @see \Drupal\Core\Access\AccessResultForbidden
Chris@0 193 */
Chris@0 194 public function isForbidden() {
Chris@0 195 return FALSE;
Chris@0 196 }
Chris@0 197
Chris@0 198 /**
Chris@0 199 * {@inheritdoc}
Chris@0 200 *
Chris@0 201 * @see \Drupal\Core\Access\AccessResultNeutral
Chris@0 202 */
Chris@0 203 public function isNeutral() {
Chris@0 204 return FALSE;
Chris@0 205 }
Chris@0 206
Chris@0 207 /**
Chris@0 208 * {@inheritdoc}
Chris@0 209 */
Chris@0 210 public function getCacheContexts() {
Chris@0 211 return $this->cacheContexts;
Chris@0 212 }
Chris@0 213
Chris@0 214 /**
Chris@0 215 * {@inheritdoc}
Chris@0 216 */
Chris@0 217 public function getCacheTags() {
Chris@0 218 return $this->cacheTags;
Chris@0 219 }
Chris@0 220
Chris@0 221 /**
Chris@0 222 * {@inheritdoc}
Chris@0 223 */
Chris@0 224 public function getCacheMaxAge() {
Chris@0 225 return $this->cacheMaxAge;
Chris@0 226 }
Chris@0 227
Chris@0 228 /**
Chris@0 229 * Resets cache contexts (to the empty array).
Chris@0 230 *
Chris@0 231 * @return $this
Chris@0 232 */
Chris@0 233 public function resetCacheContexts() {
Chris@0 234 $this->cacheContexts = [];
Chris@0 235 return $this;
Chris@0 236 }
Chris@0 237
Chris@0 238 /**
Chris@0 239 * Resets cache tags (to the empty array).
Chris@0 240 *
Chris@0 241 * @return $this
Chris@0 242 */
Chris@0 243 public function resetCacheTags() {
Chris@0 244 $this->cacheTags = [];
Chris@0 245 return $this;
Chris@0 246 }
Chris@0 247
Chris@0 248 /**
Chris@0 249 * Sets the maximum age for which this access result may be cached.
Chris@0 250 *
Chris@0 251 * @param int $max_age
Chris@0 252 * The maximum time in seconds that this access result may be cached.
Chris@0 253 *
Chris@0 254 * @return $this
Chris@0 255 */
Chris@0 256 public function setCacheMaxAge($max_age) {
Chris@0 257 $this->cacheMaxAge = $max_age;
Chris@0 258 return $this;
Chris@0 259 }
Chris@0 260
Chris@0 261 /**
Chris@0 262 * Convenience method, adds the "user.permissions" cache context.
Chris@0 263 *
Chris@0 264 * @return $this
Chris@0 265 */
Chris@0 266 public function cachePerPermissions() {
Chris@0 267 $this->addCacheContexts(['user.permissions']);
Chris@0 268 return $this;
Chris@0 269 }
Chris@0 270
Chris@0 271 /**
Chris@0 272 * Convenience method, adds the "user" cache context.
Chris@0 273 *
Chris@0 274 * @return $this
Chris@0 275 */
Chris@0 276 public function cachePerUser() {
Chris@0 277 $this->addCacheContexts(['user']);
Chris@0 278 return $this;
Chris@0 279 }
Chris@0 280
Chris@0 281 /**
Chris@0 282 * Convenience method, adds the entity's cache tag.
Chris@0 283 *
Chris@0 284 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 285 * The entity whose cache tag to set on the access result.
Chris@0 286 *
Chris@0 287 * @return $this
Chris@0 288 *
Chris@0 289 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
Chris@0 290 * ::addCacheableDependency() instead.
Chris@0 291 */
Chris@0 292 public function cacheUntilEntityChanges(EntityInterface $entity) {
Chris@0 293 return $this->addCacheableDependency($entity);
Chris@0 294 }
Chris@0 295
Chris@0 296 /**
Chris@0 297 * Convenience method, adds the configuration object's cache tag.
Chris@0 298 *
Chris@0 299 * @param \Drupal\Core\Config\ConfigBase $configuration
Chris@0 300 * The configuration object whose cache tag to set on the access result.
Chris@0 301 *
Chris@0 302 * @return $this
Chris@0 303 *
Chris@0 304 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
Chris@0 305 * ::addCacheableDependency() instead.
Chris@0 306 */
Chris@0 307 public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
Chris@0 308 return $this->addCacheableDependency($configuration);
Chris@0 309 }
Chris@0 310
Chris@0 311 /**
Chris@0 312 * {@inheritdoc}
Chris@0 313 */
Chris@0 314 public function orIf(AccessResultInterface $other) {
Chris@0 315 $merge_other = FALSE;
Chris@0 316 // $other's cacheability metadata is merged if $merge_other gets set to TRUE
Chris@0 317 // and this happens in three cases:
Chris@0 318 // 1. $other's access result is the one that determines the combined access
Chris@0 319 // result.
Chris@0 320 // 2. This access result is not cacheable and $other's access result is the
Chris@0 321 // same. i.e. attempt to return a cacheable access result.
Chris@0 322 // 3. Neither access result is 'forbidden' and both are cacheable: inherit
Chris@0 323 // the other's cacheability metadata because it may turn into a
Chris@0 324 // 'forbidden' for another value of the cache contexts in the
Chris@0 325 // cacheability metadata. In other words: this is necessary to respect
Chris@0 326 // the contagious nature of the 'forbidden' access result.
Chris@0 327 // e.g. we have two access results A and B. Neither is forbidden. A is
Chris@0 328 // globally cacheable (no cache contexts). B is cacheable per role. If we
Chris@0 329 // don't have merging case 3, then A->orIf(B) will be globally cacheable,
Chris@0 330 // which means that even if a user of a different role logs in, the
Chris@0 331 // cached access result will be used, even though for that other role, B
Chris@0 332 // is forbidden!
Chris@0 333 if ($this->isForbidden() || $other->isForbidden()) {
Chris@0 334 $result = static::forbidden();
Chris@0 335 if (!$this->isForbidden() || ($this->getCacheMaxAge() === 0 && $other->isForbidden())) {
Chris@0 336 $merge_other = TRUE;
Chris@0 337 }
Chris@0 338
Chris@16 339 if ($this->isForbidden() && $this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
Chris@0 340 $result->setReason($this->getReason());
Chris@0 341 }
Chris@16 342 elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
Chris@0 343 $result->setReason($other->getReason());
Chris@0 344 }
Chris@0 345 }
Chris@0 346 elseif ($this->isAllowed() || $other->isAllowed()) {
Chris@0 347 $result = static::allowed();
Chris@0 348 if (!$this->isAllowed() || ($this->getCacheMaxAge() === 0 && $other->isAllowed()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
Chris@0 349 $merge_other = TRUE;
Chris@0 350 }
Chris@0 351 }
Chris@0 352 else {
Chris@0 353 $result = static::neutral();
Chris@0 354 if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
Chris@0 355 $merge_other = TRUE;
Chris@0 356 }
Chris@16 357
Chris@16 358 if ($this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
Chris@16 359 $result->setReason($this->getReason());
Chris@16 360 }
Chris@16 361 elseif ($other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
Chris@16 362 $result->setReason($other->getReason());
Chris@0 363 }
Chris@0 364 }
Chris@0 365 $result->inheritCacheability($this);
Chris@0 366 if ($merge_other) {
Chris@0 367 $result->inheritCacheability($other);
Chris@0 368 }
Chris@0 369 return $result;
Chris@0 370 }
Chris@0 371
Chris@0 372 /**
Chris@0 373 * {@inheritdoc}
Chris@0 374 */
Chris@0 375 public function andIf(AccessResultInterface $other) {
Chris@0 376 // The other access result's cacheability metadata is merged if $merge_other
Chris@0 377 // gets set to TRUE. It gets set to TRUE in one case: if the other access
Chris@0 378 // result is used.
Chris@0 379 $merge_other = FALSE;
Chris@0 380 if ($this->isForbidden() || $other->isForbidden()) {
Chris@0 381 $result = static::forbidden();
Chris@0 382 if (!$this->isForbidden()) {
Chris@0 383 if ($other instanceof AccessResultReasonInterface) {
Chris@0 384 $result->setReason($other->getReason());
Chris@0 385 }
Chris@0 386 $merge_other = TRUE;
Chris@0 387 }
Chris@0 388 else {
Chris@0 389 if ($this instanceof AccessResultReasonInterface) {
Chris@0 390 $result->setReason($this->getReason());
Chris@0 391 }
Chris@0 392 }
Chris@0 393 }
Chris@0 394 elseif ($this->isAllowed() && $other->isAllowed()) {
Chris@0 395 $result = static::allowed();
Chris@0 396 $merge_other = TRUE;
Chris@0 397 }
Chris@0 398 else {
Chris@0 399 $result = static::neutral();
Chris@0 400 if (!$this->isNeutral()) {
Chris@0 401 $merge_other = TRUE;
Chris@0 402 if ($other instanceof AccessResultReasonInterface) {
Chris@0 403 $result->setReason($other->getReason());
Chris@0 404 }
Chris@0 405 }
Chris@0 406 else {
Chris@0 407 if ($this instanceof AccessResultReasonInterface) {
Chris@0 408 $result->setReason($this->getReason());
Chris@0 409 }
Chris@0 410 }
Chris@0 411 }
Chris@0 412 $result->inheritCacheability($this);
Chris@0 413 if ($merge_other) {
Chris@0 414 $result->inheritCacheability($other);
Chris@0 415 // If this access result is not cacheable, then an AND with another access
Chris@0 416 // result must also not be cacheable, except if the other access result
Chris@0 417 // has isForbidden() === TRUE. isForbidden() access results are contagious
Chris@0 418 // in that they propagate regardless of the other value.
Chris@0 419 if ($this->getCacheMaxAge() === 0 && !$result->isForbidden()) {
Chris@0 420 $result->setCacheMaxAge(0);
Chris@0 421 }
Chris@0 422 }
Chris@0 423 return $result;
Chris@0 424 }
Chris@0 425
Chris@0 426 /**
Chris@0 427 * Inherits the cacheability of the other access result, if any.
Chris@0 428 *
Chris@17 429 * This method differs from addCacheableDependency() in how it handles
Chris@17 430 * max-age, because it is designed to inherit the cacheability of the second
Chris@17 431 * operand in the andIf() and orIf() operations. There, the situation
Chris@0 432 * "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000
Chris@0 433 * as the end result.
Chris@0 434 *
Chris@0 435 * @param \Drupal\Core\Access\AccessResultInterface $other
Chris@0 436 * The other access result, whose cacheability (if any) to inherit.
Chris@0 437 *
Chris@0 438 * @return $this
Chris@0 439 */
Chris@0 440 public function inheritCacheability(AccessResultInterface $other) {
Chris@0 441 $this->addCacheableDependency($other);
Chris@0 442 if ($other instanceof CacheableDependencyInterface) {
Chris@0 443 if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) {
Chris@0 444 $this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge()));
Chris@0 445 }
Chris@0 446 else {
Chris@0 447 $this->setCacheMaxAge($other->getCacheMaxAge());
Chris@0 448 }
Chris@0 449 }
Chris@0 450 return $this;
Chris@0 451 }
Chris@0 452
Chris@0 453 }