diff --git a/core/includes/menu.inc b/core/includes/menu.inc index d8dab77..bb8ea49 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -242,48 +242,6 @@ function _menu_link_translate(&$item) { } /** - * Implements template_preprocess_HOOK() for theme_menu_tree(). - */ -function template_preprocess_menu_tree(&$variables) { - $variables['tree'] = $variables['tree']['#children']; -} - -/** - * Returns HTML for a wrapper for a menu sub-tree. - * - * @param $variables - * An associative array containing: - * - tree: An HTML string containing the tree's items. - * - * @see template_preprocess_menu_tree() - * @ingroup themeable - */ -function theme_menu_tree($variables) { - return ''; -} - -/** - * Returns HTML for a menu link and submenu. - * - * @param $variables - * An associative array containing: - * - element: Structured array data for a menu link. - * - * @ingroup themeable - */ -function theme_menu_link(array $variables) { - $element = $variables['element']; - $sub_menu = ''; - - if ($element['#below']) { - $sub_menu = drupal_render($element['#below']); - } - $element['#localized_options']['set_active_class'] = TRUE; - $output = l($element['#title'], $element['#href'], $element['#localized_options']); - return '' . $output . $sub_menu . "\n"; -} - -/** * Returns HTML for a single local task link. * * @param $variables diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 52ce316..c380563 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2692,11 +2692,9 @@ function drupal_common_theme() { 'template' => 'pager', ), // From menu.inc. - 'menu_link' => array( - 'render element' => 'element', - ), - 'menu_tree' => array( - 'render element' => 'tree', + 'menu' => array( + 'variables' => array('items' => []), + 'template' => 'menu', ), 'menu_local_task' => array( 'render element' => 'element', diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index 4c431dc..13094b7 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -53,6 +53,9 @@ public function __construct(Connection $connection, $bin) { * Implements Drupal\Core\Cache\CacheBackendInterface::get(). */ public function get($cid, $allow_invalid = FALSE) { + if ($this->bin == 'render') { + return NULL; + } $cids = array($cid); $cache = $this->getMultiple($cids, $allow_invalid); return reset($cache); diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 4cb31ca..620bf6e 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -29,6 +29,7 @@ public function getFunctions() { new \Twig_SimpleFunction('url', 'url'), // This function will receive a renderable array, if an array is detected. new \Twig_SimpleFunction('render_var', 'twig_render_var'), + new \Twig_SimpleFunction('link_path', 'l'), ); } diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockHtmlTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockHtmlTest.php index 63579b9..a7b3ac0 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockHtmlTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockHtmlTest.php @@ -54,7 +54,7 @@ function testHtml() { $this->assertFieldByXPath('//div[@id="block-test-html-block" and @data-custom-attribute="foo"]', NULL, 'HTML ID and attributes for test block are valid and on the same DOM element.'); // Ensure expected markup for a menu block. - $elements = $this->xpath('//div[contains(@class, :div-class)]/div/ul[contains(@class, :ul-class)]/li', array(':div-class' => 'block-system', ':ul-class' => 'menu')); + $elements = $this->xpath('//div[contains(@class, :div-class)]/div/nav/ul[contains(@class, :ul-class)]/li', array(':div-class' => 'block-system', ':ul-class' => 'menu')); $this->assertTrue(!empty($elements), 'The proper block markup was found.'); } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php index 24a531f..89e6115 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php @@ -14,6 +14,8 @@ use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\KeyValueStore\StateInterface; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Routing\UrlGeneratorInterface; +use Drupal\Core\Template\Attribute; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -73,6 +75,13 @@ class MenuTree implements MenuTreeInterface { protected $state; /** + * The url generator + * + * @var \Drupal\Core\Routing\UrlGeneratorInterface + */ + protected $urlGenerator; + + /** * A list of active trail paths keyed by $menu_name. * * @var array @@ -133,8 +142,10 @@ class MenuTree implements MenuTreeInterface { * The entity query factory. * @param \Drupal\Core\KeyValueStore\StateInterface $state * The state. + * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator + * The url generator. */ - public function __construct(Connection $database, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, QueryFactory $entity_query_factory, StateInterface $state) { + public function __construct(Connection $database, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, QueryFactory $entity_query_factory, StateInterface $state, UrlGeneratorInterface $url_generator) { $this->database = $database; $this->cache = $cache_backend; $this->languageManager = $language_manager; @@ -142,6 +153,7 @@ public function __construct(Connection $database, CacheBackendInterface $cache_b $this->menuLinkStorage = $entity_manager->getStorage('menu_link'); $this->queryFactory = $entity_query_factory; $this->state = $state; + $this->urlGenerator = $url_generator; } /** @@ -362,7 +374,7 @@ public function renderMenu($menu_name) { /** * {@inheritdoc} */ - public function renderTree($tree) { + public function renderTree($tree, $level = 0) { $build = array(); $items = array(); $menu_name = $tree ? end($tree)['link']['menu_name'] : ''; @@ -396,26 +408,42 @@ public function renderTree($tree) { } // Allow menu-specific theme overrides. - $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_'); - $element['#attributes']['class'] = $class; - $element['#title'] = $data['link']['title']; + $element = array(); + $element['attributes'] = new Attribute(); + $element['attributes']['class'] = $class; + $element['title'] = $data['link']['title']; // @todo Use route name and parameters to generate the link path, unless // it is external. - $element['#href'] = $data['link']['link_path']; - $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array(); - $element['#below'] = $data['below'] ? $this->renderTree($data['below']) : $data['below']; - $element['#original_link'] = $data['link']; + if (isset($data['link']['route_name'])) { + $path = $this->urlGenerator->getPathFromRoute($data['link']['route_name'], isset($data['link']['route_parameters']) ? $data['link']['route_parameters'] : array()); + } + else { + $path = $data['link']['link_path']; + } + $element['href'] = $path; + $element['mlid'] = $data['link']['mlid']; + $element['localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array(); + $element['localized_options']['set_active_class'] = TRUE; + $element['below'] = $data['below'] ? $this->renderTree($data['below'], $level + 1) : $data['below']; + $element['original_link'] = $data['link']; // Index using the link's unique mlid. $build[$data['link']['mlid']] = $element; + + } + + if (!$build) { + return array(); } - if ($build) { + elseif ($level == 0) { + $build_copy = $build; + $build = array(); // Make sure drupal_render() does not re-order the links. $build['#sorted'] = TRUE; // Add the theme wrapper for outer markup. // Allow menu-specific theme overrides. - $build['#theme_wrappers'][] = 'menu_tree__' . strtr($menu_name, '-', '_'); + $build['#theme'] = 'menu__' . strtr($menu_name, '-', '_'); + $build['#items'] = $build_copy; // Set cache tag. - $menu_name = $data['link']['menu_name']; $build['#cache']['tags']['menu'][$menu_name] = $menu_name; } diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml index 88f5037..400cf21 100644 --- a/core/modules/menu_link/menu_link.services.yml +++ b/core/modules/menu_link/menu_link.services.yml @@ -1,7 +1,7 @@ services: menu_link.tree: class: Drupal\menu_link\MenuTree - arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state'] + arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state', '@url_generator'] menu_link.static: class: Drupal\menu_link\StaticMenuLinks arguments: ['@module_handler'] diff --git a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php index 50bebc6..d8ce0f8 100644 --- a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php +++ b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php @@ -7,7 +7,8 @@ namespace Drupal\menu_link\Tests { -use Drupal\menu_link\MenuTree; + use Drupal\Core\Render\Element; + use Drupal\menu_link\MenuTree; use Drupal\Tests\UnitTestCase; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; @@ -80,6 +81,13 @@ class MenuTreeTest extends UnitTestCase { protected $state; /** + * The mocked url generator. + * + * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $urlGenerator; + + /** * Stores some default values for a menu link. * * @var array @@ -124,8 +132,9 @@ protected function setUp() { ->disableOriginalConstructor() ->getMock(); $this->state = $this->getMock('Drupal\Core\KeyValueStore\StateInterface'); + $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); - $this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state); + $this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state, $this->urlGenerator); } /** @@ -340,9 +349,10 @@ public function testOutputWithSingleLevel() { $output = $this->menuTree->renderTree($tree); // Validate that the - in main-menu is changed into an underscore - $this->assertEquals($output['1']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link'); - $this->assertEquals($output['2']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link'); - $this->assertEquals($output['#theme_wrappers'][0], 'menu_tree__main_menu', 'Hyphen is changed to an underscore on menu_tree wrapper'); + $this->assertEquals(1, $output['#items']['1']['mlid']); + $this->assertEquals(2, $output['#items']['2']['mlid']); + $this->assertCount(2, $output['#items']); + $this->assertEquals($output['#theme'], 'menu__main_menu', 'Hyphen is changed to an underscore on menu theme hook'); } /** @@ -351,36 +361,43 @@ public function testOutputWithSingleLevel() { * @covers ::renderTree */ public function testOutputWithComplexData() { + for ($i = 1; $i <= 5; $i++) { + $this->urlGenerator->expects($this->at($i - 1)) + ->method('getPathFromRoute') + ->with('test_route_' . $i) + ->will($this->returnValue('test/route_' . $i)); + } + $tree = array( '1'=> array( - 'link' => array('mlid' => 1, 'has_children' => 1, 'title' => 'Item 1', 'link_path' => 'a') + $this->defaultMenuLink, + 'link' => array('mlid' => 1, 'has_children' => 1, 'title' => 'Item 1', 'route_name' => 'test_route_1') + $this->defaultMenuLink, 'below' => array( - '2' => array('link' => array('mlid' => 2, 'title' => 'Item 2', 'link_path' => 'a/b') + $this->defaultMenuLink, + '2' => array('link' => array('mlid' => 2, 'title' => 'Item 2', 'route_name' => 'test_route_2') + $this->defaultMenuLink, 'below' => array( - '3' => array('link' => array('mlid' => 3, 'title' => 'Item 3', 'in_active_trail' => 0, 'link_path' => 'a/b/c') + $this->defaultMenuLink, + '3' => array('link' => array('mlid' => 3, 'title' => 'Item 3', 'in_active_trail' => 0, 'route_name' => 'test_route_3') + $this->defaultMenuLink, 'below' => array()), - '4' => array('link' => array('mlid' => 4, 'title' => 'Item 4', 'in_active_trail' => 0, 'link_path' => 'a/b/d') + $this->defaultMenuLink, + '4' => array('link' => array('mlid' => 4, 'title' => 'Item 4', 'in_active_trail' => 0, 'route_name' => 'test_route_4') + $this->defaultMenuLink, 'below' => array()) ) ) ) ), - '5' => array('link' => array('mlid' => 5, 'hidden' => 1, 'title' => 'Item 5', 'link_path' => 'e') + $this->defaultMenuLink, 'below' => array()), - '6' => array('link' => array('mlid' => 6, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'link_path' => 'f') + $this->defaultMenuLink, 'below' => array()), - '7' => array('link' => array('mlid' => 7, 'title' => 'Item 7', 'in_active_trail' => 0, 'link_path' => 'g') + $this->defaultMenuLink, 'below' => array()) + '5' => array('link' => array('mlid' => 5, 'hidden' => 1, 'title' => 'Item 5', 'route_name' => 'test_route_hidden') + $this->defaultMenuLink, 'below' => array()), + '6' => array('link' => array('mlid' => 6, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'route_name' => 'test_route_access_denied') + $this->defaultMenuLink, 'below' => array()), + '7' => array('link' => array('mlid' => 7, 'title' => 'Item 7', 'in_active_trail' => 0, 'route_name' => 'test_route_5') + $this->defaultMenuLink, 'below' => array()) ); $output = $this->menuTree->renderTree($tree); // Looking for child items in the data - $this->assertEquals( $output['1']['#below']['2']['#href'], 'a/b', 'Checking the href on a child item'); - $this->assertTrue(in_array('active-trail', $output['1']['#below']['2']['#attributes']['class']), 'Checking the active trail class'); + $this->assertEquals($output['#items']['1']['below']['2']['href'], 'test/route_2', 'Checking the href on a child item'); + $this->assertTrue(in_array('active-trail', $output['#items']['1']['below']['2']['attributes']['class']->value()), 'Checking the active trail class'); // Validate that the hidden and no access items are missing - $this->assertFalse(isset($output['5']), 'Hidden item should be missing'); - $this->assertFalse(isset($output['6']), 'False access should be missing'); + $this->assertFalse(isset($output['#items']['5']), 'Hidden item should be missing'); + $this->assertFalse(isset($output['#items']['6']), 'False access should be missing'); // Item 7 is after a couple hidden items. Just to make sure that 5 and 6 are // skipped and 7 still included. - $this->assertTrue(isset($output['7']), 'Item after hidden items is present'); + $this->assertTrue(isset($output['#items']['7']), 'Item after hidden items is present'); } /** diff --git a/core/modules/system/templates/menu.html.twig b/core/modules/system/templates/menu.html.twig new file mode 100644 index 0000000..0e96c3f --- /dev/null +++ b/core/modules/system/templates/menu.html.twig @@ -0,0 +1,25 @@ +{# +/** + * @file + * Default theme implementation to display a menu. +#} +{% import _self as menus %} + + +{% macro menu_links(items, menu_level) %} + {% import _self as menus %} + {% if items %} + + {% endif %} +{% endmacro %}