/** * Tests creating links with an expected tree structure. */ public function testCreateLinksInMenu() { // This creates a tree with the following structure: // - 1 // - 2 // - 3 // - 4 // - 5 // - 7 // - 6 // - 8 // With link 6 being the only external link. $links = array(1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '')), 2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => array('foo' => 'bar'))), 3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => array('baz' => 'qux'))), 4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3')), 5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '')), 6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://drupal.org/', 'title' => 'barbar', 'parent' => '')), 7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => '')), 8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => ''))); foreach ($links as $instance) { $this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition()); } $parameters = new MenuTreeParameters(); $tree = $this->linkTree->load('mock', $parameters); $count = function (array $tree) { $sum = function ($carry, MenuLinkTreeElement $item) { return $carry + $item->count(); }; return array_reduce($tree, $sum); }; $this->assertEqual($count($tree), 8); $parameters = new MenuTreeParameters(); $parameters->setRoot('test.example2'); $tree = $this->linkTree->load($instance->getMenuName(), $parameters); $top_link = reset($tree); $this->assertEqual(count($top_link->subtree), 1); $child = reset($top_link->subtree); $this->assertEqual($child->link->getPluginId(), $links[3]->getPluginId()); $height = $this->linkTree->getSubtreeHeight('test.example2'); $this->assertEqual($height, 3); }
/** * Provides test data for all test methods. * * @return array * Returns a list of test data of which each is an array containing the * following elements: * - request: A request object. * - links: An array of menu links keyed by ID. * - menu_name: The active menu name. * - expected_link: The expected active link for the given menu. */ public function provider() { $data = array(); $mock_route = new Route(''); $request = new Request(); $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'baby_llama'); $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $mock_route); $request->attributes->set('_raw_variables', new ParameterBag(array())); $link_1 = MenuLinkMock::create(array('id' => 'baby_llama_link_1', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'mama_llama_link')); $link_2 = MenuLinkMock::create(array('id' => 'baby_llama_link_2', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'papa_llama_link')); // @see \Drupal\Core\Menu\MenuLinkManagerInterface::getParentIds() $link_1_parent_ids = array('baby_llama_link_1', 'mama_llama_link', ''); $empty_active_trail = array(''); $link_1__active_trail_cache_key = 'menu_trail.baby_llama_link_1|mama_llama_link|'; $empty_active_trail_cache_key = 'menu_trail.'; // No active link is returned when zero links match the current route. $data[] = array($request, array(), $this->randomMachineName(), NULL, $empty_active_trail, $empty_active_trail_cache_key); // The first (and only) matching link is returned when one link matches the // current route. $data[] = array($request, array('baby_llama_link_1' => $link_1), $this->randomMachineName(), $link_1, $link_1_parent_ids, $link_1__active_trail_cache_key); // The first of multiple matching links is returned when multiple links // match the current route, where "first" is determined by sorting by key. $data[] = array($request, array('baby_llama_link_1' => $link_1, 'baby_llama_link_2' => $link_2), $this->randomMachineName(), $link_1, $link_1_parent_ids, $link_1__active_trail_cache_key); // No active link is returned in case of a 403. $request = new Request(); $request->attributes->set('_exception_statuscode', 403); $data[] = array($request, FALSE, $this->randomMachineName(), NULL, $empty_active_trail, $empty_active_trail_cache_key); // No active link is returned when the route name is missing. $request = new Request(); $data[] = array($request, FALSE, $this->randomMachineName(), NULL, $empty_active_trail, $empty_active_trail_cache_key); return $data; }
/** * Tests count(). * * @covers ::count */ public function testCount() { $link_1 = MenuLinkMock::create(array('id' => 'test_1')); $link_2 = MenuLinkMock::create(array('id' => 'test_2')); $child_item = new MenuLinkTreeElement($link_2, FALSE, 2, FALSE, array()); $parent_item = new MenuLinkTreeElement($link_1, FALSE, 2, FALSE, array($child_item)); $this->assertSame(1, $child_item->count()); $this->assertSame(2, $parent_item->count()); }
/** * Creates a mock tree. * * This mocks a tree with the following structure: * - 1 * - 2 * - 3 * - 4 * - 5 * - 7 * - 6 * - 8 * * With link 6 being the only external link. */ protected function mockTree() { $this->links = array(1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '')), 2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => array('foo' => 'bar'))), 3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => array('baz' => 'qux'))), 4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3')), 5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '')), 6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://drupal.org/', 'title' => 'barbar', 'parent' => '')), 7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => '')), 8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => ''))); $this->originalTree = array(); $this->originalTree[1] = new MenuLinkTreeElement($this->links[1], FALSE, 1, FALSE, array()); $this->originalTree[2] = new MenuLinkTreeElement($this->links[2], TRUE, 1, FALSE, array(3 => new MenuLinkTreeElement($this->links[3], TRUE, 2, FALSE, array(4 => new MenuLinkTreeElement($this->links[4], FALSE, 3, FALSE, array()))))); $this->originalTree[5] = new MenuLinkTreeElement($this->links[5], TRUE, 1, FALSE, array(7 => new MenuLinkTreeElement($this->links[7], FALSE, 2, FALSE, array()))); $this->originalTree[6] = new MenuLinkTreeElement($this->links[6], FALSE, 1, FALSE, array()); $this->originalTree[8] = new MenuLinkTreeElement($this->links[8], FALSE, 1, FALSE, array()); }
/** * {@inheritdoc} */ protected function setUp() { parent::setUp(); $this->installSchema('system', 'sequences'); $this->installEntitySchema('user'); $this->installSchema('system', array('router')); $this->installEntitySchema('menu_link_content'); $account = User::create(['name' => $this->randomMachineName(), 'status' => 1]); $account->save(); $this->container->get('current_user')->setAccount($account); $this->menuLinkManager = $this->container->get('plugin.manager.menu.link'); $this->linkTree = $this->container->get('menu.link_tree'); $this->blockManager = $this->container->get('plugin.manager.block'); $routes = new RouteCollection(); $requirements = array('_access' => 'TRUE'); $options = array('_access_checks' => array('access_check.default')); $routes->add('example1', new Route('/example1', array(), $requirements, $options)); $routes->add('example2', new Route('/example2', array(), $requirements, $options)); $routes->add('example3', new Route('/example3', array(), $requirements, $options)); $routes->add('example4', new Route('/example4', array(), $requirements, $options)); $routes->add('example5', new Route('/example5', array(), $requirements, $options)); $routes->add('example6', new Route('/example6', array(), $requirements, $options)); $routes->add('example7', new Route('/example7', array(), $requirements, $options)); $routes->add('example8', new Route('/example8', array(), $requirements, $options)); $mock_route_provider = new MockRouteProvider($routes); $this->container->set('router.route_provider', $mock_route_provider); // Add a new custom menu. $menu_name = 'mock'; $label = $this->randomMachineName(16); $this->menu = entity_create('menu', array('id' => $menu_name, 'label' => $label, 'description' => 'Description text')); $this->menu->save(); // This creates a tree with the following structure: // - 1 // - 2 // - 3 // - 4 // - 5 // - 7 // - 6 // - 8 // With link 6 being the only external link. $links = array(1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '', 'weight' => 0)), 2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('foo' => 'bar'), 'weight' => 1)), 3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'weight' => 2)), 4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3', 'weight' => 3)), 5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '', 'expanded' => TRUE, 'weight' => 4)), 6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://drupal.org/', 'title' => 'barbar', 'parent' => '', 'weight' => 5)), 7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => 'test.example5', 'weight' => 6)), 8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '', 'weight' => 7))); foreach ($links as $instance) { $this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition()); } }
/** * Provides the test cases to test for ::testBuildCacheability(). * * As explained in the documentation for ::testBuildCacheability(), this * generates 1 + (3 * 2 * 3) = 19 test cases. * * @see testBuildCacheability */ public function providerTestBuildCacheability() { $base_expected_build_empty = ['#cache' => ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT]]; $base_expected_build = ['#cache' => ['contexts' => [], 'tags' => ['config:system.menu.mock'], 'max-age' => Cache::PERMANENT], '#sorted' => TRUE, '#theme' => 'menu__mock', '#items' => []]; $get_built_element = function (MenuLinkTreeElement $element, array $classes) { return ['attributes' => new Attribute(['class' => array_merge(['menu-item'], $classes)]), 'title' => $element->link->getTitle(), 'url' => new Url($element->link->getRouteName(), $element->link->getRouteParameters(), ['set_active_class' => TRUE]), 'below' => [], 'original_link' => $element->link]; }; // The three access scenarios described in this method's documentation. $access_scenarios = [[NULL, []], [AccessResult::allowed(), ['access:allowed']], [AccessResult::neutral(), ['access:neutral']]]; // The two links scenarios described in this method's documentation. $cache_defaults = ['cache_max_age' => Cache::PERMANENT, 'cache_tags' => []]; $links_scenarios = [[MenuLinkMock::create(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'Example 1']), MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example1', 'title' => 'Example 2', 'metadata' => ['cache_contexts' => ['llama']] + $cache_defaults])], [MenuLinkMock::create(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'Example 1', 'metadata' => ['cache_contexts' => ['foo']] + $cache_defaults]), MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example1', 'title' => 'Example 2', 'metadata' => ['cache_contexts' => ['bar']] + $cache_defaults])]]; $data = []; // Empty tree. $data[] = ['description' => 'Empty tree.', 'tree' => [], 'expected_build' => $base_expected_build_empty]; for ($i = 0; $i < count($access_scenarios); $i++) { list($access, $access_cache_contexts) = $access_scenarios[$i]; if ($access !== NULL) { $access->addCacheContexts($access_cache_contexts); } for ($j = 0; $j < count($links_scenarios); $j++) { $links = $links_scenarios[$j]; // Single-element tree. $tree = [new MenuLinkTreeElement($links[0], FALSE, 0, FALSE, [])]; $tree[0]->access = $access; if ($access === NULL || $access->isAllowed()) { $expected_build = $base_expected_build; $expected_build['#items']['test.example1'] = $get_built_element($tree[0], []); } else { $expected_build = $base_expected_build_empty; } $expected_build['#cache']['contexts'] = array_merge($expected_build['#cache']['contexts'], $access_cache_contexts, $links[0]->getCacheContexts()); $data[] = ['description' => "Single-item tree; access={$i}; link={$j}.", 'tree' => $tree, 'expected_build' => $expected_build]; // Single-level tree. $tree = [new MenuLinkTreeElement($links[0], FALSE, 0, FALSE, []), new MenuLinkTreeElement($links[1], FALSE, 0, FALSE, [])]; $tree[0]->access = $access; $expected_build = $base_expected_build; if ($access === NULL || $access->isAllowed()) { $expected_build['#items']['test.example1'] = $get_built_element($tree[0], []); } $expected_build['#items']['test.example2'] = $get_built_element($tree[1], []); $expected_build['#cache']['contexts'] = array_merge($expected_build['#cache']['contexts'], $access_cache_contexts, $links[0]->getCacheContexts(), $links[1]->getCacheContexts()); $data[] = ['description' => "Single-level tree; access={$i}; link={$j}.", 'tree' => $tree, 'expected_build' => $expected_build]; // Multi-level tree. $multi_level_root_a = MenuLinkMock::create(['id' => 'test.roota', 'route_name' => 'roota', 'title' => 'Root A']); $multi_level_root_b = MenuLinkMock::create(['id' => 'test.rootb', 'route_name' => 'rootb', 'title' => 'Root B']); $multi_level_parent_c = MenuLinkMock::create(['id' => 'test.parentc', 'route_name' => 'parentc', 'title' => 'Parent C']); $tree = [new MenuLinkTreeElement($multi_level_root_a, TRUE, 0, FALSE, [new MenuLinkTreeElement($multi_level_parent_c, TRUE, 0, FALSE, [new MenuLinkTreeElement($links[0], FALSE, 0, FALSE, [])])]), new MenuLinkTreeElement($multi_level_root_b, TRUE, 0, FALSE, [new MenuLinkTreeElement($links[1], FALSE, 1, FALSE, [])])]; $tree[0]->subtree[0]->subtree[0]->access = $access; $expected_build = $base_expected_build; $expected_build['#items']['test.roota'] = $get_built_element($tree[0], ['menu-item--expanded']); $expected_build['#items']['test.roota']['below']['test.parentc'] = $get_built_element($tree[0]->subtree[0], ['menu-item--expanded']); if ($access === NULL || $access->isAllowed()) { $expected_build['#items']['test.roota']['below']['test.parentc']['below']['test.example1'] = $get_built_element($tree[0]->subtree[0]->subtree[0], []); } $expected_build['#items']['test.rootb'] = $get_built_element($tree[1], ['menu-item--expanded']); $expected_build['#items']['test.rootb']['below']['test.example2'] = $get_built_element($tree[1]->subtree[0], []); $expected_build['#cache']['contexts'] = array_merge($expected_build['#cache']['contexts'], $access_cache_contexts, $links[0]->getCacheContexts(), $links[1]->getCacheContexts()); $data[] = ['description' => "Multi-level tree; access={$i}; link={$j}.", 'tree' => $tree, 'expected_build' => $expected_build]; } } return $data; }
/** * Tests the optimized node access checking. * * @covers ::checkNodeAccess * @covers ::collectNodeLinks * @covers ::checkAccess */ public function testCheckNodeAccess() { $links = array(1 => MenuLinkMock::create(array('id' => 'node.1', 'route_name' => 'entity.node.canonical', 'title' => 'foo', 'parent' => '', 'route_parameters' => array('node' => 1))), 2 => MenuLinkMock::create(array('id' => 'node.2', 'route_name' => 'entity.node.canonical', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('node' => 2))), 3 => MenuLinkMock::create(array('id' => 'node.3', 'route_name' => 'entity.node.canonical', 'title' => 'baz', 'parent' => 'node.2', 'route_parameters' => array('node' => 3))), 4 => MenuLinkMock::create(array('id' => 'node.4', 'route_name' => 'entity.node.canonical', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => array('node' => 4))), 5 => MenuLinkMock::create(array('id' => 'test.1', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => '')), 6 => MenuLinkMock::create(array('id' => 'test.2', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => 'test.1'))); $tree = array(); $tree[1] = new MenuLinkTreeElement($links[1], FALSE, 1, FALSE, array()); $tree[2] = new MenuLinkTreeElement($links[2], TRUE, 1, FALSE, array(3 => new MenuLinkTreeElement($links[3], TRUE, 2, FALSE, array(4 => new MenuLinkTreeElement($links[4], FALSE, 3, FALSE, array()))))); $tree[5] = new MenuLinkTreeElement($links[5], TRUE, 1, FALSE, array(6 => new MenuLinkTreeElement($links[6], FALSE, 2, FALSE, array()))); $query = $this->getMock('Drupal\\Core\\Entity\\Query\\QueryInterface'); $query->expects($this->at(0))->method('condition')->with('nid', array(1, 2, 3, 4)); $query->expects($this->at(1))->method('condition')->with('status', NODE_PUBLISHED); $query->expects($this->once())->method('execute')->willReturn(array(1, 2, 4)); $this->queryFactory->expects($this->once())->method('get')->with('node')->willReturn($query); $node_access_result = AccessResult::allowed()->cachePerPermissions()->addCacheContexts(['user.node_grants:view']); $tree = $this->defaultMenuTreeManipulators->checkNodeAccess($tree); $this->assertEquals($node_access_result, $tree[1]->access); $this->assertEquals($node_access_result, $tree[2]->access); // Ensure that access denied is set. $this->assertEquals(AccessResult::neutral(), $tree[2]->subtree[3]->access); $this->assertEquals($node_access_result, $tree[2]->subtree[3]->subtree[4]->access); // Ensure that other routes than entity.node.canonical are set as well. $this->assertNull($tree[5]->access); $this->assertNull($tree[5]->subtree[6]->access); // On top of the node access checking now run the ordinary route based // access checkers. // Ensure that the access manager is just called for the non-node routes. $this->accessManager->expects($this->at(0))->method('checkNamedRoute')->with('test_route', [], $this->currentUser, TRUE)->willReturn(AccessResult::allowed()); $this->accessManager->expects($this->at(1))->method('checkNamedRoute')->with('test_route', [], $this->currentUser, TRUE)->willReturn(AccessResult::neutral()); $tree = $this->defaultMenuTreeManipulators->checkAccess($tree); $this->assertEquals($node_access_result, $tree[1]->access); $this->assertEquals($node_access_result, $tree[2]->access); $this->assertEquals(AccessResult::neutral(), $tree[2]->subtree[3]->access); $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $tree[5]->access); $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), $tree[5]->subtree[6]->access); }