PageRenderTime 46ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php

http://github.com/drupal/drupal
PHP | 345 lines | 193 code | 34 blank | 118 comment | 0 complexity | 73e0275829ebc694aca333395e7f0750 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Tests\Core\Menu;
  3. use Drupal\Core\Access\AccessResult;
  4. use Drupal\Core\Cache\Context\CacheContextsManager;
  5. use Drupal\Core\DependencyInjection\Container;
  6. use Drupal\Core\Entity\EntityStorageInterface;
  7. use Drupal\Core\Entity\EntityTypeManagerInterface;
  8. use Drupal\Core\Menu\DefaultMenuLinkTreeManipulators;
  9. use Drupal\Core\Menu\MenuLinkTreeElement;
  10. use Drupal\Tests\UnitTestCase;
  11. use Drupal\node\NodeInterface;
  12. /**
  13. * Tests the default menu link tree manipulators.
  14. *
  15. * @group Menu
  16. *
  17. * @coversDefaultClass \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators
  18. */
  19. class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
  20. /**
  21. * The mocked access manager.
  22. *
  23. * @var \Drupal\Core\Access\AccessManagerInterface|\PHPUnit\Framework\MockObject\MockObject
  24. */
  25. protected $accessManager;
  26. /**
  27. * The mocked current user.
  28. *
  29. * @var \Drupal\Core\Session\AccountInterface|\PHPUnit\Framework\MockObject\MockObject
  30. */
  31. protected $currentUser;
  32. /**
  33. * The mocked entity type manager.
  34. *
  35. * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
  36. */
  37. protected $entityTypeManager;
  38. /**
  39. * The default menu link tree manipulators.
  40. *
  41. * @var \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators
  42. */
  43. protected $defaultMenuTreeManipulators;
  44. /**
  45. * The original menu tree build in mockTree().
  46. *
  47. * @var \Drupal\Core\Menu\MenuLinkTreeElement[]
  48. */
  49. protected $originalTree = [];
  50. /**
  51. * Array of menu link instances
  52. *
  53. * @var \Drupal\Core\Menu\MenuLinkInterface[]
  54. */
  55. protected $links = [];
  56. /**
  57. * {@inheritdoc}
  58. */
  59. protected function setUp() {
  60. parent::setUp();
  61. $this->accessManager = $this->createMock('\Drupal\Core\Access\AccessManagerInterface');
  62. $this->currentUser = $this->createMock('Drupal\Core\Session\AccountInterface');
  63. $this->currentUser->method('isAuthenticated')
  64. ->willReturn(TRUE);
  65. $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
  66. $this->defaultMenuTreeManipulators = new DefaultMenuLinkTreeManipulators($this->accessManager, $this->currentUser, $this->entityTypeManager);
  67. $cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
  68. $cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
  69. $cache_contexts_manager->reveal();
  70. $container = new Container();
  71. $container->set('cache_contexts_manager', $cache_contexts_manager);
  72. \Drupal::setContainer($container);
  73. }
  74. /**
  75. * Creates a mock tree.
  76. *
  77. * This mocks a tree with the following structure:
  78. * - 1
  79. * - 2
  80. * - 3
  81. * - 4
  82. * - 5
  83. * - 7
  84. * - 6
  85. * - 8
  86. * - 9
  87. *
  88. * With link 6 being the only external link.
  89. */
  90. protected function mockTree() {
  91. $this->links = [
  92. 1 => MenuLinkMock::create(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '']),
  93. 2 => MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => ['foo' => 'bar']]),
  94. 3 => MenuLinkMock::create(['id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => ['baz' => 'qux']]),
  95. 4 => MenuLinkMock::create(['id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3']),
  96. 5 => MenuLinkMock::create(['id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '']),
  97. 6 => MenuLinkMock::create(['id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '']),
  98. 7 => MenuLinkMock::create(['id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => '']),
  99. 8 => MenuLinkMock::create(['id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '']),
  100. 9 => DynamicMenuLinkMock::create(['id' => 'test.example9', 'parent' => ''])->setCurrentUser($this->currentUser),
  101. ];
  102. $this->originalTree = [];
  103. $this->originalTree[1] = new MenuLinkTreeElement($this->links[1], FALSE, 1, FALSE, []);
  104. $this->originalTree[2] = new MenuLinkTreeElement($this->links[2], TRUE, 1, FALSE, [
  105. 3 => new MenuLinkTreeElement($this->links[3], TRUE, 2, FALSE, [
  106. 4 => new MenuLinkTreeElement($this->links[4], FALSE, 3, FALSE, []),
  107. ]),
  108. ]);
  109. $this->originalTree[5] = new MenuLinkTreeElement($this->links[5], TRUE, 1, FALSE, [
  110. 7 => new MenuLinkTreeElement($this->links[7], FALSE, 2, FALSE, []),
  111. ]);
  112. $this->originalTree[6] = new MenuLinkTreeElement($this->links[6], FALSE, 1, FALSE, []);
  113. $this->originalTree[8] = new MenuLinkTreeElement($this->links[8], FALSE, 1, FALSE, []);
  114. $this->originalTree[9] = new MenuLinkTreeElement($this->links[9], FALSE, 1, FALSE, []);
  115. }
  116. /**
  117. * Tests the generateIndexAndSort() tree manipulator.
  118. *
  119. * @covers ::generateIndexAndSort
  120. */
  121. public function testGenerateIndexAndSort() {
  122. $this->mockTree();
  123. $tree = $this->originalTree;
  124. $tree = $this->defaultMenuTreeManipulators->generateIndexAndSort($tree);
  125. // Validate that parent elements #1, #2, #5 and #6 exist on the root level.
  126. $this->assertEquals($this->links[1]->getPluginId(), $tree['50000 foo test.example1']->link->getPluginId());
  127. $this->assertEquals($this->links[2]->getPluginId(), $tree['50000 bar test.example2']->link->getPluginId());
  128. $this->assertEquals($this->links[5]->getPluginId(), $tree['50000 foofoo test.example5']->link->getPluginId());
  129. $this->assertEquals($this->links[6]->getPluginId(), $tree['50000 barbar test.example6']->link->getPluginId());
  130. $this->assertEquals($this->links[8]->getPluginId(), $tree['50000 quxqux test.example8']->link->getPluginId());
  131. // Verify that child element #4 is at the correct location in the hierarchy.
  132. $this->assertEquals($this->links[4]->getPluginId(), $tree['50000 bar test.example2']->subtree['50000 baz test.example3']->subtree['50000 qux test.example4']->link->getPluginId());
  133. // Verify that child element #7 is at the correct location in the hierarchy.
  134. $this->assertEquals($this->links[7]->getPluginId(), $tree['50000 foofoo test.example5']->subtree['50000 bazbaz test.example7']->link->getPluginId());
  135. }
  136. /**
  137. * Tests the checkAccess() tree manipulator.
  138. *
  139. * @covers ::checkAccess
  140. * @covers ::menuLinkCheckAccess
  141. */
  142. public function testCheckAccess() {
  143. // Those menu links that are non-external will have their access checks
  144. // performed. 9 routes, but 1 is external, 2 already have their 'access'
  145. // property set, and 1 is a child if an inaccessible menu link, so only 5
  146. // calls will be made.
  147. $this->accessManager->expects($this->exactly(5))
  148. ->method('checkNamedRoute')
  149. ->will($this->returnValueMap([
  150. ['example1', [], $this->currentUser, TRUE, AccessResult::forbidden()],
  151. ['example2', ['foo' => 'bar'], $this->currentUser, TRUE, AccessResult::allowed()->cachePerPermissions()],
  152. ['example3', ['baz' => 'qux'], $this->currentUser, TRUE, AccessResult::neutral()],
  153. ['example5', [], $this->currentUser, TRUE, AccessResult::allowed()],
  154. ['user.logout', [], $this->currentUser, TRUE, AccessResult::allowed()],
  155. ]));
  156. $this->mockTree();
  157. $this->originalTree[5]->subtree[7]->access = AccessResult::neutral();
  158. $this->originalTree[8]->access = AccessResult::allowed()->cachePerUser();
  159. // Since \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators::checkAccess()
  160. // allows access to any link if the user has the 'link to any page'
  161. // permission, *every* single access result is varied by permissions.
  162. $tree = $this->defaultMenuTreeManipulators->checkAccess($this->originalTree);
  163. // Menu link 1: route without parameters, access forbidden, but at level 0,
  164. // hence kept.
  165. $element = $tree[1];
  166. $this->assertEquals(AccessResult::forbidden()->cachePerPermissions(), $element->access);
  167. $this->assertInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link);
  168. // Menu link 2: route with parameters, access granted.
  169. $element = $tree[2];
  170. $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $element->access);
  171. $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link);
  172. // Menu link 3: route with parameters, AccessResult::neutral(), top-level
  173. // inaccessible link, hence kept for its cacheability metadata.
  174. // Note that the permissions cache context is added automatically, because
  175. // we always check the "link to any page" permission.
  176. $element = $tree[2]->subtree[3];
  177. $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), $element->access);
  178. $this->assertInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link);
  179. // Menu link 4: child of menu link 3, which was AccessResult::neutral(),
  180. // hence menu link 3's subtree is removed, of which this menu link is one.
  181. $this->assertFalse(array_key_exists(4, $tree[2]->subtree[3]->subtree));
  182. // Menu link 5: no route name, treated as external, hence access granted.
  183. $element = $tree[5];
  184. $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $element->access);
  185. $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link);
  186. // Menu link 6: external URL, hence access granted.
  187. $element = $tree[6];
  188. $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $element->access);
  189. $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link);
  190. // Menu link 7: 'access' already set: AccessResult::neutral(), top-level
  191. // inaccessible link, hence kept for its cacheability metadata.
  192. // Note that unlike for menu link 3, the permission cache context is absent,
  193. // because ::checkAccess() doesn't perform access checking when 'access' is
  194. // already set.
  195. $element = $tree[5]->subtree[7];
  196. $this->assertEquals(AccessResult::neutral(), $element->access);
  197. $this->assertInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link);
  198. // Menu link 8: 'access' already set, note that 'per permissions' caching
  199. // is not added.
  200. $element = $tree[8];
  201. $this->assertEquals(AccessResult::allowed()->cachePerUser(), $element->access);
  202. $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link);
  203. }
  204. /**
  205. * Tests checkAccess() tree manipulator with 'link to any page' permission.
  206. *
  207. * @covers ::checkAccess
  208. * @covers ::menuLinkCheckAccess
  209. */
  210. public function testCheckAccessWithLinkToAnyPagePermission() {
  211. $this->mockTree();
  212. $this->currentUser->expects($this->exactly(9))
  213. ->method('hasPermission')
  214. ->with('link to any page')
  215. ->willReturn(TRUE);
  216. $this->mockTree();
  217. $this->defaultMenuTreeManipulators->checkAccess($this->originalTree);
  218. $expected_access_result = AccessResult::allowed()->cachePerPermissions();
  219. $this->assertEquals($expected_access_result, $this->originalTree[1]->access);
  220. $this->assertEquals($expected_access_result, $this->originalTree[2]->access);
  221. $this->assertEquals($expected_access_result, $this->originalTree[2]->subtree[3]->access);
  222. $this->assertEquals($expected_access_result, $this->originalTree[2]->subtree[3]->subtree[4]->access);
  223. $this->assertEquals($expected_access_result, $this->originalTree[5]->subtree[7]->access);
  224. $this->assertEquals($expected_access_result, $this->originalTree[6]->access);
  225. $this->assertEquals($expected_access_result, $this->originalTree[8]->access);
  226. $this->assertEquals($expected_access_result, $this->originalTree[9]->access);
  227. }
  228. /**
  229. * Tests the flatten() tree manipulator.
  230. *
  231. * @covers ::flatten
  232. */
  233. public function testFlatten() {
  234. $this->mockTree();
  235. $tree = $this->defaultMenuTreeManipulators->flatten($this->originalTree);
  236. $this->assertEquals([1, 2, 5, 6, 8, 9], array_keys($this->originalTree));
  237. $this->assertEquals([1, 2, 5, 6, 8, 9, 3, 4, 7], array_keys($tree));
  238. }
  239. /**
  240. * Tests the optimized node access checking.
  241. *
  242. * @covers ::checkNodeAccess
  243. * @covers ::collectNodeLinks
  244. * @covers ::checkAccess
  245. */
  246. public function testCheckNodeAccess() {
  247. $links = [
  248. 1 => MenuLinkMock::create(['id' => 'node.1', 'route_name' => 'entity.node.canonical', 'title' => 'foo', 'parent' => '', 'route_parameters' => ['node' => 1]]),
  249. 2 => MenuLinkMock::create(['id' => 'node.2', 'route_name' => 'entity.node.canonical', 'title' => 'bar', 'parent' => '', 'route_parameters' => ['node' => 2]]),
  250. 3 => MenuLinkMock::create(['id' => 'node.3', 'route_name' => 'entity.node.canonical', 'title' => 'baz', 'parent' => 'node.2', 'route_parameters' => ['node' => 3]]),
  251. 4 => MenuLinkMock::create(['id' => 'node.4', 'route_name' => 'entity.node.canonical', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => ['node' => 4]]),
  252. 5 => MenuLinkMock::create(['id' => 'test.1', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => '']),
  253. 6 => MenuLinkMock::create(['id' => 'test.2', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => 'test.1']),
  254. ];
  255. $tree = [];
  256. $tree[1] = new MenuLinkTreeElement($links[1], FALSE, 1, FALSE, []);
  257. $tree[2] = new MenuLinkTreeElement($links[2], TRUE, 1, FALSE, [
  258. 3 => new MenuLinkTreeElement($links[3], TRUE, 2, FALSE, [
  259. 4 => new MenuLinkTreeElement($links[4], FALSE, 3, FALSE, []),
  260. ]),
  261. ]);
  262. $tree[5] = new MenuLinkTreeElement($links[5], TRUE, 1, FALSE, [
  263. 6 => new MenuLinkTreeElement($links[6], FALSE, 2, FALSE, []),
  264. ]);
  265. $query = $this->createMock('Drupal\Core\Entity\Query\QueryInterface');
  266. $query->expects($this->at(0))
  267. ->method('condition')
  268. ->with('nid', [1, 2, 3, 4]);
  269. $query->expects($this->at(1))
  270. ->method('condition')
  271. ->with('status', NodeInterface::PUBLISHED);
  272. $query->expects($this->once())
  273. ->method('execute')
  274. ->willReturn([1, 2, 4]);
  275. $storage = $this->createMock(EntityStorageInterface::class);
  276. $storage->expects($this->once())
  277. ->method('getQuery')
  278. ->willReturn($query);
  279. $this->entityTypeManager->expects($this->once())
  280. ->method('getStorage')
  281. ->with('node')
  282. ->willReturn($storage);
  283. $node_access_result = AccessResult::allowed()->cachePerPermissions()->addCacheContexts(['user.node_grants:view']);
  284. $tree = $this->defaultMenuTreeManipulators->checkNodeAccess($tree);
  285. $this->assertEquals($node_access_result, $tree[1]->access);
  286. $this->assertEquals($node_access_result, $tree[2]->access);
  287. // Ensure that access denied is set.
  288. $this->assertEquals(AccessResult::neutral(), $tree[2]->subtree[3]->access);
  289. $this->assertEquals($node_access_result, $tree[2]->subtree[3]->subtree[4]->access);
  290. // Ensure that other routes than entity.node.canonical are set as well.
  291. $this->assertNull($tree[5]->access);
  292. $this->assertNull($tree[5]->subtree[6]->access);
  293. // On top of the node access checking now run the ordinary route based
  294. // access checkers.
  295. // Ensure that the access manager is just called for the non-node routes.
  296. $this->accessManager->expects($this->at(0))
  297. ->method('checkNamedRoute')
  298. ->with('test_route', [], $this->currentUser, TRUE)
  299. ->willReturn(AccessResult::allowed());
  300. $this->accessManager->expects($this->at(1))
  301. ->method('checkNamedRoute')
  302. ->with('test_route', [], $this->currentUser, TRUE)
  303. ->willReturn(AccessResult::neutral());
  304. $tree = $this->defaultMenuTreeManipulators->checkAccess($tree);
  305. $this->assertEquals($node_access_result, $tree[1]->access);
  306. $this->assertEquals($node_access_result, $tree[2]->access);
  307. $this->assertEquals(AccessResult::neutral(), $tree[2]->subtree[3]->access);
  308. $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $tree[5]->access);
  309. $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), $tree[5]->subtree[6]->access);
  310. }
  311. }