diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 7a6828c..1d133fd 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 b62ad74..5c24892 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2627,11 +2627,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/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 adbf85d..c9d6005 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\State\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\State\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;
}
/**
@@ -361,7 +373,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'] : '';
@@ -395,26 +407,41 @@ 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) {
+
+ if (!$build) {
+ return array();
+ }
+ 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 bd77a45..c198c8d 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
@@ -10,6 +10,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\Language;
+use Drupal\Core\Render\Element;
use Drupal\menu_link\MenuTree;
use Drupal\Tests\UnitTestCase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -87,6 +88,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
@@ -131,8 +139,9 @@ protected function setUp() {
->disableOriginalConstructor()
->getMock();
$this->state = $this->getMock('Drupal\Core\State\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);
}
/**
@@ -419,9 +428,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');
}
/**
@@ -430,36 +440,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..29b2603
--- /dev/null
+++ b/core/modules/system/templates/menu.html.twig
@@ -0,0 +1,40 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a menu.
+ *
+ * Available variables:
+ * - menu_name: The machine name of the menu.
+ * - items: A nested list of menu items. Each menu item contains:
+ * - attributes: HTML attributes for the menu item.
+ * - below: The menu item child items.
+ * - title: The menu link title.
+ * - href: The menu link url.
+ * - localized_options: Menu link localized options.
+ *
+ * @ingroup themeable
+ */
+#}
+{% import _self as menus %}
+
+
+{% macro menu_links(items, menu_level) %}
+ {% import _self as menus %}
+ {% if items %}
+
+ {% endif %}
+{% endmacro %}
diff --git a/core/modules/toolbar/css/toolbar.menu.css b/core/modules/toolbar/css/toolbar.menu.css
index 5e70a02..bf1b0a1 100644
--- a/core/modules/toolbar/css/toolbar.menu.css
+++ b/core/modules/toolbar/css/toolbar.menu.css
@@ -1,8 +1,8 @@
/**
* @file toolbar.menu.css
*/
-.toolbar .menu,
-[dir="rtl"] .toolbar .menu {
+.toolbar ul.menu,
+[dir="rtl"] .toolbar ul.menu {
list-style: none;
margin: 0;
padding: 0;
@@ -13,9 +13,9 @@
position: relative;
width: auto;
}
-.toolbar .toolbar-tray-horizontal .menu .toolbar-handle,
-.toolbar .toolbar-tray-horizontal .menu ul,
-.toolbar .toolbar-tray-vertical .menu ul {
+.toolbar .toolbar-tray-horizontal ul.menu .toolbar-handle,
+.toolbar .toolbar-tray-horizontal ul.menu ul,
+.toolbar .toolbar-tray-vertical ul.menu ul {
display: none;
}
.toolbar .toolbar-tray-vertical li.open > ul {
diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js
index 909708d..a709682 100644
--- a/core/modules/toolbar/js/toolbar.js
+++ b/core/modules/toolbar/js/toolbar.js
@@ -68,7 +68,7 @@
// Render collapsible menus.
var menuModel = Drupal.toolbar.models.menuModel = new Drupal.toolbar.MenuModel();
Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView({
- el: $(this).find('.toolbar-menu-administration').get(0),
+ el: $(this).find('.toolbar-menu-administration > nav').get(0),
model: menuModel,
strings: options.strings
});
diff --git a/core/modules/toolbar/js/toolbar.menu.js b/core/modules/toolbar/js/toolbar.menu.js
index 1100278..fd79188 100644
--- a/core/modules/toolbar/js/toolbar.menu.js
+++ b/core/modules/toolbar/js/toolbar.menu.js
@@ -80,7 +80,7 @@
// Add a handle to each list item if it has a menu.
$menu.find('li').each(function (index, element) {
var $item = $(element);
- if ($item.children('ul.menu').length) {
+ if ($item.children('nav ul.menu').length) {
var $box = $item.children('.toolbar-box');
options.text = Drupal.t('@label', {'@label': $box.find('a').text()});
$item.children('.toolbar-box')