/** * 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); }
/** * @covers ::build * * MenuLinkTree::build() gathers both: * 1. the tree's access cacheability: the cacheability of the access result * of checking a link in a menu tree's access. Callers can opt out of * this by MenuLinkTreeElement::access to NULL (the default) value, in * which case the menu link is always visible. Only when an * AccessResultInterface object is specified, we gather this cacheability * metadata. * This means there are three cases: * a. no access result (NULL): menu link is visible * b. AccessResultInterface object that is allowed: menu link is visible * c. AccessResultInterface object that is not allowed: menu link is * invisible, but cacheability metadata is still applicable * 2. the tree's menu links' cacheability: the cacheability of a menu link * itself, because it may be dynamic. For this reason, MenuLinkInterface * extends CacheableDependencyInterface. It allows any menu link plugin to * mark itself as uncacheable (max-age=0) or dynamic (by specifying cache * tags and/or contexts), to indicate the extent of dynamism. * This means there are two cases: * a. permanently cacheable, no cache tags, no cache contexts * b. anything else: non-permanently cacheable, and/or cache tags, and/or * cache contexts. * * Finally, there are four important shapes of trees, all of which we want to * test: * 1. the empty tree * 2. a single-element tree * 3. a single-level tree (>1 element; just 1 element is case 2) * 4. a multi-level tree * * The associated data provider aims to test the handling of both of these * types of cacheability, and for all four tree shapes, for each of the types * of values for the two types of cacheability. * * There is another level of cacheability involved when actually rendering * built menu trees (i.e. when invoking RendererInterface::render() on the * return value of MenuLinkTreeInterface::build()): the cacheability of the * generated URLs. * Fortunately, that doesn't need additional test coverage here because that * cacheability is handled at the level of the Renderer (i.e. menu.html.twig * template's link() function invocation). It also has its own test coverage. * * @see \Drupal\menu_link_content\Tests\MenuLinkContentCacheabilityBubblingTest * * @dataProvider providerTestBuildCacheability */ public function testBuildCacheability($description, $tree, $expected_build, $access, array $access_cache_contexts = []) { if ($access !== NULL) { $access->addCacheContexts($access_cache_contexts); } $build = $this->menuLinkTree->build($tree); sort($expected_build['#cache']['contexts']); $this->assertEquals($expected_build, $build, $description); }
/** * {@inheritdoc} */ public function build(array $tree) { $build = parent::build($tree); /** @var \Drupal\Core\Menu\MenuLinkInterface $first_link */ $first_link = reset($tree)->link; $menu_name = $first_link->getMenuName(); // Add a more specific theme suggestion to differentiate this rendered // menu from others. $build['#menu_name'] = $menu_name; $build['#theme'] = 'menu__mega_menu__' . strtr($menu_name, '-', '_'); return $build; }
/** * {@inheritdoc} */ protected function buildItems(array $tree, CacheableMetadata &$tree_access_cacheability, CacheableMetadata &$tree_link_cacheability) { $items = parent::buildItems($tree, $tree_access_cacheability, $tree_link_cacheability); foreach ($items as $key => $item) { // Only operate if there is a menu item that points // to the route that should get the active-trail class. if ($item['url']->getRouteName() == 'view.blog.page_1') { // Add cacheability metadata. $tree_link_cacheability->addCacheContexts(['url']); // Set the in_active_trail key to TRUE if a blog node is displayed. $node = $this->routeMatch->getParameter('node'); if ($node instanceof NodeInterface && $node->bundle() == 'blog') { $items[$key]['in_active_trail'] = TRUE; } } } return $items; }
/** * {@inheritdoc} */ public function build(array $tree, $level = 0) { if ($level == 0) { if (!$tree) { return array(); } $build = parent::build($tree, $level); /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $first_link = reset($tree)->link; // Get the menu name of the first link. $menu_name = $first_link->getMenuName(); // Add a more specific theme suggestion to differentiate this rendered // menu from others. $build['#theme'] = 'menu__toolbar__' . strtr($menu_name, '-', '_'); return $build; } else { return parent::build($tree, $level); } }
/** * @covers ::build * * MenuLinkTree::build() gathers both: * 1. the tree's access cacheability: the cacheability of the access result * of checking a link in a menu tree's access. Callers can opt out of * this by MenuLinkTreeElement::access to NULL (the default) value, in * which case the menu link is always visible. Only when an * AccessResultInterface object is specified, we gather this cacheability * metadata. * This means there are three cases: * a. no access result (NULL): menu link is visible * b. AccessResultInterface object that is allowed: menu link is visible * c. AccessResultInterface object that is not allowed: menu link is * invisible, but cacheability metadata is still applicable * 2. the tree's menu links' cacheability: the cacheability of a menu link * itself, because it may be dynamic. For this reason, MenuLinkInterface * extends CacheableDependencyInterface. It allows any menu link plugin to * mark itself as uncacheable (max-age=0) or dynamic (by specifying cache * tags and/or contexts), to indicate the extent of dynamism. * This means there are two cases: * a. permanently cacheable, no cache tags, no cache contexts * b. anything else: non-permanently cacheable, and/or cache tags, and/or * cache contexts. * * Finally, there are four important shapes of trees, all of which we want to * test: * 1. the empty tree * 2. a single-element tree * 3. a single-level tree (>1 element; just 1 element is case 2) * 4. a multi-level tree * * The associated data provider aims to test the handling of both of these * types of cacheability, and for all four tree shapes, for each of the types * of values for the two types of cacheability. * * There is another level of cacheability involved when actually rendering * built menu trees (i.e. when invoking RendererInterface::render() on the * return value of MenuLinkTreeInterface::build()): the cacheability of the * generated URLs. * Fortunately, that doesn't need additional test coverage here because that * cacheability is handled at the level of the Renderer (i.e. menu.html.twig * template's link() function invocation). It also has its own test coverage. * * @see \Drupal\menu_link_content\Tests\MenuLinkContentCacheabilityBubblingTest * * @dataProvider providerTestBuildCacheability */ public function testBuildCacheability($description, $tree, $expected_build) { $build = $this->menuLinkTree->build($tree); sort($expected_build['#cache']['contexts']); $this->assertEquals($expected_build, $build, $description); }