PageRenderTime 62ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/src_8/core/lib/Drupal/Core/Access/AccessResult.php

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