annotate core/lib/Drupal/Core/Access/AccessResult.php @ 0:4c8ae668cc8c

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