60a17a0fb68b7564a15a85f107a9173c5b275681
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Access / AccessResultTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Access\AccessResultTest.
6  */
7
8 namespace Drupal\Tests\Core\Access;
9
10 use Drupal\Core\Access\AccessResult;
11 use Drupal\Core\Access\AccessResultInterface;
12 use Drupal\Core\Access\AccessResultNeutral;
13 use Drupal\Core\Access\AccessResultReasonInterface;
14 use Drupal\Core\Cache\Cache;
15 use Drupal\Core\Cache\CacheableDependencyInterface;
16 use Drupal\Core\DependencyInjection\ContainerBuilder;
17 use Drupal\Tests\UnitTestCase;
18
19 /**
20  * @coversDefaultClass \Drupal\Core\Access\AccessResult
21  * @group Access
22  */
23 class AccessResultTest extends UnitTestCase {
24
25   /**
26    * The cache contexts manager.
27    *
28    * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
29    */
30   protected $cacheContextsManager;
31
32   /**
33    * {@inheritdoc}
34    */
35   protected function setUp() {
36     parent::setUp();
37
38     $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
39       ->disableOriginalConstructor()
40       ->getMock();
41
42     $this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
43     $container = new ContainerBuilder();
44     $container->set('cache_contexts_manager', $this->cacheContextsManager);
45     \Drupal::setContainer($container);
46   }
47
48   protected function assertDefaultCacheability(AccessResult $access) {
49     $this->assertSame([], $access->getCacheContexts());
50     $this->assertSame([], $access->getCacheTags());
51     $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
52   }
53
54   /**
55    * Tests the construction of an AccessResult object.
56    *
57    * @covers ::neutral
58    */
59   public function testConstruction() {
60     $verify = function (AccessResult $access) {
61       $this->assertFalse($access->isAllowed());
62       $this->assertFalse($access->isForbidden());
63       $this->assertTrue($access->isNeutral());
64       $this->assertDefaultCacheability($access);
65     };
66
67     // Verify the object when using the constructor.
68     $a = new AccessResultNeutral();
69     $verify($a);
70
71     // Verify the object when using the ::create() convenience method.
72     $b = AccessResult::neutral();
73     $verify($b);
74
75     $this->assertEquals($a, $b);
76   }
77
78   /**
79    * @covers ::allowed
80    * @covers ::isAllowed
81    * @covers ::isForbidden
82    * @covers ::isNeutral
83    */
84   public function testAccessAllowed() {
85     $verify = function (AccessResult $access) {
86       $this->assertTrue($access->isAllowed());
87       $this->assertFalse($access->isForbidden());
88       $this->assertFalse($access->isNeutral());
89       $this->assertDefaultCacheability($access);
90     };
91
92     // Verify the object when using the ::allowed() convenience static method.
93     $b = AccessResult::allowed();
94     $verify($b);
95   }
96
97   /**
98    * @covers ::forbidden
99    * @covers ::isAllowed
100    * @covers ::isForbidden
101    * @covers ::isNeutral
102    */
103   public function testAccessForbidden() {
104     $verify = function (AccessResult $access) {
105       $this->assertFalse($access->isAllowed());
106       $this->assertTrue($access->isForbidden());
107       $this->assertFalse($access->isNeutral());
108       $this->assertDefaultCacheability($access);
109     };
110
111     // Verify the object when using the ::forbidden() convenience static method.
112     $b = AccessResult::forbidden();
113     $verify($b);
114   }
115
116   /**
117    * @covers ::forbidden
118    */
119   public function testAccessForbiddenReason() {
120     $verify = function (AccessResult $access, $reason) {
121       $this->assertInstanceOf(AccessResultReasonInterface::class, $access);
122       $this->assertSame($reason, $access->getReason());
123     };
124
125     $b = AccessResult::forbidden();
126     $verify($b, NULL);
127
128     $reason = $this->getRandomGenerator()->string();
129     $b = AccessResult::forbidden($reason);
130     $verify($b, $reason);
131
132     $b = AccessResult::forbiddenIf(TRUE, $reason);
133     $verify($b, $reason);
134   }
135
136   /**
137    * @covers ::allowedIf
138    * @covers ::isAllowed
139    * @covers ::isForbidden
140    * @covers ::isNeutral
141    */
142   public function testAccessConditionallyAllowed() {
143     $verify = function (AccessResult $access, $allowed) {
144       $this->assertSame($allowed, $access->isAllowed());
145       $this->assertFalse($access->isForbidden());
146       $this->assertSame(!$allowed, $access->isNeutral());
147       $this->assertDefaultCacheability($access);
148     };
149
150     $b1 = AccessResult::allowedIf(TRUE);
151     $verify($b1, TRUE);
152     $b2 = AccessResult::allowedIf(FALSE);
153     $verify($b2, FALSE);
154   }
155
156   /**
157    * @covers ::forbiddenIf
158    * @covers ::isAllowed
159    * @covers ::isForbidden
160    * @covers ::isNeutral
161    */
162   public function testAccessConditionallyForbidden() {
163     $verify = function (AccessResult $access, $forbidden) {
164       $this->assertFalse($access->isAllowed());
165       $this->assertSame($forbidden, $access->isForbidden());
166       $this->assertSame(!$forbidden, $access->isNeutral());
167       $this->assertDefaultCacheability($access);
168     };
169
170     $b1 = AccessResult::forbiddenIf(TRUE);
171     $verify($b1, TRUE);
172     $b2 = AccessResult::forbiddenIf(FALSE);
173     $verify($b2, FALSE);
174   }
175
176   /**
177    * @covers ::andIf
178    */
179   public function testAndIf() {
180     $neutral = AccessResult::neutral('neutral message');
181     $allowed = AccessResult::allowed();
182     $forbidden = AccessResult::forbidden('forbidden message');
183     $unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
184     $unused_access_result_due_to_lazy_evaluation->expects($this->never())
185       ->method($this->anything());
186
187     // ALLOWED && ALLOWED === ALLOWED.
188     $access = $allowed->andIf($allowed);
189     $this->assertTrue($access->isAllowed());
190     $this->assertFalse($access->isForbidden());
191     $this->assertFalse($access->isNeutral());
192     $this->assertDefaultCacheability($access);
193
194     // ALLOWED && NEUTRAL === NEUTRAL.
195     $access = $allowed->andIf($neutral);
196     $this->assertFalse($access->isAllowed());
197     $this->assertFalse($access->isForbidden());
198     $this->assertTrue($access->isNeutral());
199     $this->assertEquals('neutral message', $access->getReason());
200     $this->assertDefaultCacheability($access);
201
202     // ALLOWED && FORBIDDEN === FORBIDDEN.
203     $access = $allowed->andIf($forbidden);
204     $this->assertFalse($access->isAllowed());
205     $this->assertTrue($access->isForbidden());
206     $this->assertFalse($access->isNeutral());
207     $this->assertEquals('forbidden message', $access->getReason());
208     $this->assertDefaultCacheability($access);
209
210     // NEUTRAL && ALLOW == NEUTRAL
211     $access = $neutral->andIf($allowed);
212     $this->assertFalse($access->isAllowed());
213     $this->assertFalse($access->isForbidden());
214     $this->assertTrue($access->isNeutral());
215     $this->assertEquals('neutral message', $access->getReason());
216     $this->assertDefaultCacheability($access);
217
218     // NEUTRAL && NEUTRAL === NEUTRAL.
219     $access = $neutral->andIf($neutral);
220     $this->assertFalse($access->isAllowed());
221     $this->assertFalse($access->isForbidden());
222     $this->assertTrue($access->isNeutral());
223     $this->assertEquals('neutral message', $access->getReason());
224     $this->assertDefaultCacheability($access);
225
226     // NEUTRAL && FORBIDDEN === FORBIDDEN.
227     $access = $neutral->andIf($forbidden);
228     $this->assertFalse($access->isAllowed());
229     $this->assertTrue($access->isForbidden());
230     $this->assertFalse($access->isNeutral());
231     $this->assertEquals('forbidden message', $access->getReason());
232     $this->assertDefaultCacheability($access);
233
234     // FORBIDDEN && ALLOWED = FORBIDDEN
235     $access = $forbidden->andif($allowed);
236     $this->assertFalse($access->isAllowed());
237     $this->assertTrue($access->isForbidden());
238     $this->assertFalse($access->isNeutral());
239     $this->assertEquals('forbidden message', $access->getReason());
240     $this->assertDefaultCacheability($access);
241
242     // FORBIDDEN && NEUTRAL = FORBIDDEN
243     $access = $forbidden->andif($neutral);
244     $this->assertFalse($access->isAllowed());
245     $this->assertTrue($access->isForbidden());
246     $this->assertFalse($access->isNeutral());
247     $this->assertEquals('forbidden message', $access->getReason());
248     $this->assertDefaultCacheability($access);
249
250     // FORBIDDEN && FORBIDDEN = FORBIDDEN
251     $access = $forbidden->andif($forbidden);
252     $this->assertFalse($access->isAllowed());
253     $this->assertTrue($access->isForbidden());
254     $this->assertFalse($access->isNeutral());
255     $this->assertEquals('forbidden message', $access->getReason());
256     $this->assertDefaultCacheability($access);
257
258     // FORBIDDEN && * === FORBIDDEN: lazy evaluation verification.
259     $access = $forbidden->andIf($unused_access_result_due_to_lazy_evaluation);
260     $this->assertFalse($access->isAllowed());
261     $this->assertTrue($access->isForbidden());
262     $this->assertFalse($access->isNeutral());
263     $this->assertEquals('forbidden message', $access->getReason());
264     $this->assertDefaultCacheability($access);
265   }
266
267   /**
268    * @covers ::orIf
269    */
270   public function testOrIf() {
271     $neutral = AccessResult::neutral('neutral message');
272     $allowed = AccessResult::allowed();
273     $forbidden = AccessResult::forbidden('forbidden message');
274     $unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
275     $unused_access_result_due_to_lazy_evaluation->expects($this->never())
276       ->method($this->anything());
277
278     // ALLOWED || ALLOWED === ALLOWED.
279     $access = $allowed->orIf($allowed);
280     $this->assertTrue($access->isAllowed());
281     $this->assertFalse($access->isForbidden());
282     $this->assertFalse($access->isNeutral());
283     $this->assertDefaultCacheability($access);
284
285     // ALLOWED || NEUTRAL === ALLOWED.
286     $access = $allowed->orIf($neutral);
287     $this->assertTrue($access->isAllowed());
288     $this->assertFalse($access->isForbidden());
289     $this->assertFalse($access->isNeutral());
290     $this->assertDefaultCacheability($access);
291
292     // ALLOWED || FORBIDDEN === FORBIDDEN.
293     $access = $allowed->orIf($forbidden);
294     $this->assertFalse($access->isAllowed());
295     $this->assertTrue($access->isForbidden());
296     $this->assertFalse($access->isNeutral());
297     $this->assertEquals('forbidden message', $access->getReason());
298     $this->assertDefaultCacheability($access);
299
300     // NEUTRAL || NEUTRAL === NEUTRAL.
301     $access = $neutral->orIf($neutral);
302     $this->assertFalse($access->isAllowed());
303     $this->assertFalse($access->isForbidden());
304     $this->assertTrue($access->isNeutral());
305     $this->assertEquals('neutral message', $access->getReason());
306     $this->assertDefaultCacheability($access);
307
308     // NEUTRAL || ALLOWED === ALLOWED.
309     $access = $neutral->orIf($allowed);
310     $this->assertTrue($access->isAllowed());
311     $this->assertFalse($access->isForbidden());
312     $this->assertFalse($access->isNeutral());
313     $this->assertDefaultCacheability($access);
314
315     // NEUTRAL || FORBIDDEN === FORBIDDEN.
316     $access = $neutral->orIf($forbidden);
317     $this->assertFalse($access->isAllowed());
318     $this->assertTrue($access->isForbidden());
319     $this->assertFalse($access->isNeutral());
320     $this->assertEquals('forbidden message', $access->getReason());
321     $this->assertDefaultCacheability($access);
322
323     // FORBIDDEN || ALLOWED === FORBIDDEN.
324     $access = $forbidden->orIf($allowed);
325     $this->assertFalse($access->isAllowed());
326     $this->assertTrue($access->isForbidden());
327     $this->assertFalse($access->isNeutral());
328     $this->assertEquals('forbidden message', $access->getReason());
329     $this->assertDefaultCacheability($access);
330
331     // FORBIDDEN || NEUTRAL === FORBIDDEN.
332     $access = $forbidden->orIf($allowed);
333     $this->assertFalse($access->isAllowed());
334     $this->assertTrue($access->isForbidden());
335     $this->assertFalse($access->isNeutral());
336     $this->assertEquals('forbidden message', $access->getReason());
337     $this->assertDefaultCacheability($access);
338
339     // FORBIDDEN || FORBIDDEN === FORBIDDEN.
340     $access = $forbidden->orIf($allowed);
341     $this->assertFalse($access->isAllowed());
342     $this->assertTrue($access->isForbidden());
343     $this->assertFalse($access->isNeutral());
344     $this->assertEquals('forbidden message', $access->getReason());
345     $this->assertDefaultCacheability($access);
346
347     // FORBIDDEN || * === FORBIDDEN.
348     $access = $forbidden->orIf($unused_access_result_due_to_lazy_evaluation);
349     $this->assertFalse($access->isAllowed());
350     $this->assertTrue($access->isForbidden());
351     $this->assertFalse($access->isNeutral());
352     $this->assertEquals('forbidden message', $access->getReason());
353     $this->assertDefaultCacheability($access);
354   }
355
356   /**
357    * @covers ::setCacheMaxAge
358    * @covers ::getCacheMaxAge
359    */
360   public function testCacheMaxAge() {
361     $this->assertSame(Cache::PERMANENT, AccessResult::neutral()->getCacheMaxAge());
362     $this->assertSame(1337, AccessResult::neutral()->setCacheMaxAge(1337)->getCacheMaxAge());
363   }
364
365   /**
366    * @covers ::addCacheContexts
367    * @covers ::resetCacheContexts
368    * @covers ::getCacheContexts
369    * @covers ::cachePerPermissions
370    * @covers ::cachePerUser
371    * @covers ::allowedIfHasPermission
372    */
373   public function testCacheContexts() {
374     $verify = function (AccessResult $access, array $contexts) {
375       $this->assertFalse($access->isAllowed());
376       $this->assertFalse($access->isForbidden());
377       $this->assertTrue($access->isNeutral());
378       $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
379       $this->assertSame($contexts, $access->getCacheContexts());
380       $this->assertSame([], $access->getCacheTags());
381     };
382
383     $access = AccessResult::neutral()->addCacheContexts(['foo']);
384     $verify($access, ['foo']);
385     // Verify resetting works.
386     $access->resetCacheContexts();
387     $verify($access, []);
388     // Verify idempotency.
389     $access->addCacheContexts(['foo'])
390       ->addCacheContexts(['foo']);
391     $verify($access, ['foo']);
392     // Verify same values in different call order yields the same result.
393     $access->resetCacheContexts()
394       ->addCacheContexts(['foo'])
395       ->addCacheContexts(['bar']);
396     $verify($access, ['bar', 'foo']);
397     $access->resetCacheContexts()
398       ->addCacheContexts(['bar'])
399       ->addCacheContexts(['foo']);
400     $verify($access, ['bar', 'foo']);
401
402     // ::cachePerPermissions() convenience method.
403     $contexts = ['user.permissions'];
404     $a = AccessResult::neutral()->addCacheContexts($contexts);
405     $verify($a, $contexts);
406     $b = AccessResult::neutral()->cachePerPermissions();
407     $verify($b, $contexts);
408     $this->assertEquals($a, $b);
409
410     // ::cachePerUser() convenience method.
411     $contexts = ['user'];
412     $a = AccessResult::neutral()->addCacheContexts($contexts);
413     $verify($a, $contexts);
414     $b = AccessResult::neutral()->cachePerUser();
415     $verify($b, $contexts);
416     $this->assertEquals($a, $b);
417
418     // Both.
419     $contexts = ['user', 'user.permissions'];
420     $a = AccessResult::neutral()->addCacheContexts($contexts);
421     $verify($a, $contexts);
422     $b = AccessResult::neutral()->cachePerPermissions()->cachePerUser();
423     $verify($b, $contexts);
424     $c = AccessResult::neutral()->cachePerUser()->cachePerPermissions();
425     $verify($c, $contexts);
426     $this->assertEquals($a, $b);
427     $this->assertEquals($a, $c);
428
429     // ::allowIfHasPermission and ::allowedIfHasPermission convenience methods.
430     $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
431     $account->expects($this->any())
432       ->method('hasPermission')
433       ->with('may herd llamas')
434       ->will($this->returnValue(FALSE));
435     $contexts = ['user.permissions'];
436
437     // Verify the object when using the ::allowedIfHasPermission() convenience
438     // static method.
439     $b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
440     $verify($b, $contexts);
441   }
442
443   /**
444    * @covers ::addCacheTags
445    * @covers ::addCacheableDependency
446    * @covers ::getCacheTags
447    * @covers ::resetCacheTags
448    */
449   public function testCacheTags() {
450     $verify = function (AccessResult $access, array $tags, array $contexts = [], $max_age = Cache::PERMANENT) {
451       $this->assertFalse($access->isAllowed());
452       $this->assertFalse($access->isForbidden());
453       $this->assertTrue($access->isNeutral());
454       $this->assertSame($max_age, $access->getCacheMaxAge());
455       $this->assertSame($contexts, $access->getCacheContexts());
456       $this->assertSame($tags, $access->getCacheTags());
457     };
458
459     $access = AccessResult::neutral()->addCacheTags(['foo:bar']);
460     $verify($access, ['foo:bar']);
461     // Verify resetting works.
462     $access->resetCacheTags();
463     $verify($access, []);
464     // Verify idempotency.
465     $access->addCacheTags(['foo:bar'])
466       ->addCacheTags(['foo:bar']);
467     $verify($access, ['foo:bar']);
468     // Verify same values in different call order yields the same result.
469     $access->resetCacheTags()
470       ->addCacheTags(['bar:baz'])
471       ->addCacheTags(['bar:qux'])
472       ->addCacheTags(['foo:bar'])
473       ->addCacheTags(['foo:baz']);
474     $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
475     $access->resetCacheTags()
476       ->addCacheTags(['foo:bar'])
477       ->addCacheTags(['bar:qux'])
478       ->addCacheTags(['foo:baz'])
479       ->addCacheTags(['bar:baz']);
480     $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
481
482     // ::addCacheableDependency() convenience method.
483     $node = $this->getMock('\Drupal\node\NodeInterface');
484     $node->expects($this->any())
485       ->method('getCacheTags')
486       ->will($this->returnValue(['node:20011988']));
487     $node->expects($this->any())
488       ->method('getCacheMaxAge')
489       ->willReturn(600);
490     $node->expects($this->any())
491       ->method('getCacheContexts')
492       ->willReturn(['user']);
493     $tags = ['node:20011988'];
494     $a = AccessResult::neutral()->addCacheTags($tags);
495     $verify($a, $tags);
496     $b = AccessResult::neutral()->addCacheableDependency($node);
497     $verify($b, $tags, ['user'], 600);
498
499     $non_cacheable_dependency = new \stdClass();
500     $non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency);
501     $verify($non_cacheable, [], [], 0);
502   }
503
504   /**
505    * @covers ::inheritCacheability
506    */
507   public function testInheritCacheability() {
508     // andIf(); 1st has defaults, 2nd has custom tags, contexts and max-age.
509     $access = AccessResult::allowed();
510     $other = AccessResult::allowed()->setCacheMaxAge(1500)->cachePerPermissions()->addCacheTags(['node:20011988']);
511     $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
512     $this->assertSame(['user.permissions'], $access->getCacheContexts());
513     $this->assertSame(['node:20011988'], $access->getCacheTags());
514     $this->assertSame(1500, $access->getCacheMaxAge());
515
516     // andIf(); 1st has custom tags, max-age, 2nd has custom contexts and max-age.
517     $access = AccessResult::allowed()->cachePerUser()->setCacheMaxAge(43200);
518     $other = AccessResult::forbidden()->addCacheTags(['node:14031991'])->setCacheMaxAge(86400);
519     $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
520     $this->assertSame(['user'], $access->getCacheContexts());
521     $this->assertSame(['node:14031991'], $access->getCacheTags());
522     $this->assertSame(43200, $access->getCacheMaxAge());
523   }
524
525   /**
526    * Provides a list of access result pairs and operations to test.
527    *
528    * This tests the propagation of cacheability metadata. Rather than testing
529    * every single bit of cacheability metadata, which would lead to a mind-
530    * boggling number of permutations, in this test, we only consider the
531    * permutations of all pairs of the following set:
532    * - Allowed, implements CDI and is cacheable.
533    * - Allowed, implements CDI and is not cacheable.
534    * - Allowed, does not implement CDI (hence not cacheable).
535    * - Forbidden, implements CDI and is cacheable.
536    * - Forbidden, implements CDI and is not cacheable.
537    * - Forbidden, does not implement CDI (hence not cacheable).
538    * - Neutral, implements CDI and is cacheable.
539    * - Neutral, implements CDI and is not cacheable.
540    * - Neutral, does not implement CDI (hence not cacheable).
541    *
542    * (Where "CDI" is CacheableDependencyInterface.)
543    *
544    * This leads to 72 permutations (9!/(9-2)! = 9*8 = 72) per operation. There
545    * are two operations to test (AND and OR), so that leads to a grand total of
546    * 144 permutations, all of which are tested.
547    *
548    * There are two "contagious" patterns:
549    * - Any operation with a forbidden access result yields a forbidden result.
550    *   This therefore also applies to the cacheability metadata associated with
551    *   a forbidden result. This is the case for bullets 4, 5 and 6 in the set
552    *   above.
553    * - Any operation yields an access result object that is of the same class
554    *   (implementation) as the first operand. This is because operations are
555    *   invoked on the first operand. Therefore, if the first implementation
556    *   does not implement CacheableDependencyInterface, then the result won't
557    *   either. This is the case for bullets 3, 6 and 9 in the set above.
558    */
559   public function andOrCacheabilityPropagationProvider() {
560     // ct: cacheable=true, cf: cacheable=false, un: uncacheable.
561     // Note: the test cases that have a "un" access result as the first operand
562     // test UncacheableTestAccessResult, not AccessResult. However, we
563     // definitely want to verify that AccessResult's orIf() and andIf() methods
564     // work correctly when given an AccessResultInterface implementation that
565     // does not implement CacheableDependencyInterface, and we want to test the
566     // full gamut of permutations, so that's not a problem.
567     $allowed_ct = AccessResult::allowed();
568     $allowed_cf = AccessResult::allowed()->setCacheMaxAge(0);
569     $allowed_un = new UncacheableTestAccessResult('ALLOWED');
570     $forbidden_ct = AccessResult::forbidden();
571     $forbidden_cf = AccessResult::forbidden()->setCacheMaxAge(0);
572     $forbidden_un = new UncacheableTestAccessResult('FORBIDDEN');
573     $neutral_ct = AccessResult::neutral();
574     $neutral_cf = AccessResult::neutral()->setCacheMaxAge(0);
575     $neutral_un = new UncacheableTestAccessResult('NEUTRAL');
576
577     // Structure:
578     // - First column: first access result.
579     // - Second column: operator ('OR' or 'AND').
580     // - Third column: second access result.
581     // - Fourth column: whether result implements CacheableDependencyInterface
582     // - Fifth column: whether the result is cacheable (if column 4 is TRUE)
583     return [
584       // Allowed (ct) OR allowed (ct,cf,un).
585       [$allowed_ct, 'OR', $allowed_ct, TRUE, TRUE],
586       [$allowed_ct, 'OR', $allowed_cf, TRUE, TRUE],
587       [$allowed_ct, 'OR', $allowed_un, TRUE, TRUE],
588       // Allowed (cf) OR allowed (ct,cf,un).
589       [$allowed_cf, 'OR', $allowed_ct, TRUE, TRUE],
590       [$allowed_cf, 'OR', $allowed_cf, TRUE, FALSE],
591       [$allowed_cf, 'OR', $allowed_un, TRUE, FALSE],
592       // Allowed (un) OR allowed (ct,cf,un).
593       [$allowed_un, 'OR', $allowed_ct, FALSE, NULL],
594       [$allowed_un, 'OR', $allowed_cf, FALSE, NULL],
595       [$allowed_un, 'OR', $allowed_un, FALSE, NULL],
596
597       // Allowed (ct) OR forbidden (ct,cf,un).
598       [$allowed_ct, 'OR', $forbidden_ct, TRUE, TRUE],
599       [$allowed_ct, 'OR', $forbidden_cf, TRUE, FALSE],
600       [$allowed_ct, 'OR', $forbidden_un, TRUE, FALSE],
601       // Allowed (cf) OR forbidden (ct,cf,un).
602       [$allowed_cf, 'OR', $forbidden_ct, TRUE, TRUE],
603       [$allowed_cf, 'OR', $forbidden_cf, TRUE, FALSE],
604       [$allowed_cf, 'OR', $forbidden_un, TRUE, FALSE],
605       // Allowed (un) OR forbidden (ct,cf,un).
606       [$allowed_un, 'OR', $forbidden_ct, FALSE, NULL],
607       [$allowed_un, 'OR', $forbidden_cf, FALSE, NULL],
608       [$allowed_un, 'OR', $forbidden_un, FALSE, NULL],
609
610       // Allowed (ct) OR neutral (ct,cf,un).
611       [$allowed_ct, 'OR', $neutral_ct, TRUE, TRUE],
612       [$allowed_ct, 'OR', $neutral_cf, TRUE, TRUE],
613       [$allowed_ct, 'OR', $neutral_un, TRUE, TRUE],
614       // Allowed (cf) OR neutral (ct,cf,un).
615       [$allowed_cf, 'OR', $neutral_ct, TRUE, FALSE],
616       [$allowed_cf, 'OR', $neutral_cf, TRUE, FALSE],
617       [$allowed_cf, 'OR', $neutral_un, TRUE, FALSE],
618       // Allowed (un) OR neutral (ct,cf,un).
619       [$allowed_un, 'OR', $neutral_ct, FALSE, NULL],
620       [$allowed_un, 'OR', $neutral_cf, FALSE, NULL],
621       [$allowed_un, 'OR', $neutral_un, FALSE, NULL],
622
623       // Forbidden (ct) OR allowed (ct,cf,un).
624       [$forbidden_ct, 'OR', $allowed_ct, TRUE, TRUE],
625       [$forbidden_ct, 'OR', $allowed_cf, TRUE, TRUE],
626       [$forbidden_ct, 'OR', $allowed_un, TRUE, TRUE],
627       // Forbidden (cf) OR allowed (ct,cf,un).
628       [$forbidden_cf, 'OR', $allowed_ct, TRUE, FALSE],
629       [$forbidden_cf, 'OR', $allowed_cf, TRUE, FALSE],
630       [$forbidden_cf, 'OR', $allowed_un, TRUE, FALSE],
631       // Forbidden (un) OR allowed (ct,cf,un).
632       [$forbidden_un, 'OR', $allowed_ct, FALSE, NULL],
633       [$forbidden_un, 'OR', $allowed_cf, FALSE, NULL],
634       [$forbidden_un, 'OR', $allowed_un, FALSE, NULL],
635
636       // Forbidden (ct) OR neutral (ct,cf,un).
637       [$forbidden_ct, 'OR', $neutral_ct, TRUE, TRUE],
638       [$forbidden_ct, 'OR', $neutral_cf, TRUE, TRUE],
639       [$forbidden_ct, 'OR', $neutral_un, TRUE, TRUE],
640       // Forbidden (cf) OR neutral (ct,cf,un).
641       [$forbidden_cf, 'OR', $neutral_ct, TRUE, FALSE],
642       [$forbidden_cf, 'OR', $neutral_cf, TRUE, FALSE],
643       [$forbidden_cf, 'OR', $neutral_un, TRUE, FALSE],
644       // Forbidden (un) OR neutral (ct,cf,un).
645       [$forbidden_un, 'OR', $neutral_ct, FALSE, NULL],
646       [$forbidden_un, 'OR', $neutral_cf, FALSE, NULL],
647       [$forbidden_un, 'OR', $neutral_un, FALSE, NULL],
648
649       // Forbidden (ct) OR forbidden (ct,cf,un).
650       [$forbidden_ct, 'OR', $forbidden_ct, TRUE, TRUE],
651       [$forbidden_ct, 'OR', $forbidden_cf, TRUE, TRUE],
652       [$forbidden_ct, 'OR', $forbidden_un, TRUE, TRUE],
653       // Forbidden (cf) OR forbidden (ct,cf,un).
654       [$forbidden_cf, 'OR', $forbidden_ct, TRUE, TRUE],
655       [$forbidden_cf, 'OR', $forbidden_cf, TRUE, FALSE],
656       [$forbidden_cf, 'OR', $forbidden_un, TRUE, FALSE],
657       // Forbidden (un) OR forbidden (ct,cf,un).
658       [$forbidden_un, 'OR', $forbidden_ct, FALSE, NULL],
659       [$forbidden_un, 'OR', $forbidden_cf, FALSE, NULL],
660       [$forbidden_un, 'OR', $forbidden_un, FALSE, NULL],
661
662       // Neutral (ct) OR allowed (ct,cf,un).
663       [$neutral_ct, 'OR', $allowed_ct, TRUE, TRUE],
664       [$neutral_ct, 'OR', $allowed_cf, TRUE, FALSE],
665       [$neutral_ct, 'OR', $allowed_un, TRUE, FALSE],
666       // Neutral (cf) OR allowed (ct,cf,un).
667       [$neutral_cf, 'OR', $allowed_ct, TRUE, TRUE],
668       [$neutral_cf, 'OR', $allowed_cf, TRUE, FALSE],
669       [$neutral_cf, 'OR', $allowed_un, TRUE, FALSE],
670       // Neutral (un) OR allowed (ct,cf,un).
671       [$neutral_un, 'OR', $allowed_ct, FALSE, NULL],
672       [$neutral_un, 'OR', $allowed_cf, FALSE, NULL],
673       [$neutral_un, 'OR', $allowed_un, FALSE, NULL],
674
675       // Neutral (ct) OR neutral (ct,cf,un).
676       [$neutral_ct, 'OR', $neutral_ct, TRUE, TRUE],
677       [$neutral_ct, 'OR', $neutral_cf, TRUE, TRUE],
678       [$neutral_ct, 'OR', $neutral_un, TRUE, TRUE],
679       // Neutral (cf) OR neutral (ct,cf,un).
680       [$neutral_cf, 'OR', $neutral_ct, TRUE, TRUE],
681       [$neutral_cf, 'OR', $neutral_cf, TRUE, FALSE],
682       [$neutral_cf, 'OR', $neutral_un, TRUE, FALSE],
683       // Neutral (un) OR neutral (ct,cf,un).
684       [$neutral_un, 'OR', $neutral_ct, FALSE, NULL],
685       [$neutral_un, 'OR', $neutral_cf, FALSE, NULL],
686       [$neutral_un, 'OR', $neutral_un, FALSE, NULL],
687
688       // Neutral (ct) OR forbidden (ct,cf,un).
689       [$neutral_ct, 'OR', $forbidden_ct, TRUE, TRUE],
690       [$neutral_ct, 'OR', $forbidden_cf, TRUE, FALSE],
691       [$neutral_ct, 'OR', $forbidden_un, TRUE, FALSE],
692       // Neutral (cf) OR forbidden (ct,cf,un).
693       [$neutral_cf, 'OR', $forbidden_ct, TRUE, TRUE],
694       [$neutral_cf, 'OR', $forbidden_cf, TRUE, FALSE],
695       [$neutral_cf, 'OR', $forbidden_un, TRUE, FALSE],
696       // Neutral (un) OR forbidden (ct,cf,un).
697       [$neutral_un, 'OR', $forbidden_ct, FALSE, NULL],
698       [$neutral_un, 'OR', $forbidden_cf, FALSE, NULL],
699       [$neutral_un, 'OR', $forbidden_un, FALSE, NULL],
700
701       // Allowed (ct) AND allowed (ct,cf,un).
702       [$allowed_ct, 'AND', $allowed_ct, TRUE, TRUE],
703       [$allowed_ct, 'AND', $allowed_cf, TRUE, FALSE],
704       [$allowed_ct, 'AND', $allowed_un, TRUE, FALSE],
705       // Allowed (cf) AND allowed (ct,cf,un).
706       [$allowed_cf, 'AND', $allowed_ct, TRUE, FALSE],
707       [$allowed_cf, 'AND', $allowed_cf, TRUE, FALSE],
708       [$allowed_cf, 'AND', $allowed_un, TRUE, FALSE],
709       // Allowed (un) AND allowed (ct,cf,un).
710       [$allowed_un, 'AND', $allowed_ct, FALSE, NULL],
711       [$allowed_un, 'AND', $allowed_cf, FALSE, NULL],
712       [$allowed_un, 'AND', $allowed_un, FALSE, NULL],
713
714       // Allowed (ct) AND forbidden (ct,cf,un).
715       [$allowed_ct, 'AND', $forbidden_ct, TRUE, TRUE],
716       [$allowed_ct, 'AND', $forbidden_cf, TRUE, FALSE],
717       [$allowed_ct, 'AND', $forbidden_un, TRUE, FALSE],
718       // Allowed (cf) AND forbidden (ct,cf,un).
719       [$allowed_cf, 'AND', $forbidden_ct, TRUE, TRUE],
720       [$allowed_cf, 'AND', $forbidden_cf, TRUE, FALSE],
721       [$allowed_cf, 'AND', $forbidden_un, TRUE, FALSE],
722       // Allowed (un) AND forbidden (ct,cf,un).
723       [$allowed_un, 'AND', $forbidden_ct, FALSE, NULL],
724       [$allowed_un, 'AND', $forbidden_cf, FALSE, NULL],
725       [$allowed_un, 'AND', $forbidden_un, FALSE, NULL],
726
727       // Allowed (ct) AND neutral (ct,cf,un).
728       [$allowed_ct, 'AND', $neutral_ct, TRUE, TRUE],
729       [$allowed_ct, 'AND', $neutral_cf, TRUE, FALSE],
730       [$allowed_ct, 'AND', $neutral_un, TRUE, FALSE],
731       // Allowed (cf) AND neutral (ct,cf,un).
732       [$allowed_cf, 'AND', $neutral_ct, TRUE, FALSE],
733       [$allowed_cf, 'AND', $neutral_cf, TRUE, FALSE],
734       [$allowed_cf, 'AND', $neutral_un, TRUE, FALSE],
735       // Allowed (un) AND neutral (ct,cf,un).
736       [$allowed_un, 'AND', $neutral_ct, FALSE, NULL],
737       [$allowed_un, 'AND', $neutral_cf, FALSE, NULL],
738       [$allowed_un, 'AND', $neutral_un, FALSE, NULL],
739
740       // Forbidden (ct) AND allowed (ct,cf,un).
741       [$forbidden_ct, 'AND', $allowed_ct, TRUE, TRUE],
742       [$forbidden_ct, 'AND', $allowed_cf, TRUE, TRUE],
743       [$forbidden_ct, 'AND', $allowed_un, TRUE, TRUE],
744       // Forbidden (cf) AND allowed (ct,cf,un).
745       [$forbidden_cf, 'AND', $allowed_ct, TRUE, FALSE],
746       [$forbidden_cf, 'AND', $allowed_cf, TRUE, FALSE],
747       [$forbidden_cf, 'AND', $allowed_un, TRUE, FALSE],
748       // Forbidden (un) AND allowed (ct,cf,un).
749       [$forbidden_un, 'AND', $allowed_ct, FALSE, NULL],
750       [$forbidden_un, 'AND', $allowed_cf, FALSE, NULL],
751       [$forbidden_un, 'AND', $allowed_un, FALSE, NULL],
752
753       // Forbidden (ct) AND neutral (ct,cf,un).
754       [$forbidden_ct, 'AND', $neutral_ct, TRUE, TRUE],
755       [$forbidden_ct, 'AND', $neutral_cf, TRUE, TRUE],
756       [$forbidden_ct, 'AND', $neutral_un, TRUE, TRUE],
757       // Forbidden (cf) AND neutral (ct,cf,un).
758       [$forbidden_cf, 'AND', $neutral_ct, TRUE, FALSE],
759       [$forbidden_cf, 'AND', $neutral_cf, TRUE, FALSE],
760       [$forbidden_cf, 'AND', $neutral_un, TRUE, FALSE],
761       // Forbidden (un) AND neutral (ct,cf,un).
762       [$forbidden_un, 'AND', $neutral_ct, FALSE, NULL],
763       [$forbidden_un, 'AND', $neutral_cf, FALSE, NULL],
764       [$forbidden_un, 'AND', $neutral_un, FALSE, NULL],
765
766       // Forbidden (ct) AND forbidden (ct,cf,un).
767       [$forbidden_ct, 'AND', $forbidden_ct, TRUE, TRUE],
768       [$forbidden_ct, 'AND', $forbidden_cf, TRUE, TRUE],
769       [$forbidden_ct, 'AND', $forbidden_un, TRUE, TRUE],
770       // Forbidden (cf) AND forbidden (ct,cf,un).
771       [$forbidden_cf, 'AND', $forbidden_ct, TRUE, FALSE],
772       [$forbidden_cf, 'AND', $forbidden_cf, TRUE, FALSE],
773       [$forbidden_cf, 'AND', $forbidden_un, TRUE, FALSE],
774       // Forbidden (un) AND forbidden (ct,cf,un).
775       [$forbidden_un, 'AND', $forbidden_ct, FALSE, NULL],
776       [$forbidden_un, 'AND', $forbidden_cf, FALSE, NULL],
777       [$forbidden_un, 'AND', $forbidden_un, FALSE, NULL],
778
779       // Neutral (ct) AND allowed (ct,cf,un).
780       [$neutral_ct, 'AND', $allowed_ct, TRUE, TRUE],
781       [$neutral_ct, 'AND', $allowed_cf, TRUE, TRUE],
782       [$neutral_ct, 'AND', $allowed_un, TRUE, TRUE],
783       // Neutral (cf) AND allowed (ct,cf,un).
784       [$neutral_cf, 'AND', $allowed_ct, TRUE, FALSE],
785       [$neutral_cf, 'AND', $allowed_cf, TRUE, FALSE],
786       [$neutral_cf, 'AND', $allowed_un, TRUE, FALSE],
787       // Neutral (un) AND allowed (ct,cf,un).
788       [$neutral_un, 'AND', $allowed_ct, FALSE, NULL],
789       [$neutral_un, 'AND', $allowed_cf, FALSE, NULL],
790       [$neutral_un, 'AND', $allowed_un, FALSE, NULL],
791
792       // Neutral (ct) AND neutral (ct,cf,un).
793       [$neutral_ct, 'AND', $neutral_ct, TRUE, TRUE],
794       [$neutral_ct, 'AND', $neutral_cf, TRUE, TRUE],
795       [$neutral_ct, 'AND', $neutral_un, TRUE, TRUE],
796       // Neutral (cf) AND neutral (ct,cf,un).
797       [$neutral_cf, 'AND', $neutral_ct, TRUE, FALSE],
798       [$neutral_cf, 'AND', $neutral_cf, TRUE, FALSE],
799       [$neutral_cf, 'AND', $neutral_un, TRUE, FALSE],
800       // Neutral (un) AND neutral (ct,cf,un).
801       [$neutral_un, 'AND', $neutral_ct, FALSE, NULL],
802       [$neutral_un, 'AND', $neutral_cf, FALSE, NULL],
803       [$neutral_un, 'AND', $neutral_un, FALSE, NULL],
804
805       // Neutral (ct) AND forbidden (ct,cf,un).
806       [$neutral_ct, 'AND', $forbidden_ct, TRUE, TRUE],
807       [$neutral_ct, 'AND', $forbidden_cf, TRUE, FALSE],
808       [$neutral_ct, 'AND', $forbidden_un, TRUE, FALSE],
809       // Neutral (cf) AND forbidden (ct,cf,un).
810       [$neutral_cf, 'AND', $forbidden_ct, TRUE, TRUE],
811       [$neutral_cf, 'AND', $forbidden_cf, TRUE, FALSE],
812       [$neutral_cf, 'AND', $forbidden_un, TRUE, FALSE],
813       // Neutral (un) AND forbidden (ct,cf,un).
814       [$neutral_un, 'AND', $forbidden_ct, FALSE, NULL],
815       [$neutral_un, 'AND', $forbidden_cf, FALSE, NULL],
816       [$neutral_un, 'AND', $forbidden_un, FALSE, NULL],
817     ];
818   }
819
820   /**
821    * @covers ::andIf
822    * @covers ::orIf
823    * @covers ::inheritCacheability
824    *
825    * @dataProvider andOrCacheabilityPropagationProvider
826    */
827   public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
828     if ($op === 'OR') {
829       $result = $first->orIf($second);
830     }
831     elseif ($op === 'AND') {
832       $result = $first->andIf($second);
833     }
834     else {
835       throw new \LogicException('Invalid operator specified');
836     }
837     if ($implements_cacheable_dependency_interface) {
838       $this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.');
839       if ($result instanceof CacheableDependencyInterface) {
840         $this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.');
841       }
842     }
843     else {
844       $this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
845     }
846   }
847
848   /**
849    * @covers ::orIf
850    *
851    * Tests the special case of ORing non-forbidden access results that are both
852    * cacheable but have different cacheability metadata.
853    * This is only the case for non-forbidden access results; we still abort the
854    * ORing process as soon as a forbidden access result is encountered. This is
855    * tested in ::testOrIf().
856    */
857   public function testOrIfCacheabilityMerging() {
858     $merge_both_directions = function (AccessResult $a, AccessResult $b) {
859       // A globally cacheable access result.
860       $a->setCacheMaxAge(3600);
861       // Another access result that is cacheable per permissions.
862       $b->setCacheMaxAge(86400)->cachePerPermissions();
863
864       $r1 = $a->orIf($b);
865       $this->assertTrue($r1->getCacheMaxAge() === 3600);
866       $this->assertSame(['user.permissions'], $r1->getCacheContexts());
867       $r2 = $b->orIf($a);
868       $this->assertTrue($r2->getCacheMaxAge() === 3600);
869       $this->assertSame(['user.permissions'], $r2->getCacheContexts());
870     };
871
872     // Merge either direction, get the same result.
873     $merge_both_directions(AccessResult::allowed(), AccessResult::allowed());
874     $merge_both_directions(AccessResult::allowed(), AccessResult::neutral());
875     $merge_both_directions(AccessResult::neutral(), AccessResult::neutral());
876     $merge_both_directions(AccessResult::neutral(), AccessResult::allowed());
877   }
878
879   /**
880    * Tests allowedIfHasPermissions().
881    *
882    * @covers ::allowedIfHasPermissions
883    *
884    * @dataProvider providerTestAllowedIfHasPermissions
885    *
886    * @param string[] $permissions
887    *   The permissions to check for.
888    * @param string $conjunction
889    *   The conjunction to use when checking for permission. 'AND' or 'OR'.
890    * @param \Drupal\Core\Access\AccessResult $expected_access
891    *   The expected access check result.
892    */
893   public function testAllowedIfHasPermissions($permissions, $conjunction, AccessResult $expected_access) {
894     $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
895     $account->expects($this->any())
896       ->method('hasPermission')
897       ->willReturnMap([
898         ['allowed', TRUE],
899         ['denied', FALSE],
900       ]);
901
902     if ($permissions) {
903       $expected_access->cachePerPermissions();
904     }
905
906     $access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
907     $this->assertEquals($expected_access, $access_result);
908   }
909
910   /**
911    * Provides data for the testAllowedIfHasPermissions() method.
912    *
913    * @return array
914    */
915   public function providerTestAllowedIfHasPermissions() {
916     $access_result = AccessResult::allowedIf(FALSE);
917     $data[] = [[], 'AND', $access_result];
918     $data[] = [[], 'OR', $access_result];
919
920     $access_result = AccessResult::allowedIf(TRUE);
921     $data[] = [['allowed'], 'OR', $access_result];
922     $data[] = [['allowed'], 'AND', $access_result];
923
924     $access_result = AccessResult::allowedIf(FALSE);
925     $access_result->setReason("The 'denied' permission is required.");
926     $data[] = [['denied'], 'OR', $access_result];
927     $data[] = [['denied'], 'AND', $access_result];
928
929     $access_result = AccessResult::allowedIf(TRUE);
930     $data[] = [['allowed', 'denied'], 'OR', $access_result];
931     $data[] = [['denied', 'allowed'], 'OR', $access_result];
932
933     $access_result = AccessResult::allowedIf(TRUE);
934     $data[] = [['allowed', 'denied', 'other'], 'OR', $access_result];
935
936     $access_result = AccessResult::allowedIf(FALSE);
937     $access_result->setReason("The following permissions are required: 'allowed' AND 'denied'.");
938     $data[] = [['allowed', 'denied'], 'AND', $access_result];
939
940     return $data;
941   }
942
943 }
944
945 class UncacheableTestAccessResult implements AccessResultInterface {
946
947   /**
948    * The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
949    *
950    * @var string
951    */
952   protected $value;
953
954   /**
955    * Constructs a new UncacheableTestAccessResult object.
956    */
957   public function __construct($value) {
958     $this->value = $value;
959   }
960   /**
961    * {@inheritdoc}
962    */
963   public function isAllowed() {
964     return $this->value === 'ALLOWED';
965   }
966
967   /**
968    * {@inheritdoc}
969    */
970   public function isForbidden() {
971     return $this->value === 'FORBIDDEN';
972   }
973
974   /**
975    * {@inheritdoc}
976    */
977   public function isNeutral() {
978     return $this->value === 'NEUTRAL';
979   }
980
981   /**
982    * {@inheritdoc}
983    */
984   public function orIf(AccessResultInterface $other) {
985     if ($this->isForbidden() || $other->isForbidden()) {
986       return new static('FORBIDDEN');
987     }
988     elseif ($this->isAllowed() || $other->isAllowed()) {
989       return new static('ALLOWED');
990     }
991     else {
992       return new static('NEUTRAL');
993     }
994   }
995
996   /**
997    * {@inheritdoc}
998    */
999   public function andIf(AccessResultInterface $other) {
1000     if ($this->isForbidden() || $other->isForbidden()) {
1001       return new static('FORBIDDEN');
1002     }
1003     elseif ($this->isAllowed() && $other->isAllowed()) {
1004       return new static('ALLOWED');
1005     }
1006     else {
1007       return new static('NEUTRAL');
1008     }
1009   }
1010
1011 }