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