diff --git a/core/core.services.yml b/core/core.services.yml
index d3f91ee..a1f6f23 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1020,6 +1020,7 @@ services:
- { name: twig.extension, priority: 100 }
calls:
- [setGenerators, ['@url_generator']]
+ - [setLinkGenerator, ['@link_generator']]
# @todo Figure out what to do about debugging functions.
# @see http://drupal.org/node/1804998
twig.extension.debug:
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 51dcd9a..0780a1d 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -308,64 +308,6 @@
*/
/**
- * Implements template_preprocess_HOOK() for theme_menu_tree().
- */
-function template_preprocess_menu_tree(&$variables) {
- if (isset($variables['tree']['#heading'])) {
- $variables['heading'] = $variables['tree']['#heading'];
- $heading = &$variables['heading'];
- // Convert a string heading into an array, using a H2 tag by default.
- if (is_string($heading)) {
- $heading = array('text' => $heading);
- }
- // Merge in default array properties into $heading.
- $heading += array(
- 'level' => 'h2',
- 'attributes' => array(),
- );
- // Convert the attributes array into an Attribute object.
- $heading['attributes'] = new Attribute($heading['attributes']);
- $heading['text'] = String::checkPlain($heading['text']);
- }
-
- if (isset($variables['tree']['#attributes'])) {
- $variables['attributes'] = new Attribute($variables['tree']['#attributes']);
- }
- else {
- $variables['attributes'] = new Attribute();
- }
- if (!isset($variables['attributes']['class'])) {
- $variables['attributes']['class'] = array();
- }
- $variables['attributes']['class'][] = 'menu';
-
- $variables['tree'] = $variables['tree']['#children'];
-}
-
-/**
- * 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']);
- }
- /** @var \Drupal\Core\Url $url */
- $url = $element['#url'];
- $url->setOption('set_active_class', TRUE);
- $output = \Drupal::linkGenerator()->generateFromUrl($element['#title'], $url);
- return '
' . $output . $sub_menu . "\n";
-}
-
-/**
* Prepares variables for single local task link templates.
*
* Default template: menu-local-task.html.twig.
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index d7387a2..d1b5826 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -439,7 +439,7 @@ function _theme($hook, $variables = array()) {
$variables['theme_hook_suggestions'] = $suggestions;
// For backwards compatibility, pass 'theme_hook_suggestion' on to the
// template engine. This is only set when calling a direct suggestion like
- // '#theme' => 'menu_tree__shortcut_default' when the template exists in the
+ // '#theme' => 'menu__shortcut_default' when the template exists in the
// current theme.
if (isset($theme_hook_suggestion)) {
$variables['theme_hook_suggestion'] = $theme_hook_suggestion;
@@ -1814,22 +1814,10 @@ function template_preprocess_page(&$variables) {
// Pass the main menu and secondary menu to the template as render arrays.
if (!empty($variables['main_menu'])) {
- $variables['main_menu']['#heading'] = array(
- 'text' => t('Main menu'),
- 'attributes' => array(
- 'id' => 'links__system_main_menu',
- 'class' => array('visually-hidden'),
- ),
- );
+ $variables['main_menu']['#prefix'] = '' . t('Main menu') . '
';
}
if (!empty($variables['secondary_menu'])) {
- $variables['secondary_menu']['#heading'] = array(
- 'text' => t('Secondary menu'),
- 'attributes' => array(
- 'id' => 'links__system_secondary_menu',
- 'class' => array('visually-hidden'),
- ),
- );
+ $variables['secondary_menu']['#prefix'] = '';
}
if ($node = \Drupal::routeMatch()->getParameter('node')) {
@@ -2327,12 +2315,9 @@ function drupal_common_theme() {
'template' => 'pager',
),
// From menu.inc.
- 'menu_link' => array(
- 'render element' => 'element',
- ),
- 'menu_tree' => array(
- 'render element' => 'tree',
- 'template' => 'menu-tree',
+ 'menu' => array(
+ 'variables' => array('items' => array(), 'attributes' => array()),
+ 'template' => 'menu',
),
'menu_local_task' => array(
'render element' => 'element',
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTree.php b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
index 25dede7..c015b99 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkTree.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
@@ -12,6 +12,7 @@
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\Template\Attribute;
/**
* Implements the loading, transforming and rendering of menu link trees.
@@ -197,8 +198,8 @@ public function transform(array $tree, array $manipulators) {
/**
* {@inheritdoc}
*/
- public function build(array $tree) {
- $build = array();
+ public function build(array $tree, $level = 0) {
+ $items = array();
foreach ($tree as $data) {
$class = array();
@@ -225,31 +226,41 @@ public function build(array $tree) {
}
// Allow menu-specific theme overrides.
- $element['#theme'] = 'menu_link__' . strtr($link->getMenuName(), '-', '_');
- $element['#attributes']['class'] = $class;
- $element['#title'] = $link->getTitle();
- $element['#url'] = $link->getUrlObject();
- $element['#below'] = $data->subtree ? $this->build($data->subtree) : array();
+ $element = array();
+ $element['attributes'] = new Attribute();
+ $element['attributes']['class'] = $class;
+ $element['title'] = $link->getTitle();
+ $element['url'] = $link->getUrlObject();
+ $element['url']->setOption('set_active_class', TRUE);
+ $element['below'] = $data->subtree ? $this->build($data->subtree, $level + 1) : array();
if (isset($data->options)) {
- $element['#url']->setOptions(NestedArray::mergeDeep($element['#url']->getOptions(), $data->options));
+ $element['url']->setOptions(NestedArray::mergeDeep($element['url']->getOptions(), $data->options));
}
- $element['#original_link'] = $link;
+ $element['original_link'] = $link;
// Index using the link's unique ID.
- $build[$link->getPluginId()] = $element;
+ $items[$link->getPluginId()] = $element;
}
- if ($build) {
+
+ if (!$items) {
+ return array();
+ }
+ elseif ($level == 0) {
+ $build = array();
// Make sure drupal_render() does not re-order the links.
$build['#sorted'] = TRUE;
// Get the menu name from the last link.
$menu_name = $link->getMenuName();
// 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'] = $items;
// Set cache tag.
$build['#cache']['tags']['menu'][$menu_name] = $menu_name;
+ return $build;
+ }
+ else {
+ return $items;
}
-
- return $build;
}
/**
diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php
index cf3500e..e71d9d0 100644
--- a/core/lib/Drupal/Core/Template/TwigExtension.php
+++ b/core/lib/Drupal/Core/Template/TwigExtension.php
@@ -13,6 +13,8 @@
namespace Drupal\Core\Template;
use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Utility\LinkGeneratorInterface;
/**
* A class providing Drupal Twig extensions.
@@ -31,6 +33,13 @@ class TwigExtension extends \Twig_Extension {
protected $urlGenerator;
/**
+ * The link generator.
+ *
+ * @var \Drupal\Core\Utility\LinkGeneratorInterface
+ */
+ protected $linkGenerator;
+
+ /**
* Constructs \Drupal\Core\Template\TwigExtension.
*
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
@@ -42,6 +51,19 @@ public function setGenerators(UrlGeneratorInterface $url_generator) {
}
/**
+ * Sets the link generator.
+ *
+ * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
+ * The link generator.
+ *
+ * @return $this
+ */
+ public function setLinkGenerator(LinkGeneratorInterface $link_generator) {
+ $this->linkGenerator = $link_generator;
+ return $this;
+ }
+
+ /**
* {@inheritdoc}
*/
public function getFunctions() {
@@ -53,6 +75,7 @@ public function getFunctions() {
new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
new \Twig_SimpleFunction('url_from_path', array($this, 'getUrlFromPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
+ new \Twig_SimpleFunction('link', array($this, 'getLink')),
);
}
@@ -175,6 +198,27 @@ public function getUrlFromPath($path, $options = array()) {
}
/**
+ * Gets a rendered link from an url object.
+ *
+ * @param string $text
+ * The link text for the anchor tag as a translated string.
+ * @param \Drupal\Core\Url|string $url
+ * The URL object or string used for the link.
+ *
+ * @return string
+ * An HTML string containing a link to the given url.
+ */
+ public function getLink($text, $url) {
+ if ($url instanceof Url) {
+ return $this->linkGenerator->generateFromUrl($text, $url);
+ }
+ else {
+ // @todo Convert once https://www.drupal.org/node/2306901 is in
+ return l($text, $url);
+ }
+ }
+
+ /**
* Determines at compile time whether the generated URL will be safe.
*
* Saves the unneeded automatic escaping for performance reasons.
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index 184b78a..ca8f188 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -348,7 +348,7 @@ th.checkbox {
}
/**
- * Markup generated by theme_menu_tree().
+ * Markup generated by menu.html.twig.
*/
ul.menu {
list-style: none outside;
diff --git a/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php b/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
index 1016c04..8695f0e 100644
--- a/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
+++ b/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
@@ -227,7 +227,9 @@ public function testConfigLevelDepth() {
$no_active_trail_expectations['level_2_and_beyond'] = $no_active_trail_expectations['level_2_only'];
$no_active_trail_expectations['level_3_and_beyond'] = [];
foreach ($blocks as $id => $block) {
- $this->assertIdentical($no_active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($block->build()), format_string('Menu block %id with no active trail renders the expected tree.', ['%id' => $id]));
+ $block_build = $block->build();
+ $items = isset($block_build['#items']) ? $block_build['#items'] : [];
+ $this->assertIdentical($no_active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with no active trail renders the expected tree.', ['%id' => $id]));
}
// Scenario 2: test all block instances when there's an active trail.
@@ -273,7 +275,9 @@ public function testConfigLevelDepth() {
];
$active_trail_expectations['level_3_and_beyond'] = $active_trail_expectations['level_3_only'];
foreach ($blocks as $id => $block) {
- $this->assertIdentical($active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($block->build()), format_string('Menu block %id with an active trail renders the expected tree.', ['%id' => $id]));
+ $block_build = $block->build();
+ $items = isset($block_build['#items']) ? $block_build['#items'] : [];
+ $this->assertIdentical($active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with an active trail renders the expected tree.', ['%id' => $id]));
}
}
@@ -292,8 +296,8 @@ protected function convertBuiltMenuToIdTree(array $build) {
$level = [];
foreach (Element::children($build) as $id) {
$level[$id] = [];
- if (isset($build[$id]['#below'])) {
- $level[$id] = $this->convertBuiltMenuToIdTree($build[$id]['#below']);
+ if (isset($build[$id]['below'])) {
+ $level[$id] = $this->convertBuiltMenuToIdTree($build[$id]['below']);
}
}
return $level;
diff --git a/core/modules/system/src/Tests/Theme/EngineTwigTest.php b/core/modules/system/src/Tests/Theme/EngineTwigTest.php
index 13f947e..a98bb3c 100644
--- a/core/modules/system/src/Tests/Theme/EngineTwigTest.php
+++ b/core/modules/system/src/Tests/Theme/EngineTwigTest.php
@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Theme;
+use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
@@ -63,4 +64,23 @@ public function testTwigUrlGenerator() {
}
}
+ /**
+ * Tests the link_generator twig functions.
+ */
+ public function testTwigLinkGenerator() {
+ $this->drupalGet('twig-theme-test/link-generator');
+
+ $link_generator = $this->container->get('link_generator');
+
+ $expected = [
+ 'link via the linkgenerator: ' . $link_generator->generateFromUrl('register', new Url('user.register')),
+ ];
+
+ $content = $this->getRawContent();
+ $this->assertFalse(empty($content), 'Page content is not empty');
+ foreach ($expected as $string) {
+ $this->assertRaw('' . $string . '
');
+ }
+ }
+
}
diff --git a/core/modules/system/templates/menu-tree.html.twig b/core/modules/system/templates/menu-tree.html.twig
deleted file mode 100644
index 03acca6..0000000
--- a/core/modules/system/templates/menu-tree.html.twig
+++ /dev/null
@@ -1,40 +0,0 @@
-{#
-/**
- * @file
- * Default theme implementation for a menu tree.
- *
- * Available variables:
- * - attributes: Attributes for the UL containing the tree of links.
- * - tree: Menu tree to be output.
- * - heading: (optional) A heading to precede the links.
- * - text: The heading text.
- * - level: The heading level (e.g. 'h2', 'h3').
- * - attributes: (optional) A keyed list of attributes for the heading.
- * If the heading is a string, it will be used as the text of the heading and
- * the level will default to 'h2'.
- *
- * Headings should be used on navigation menus and any list of links that
- * consistently appears on multiple pages. To make the heading invisible use
- * the 'visually-hidden' CSS class. Do not use 'display:none', which
- * removes it from screen-readers and assistive technology. Headings allow
- * screen-reader and keyboard only users to navigate to or skip the links.
- * See http://juicystudio.com/article/screen-readers-display-none.php and
- * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
- *
- * @see template_preprocess_menu_tree()
- *
- * @ingroup themeable
- */
-#}
-{% if tree -%}
- {%- if heading -%}
- {%- if heading.level -%}
- <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}{{ heading.level }}>
- {%- else -%}
- {{ heading.text }}
- {%- endif -%}
- {%- endif -%}
-
-{%- endif %}
diff --git a/core/modules/system/templates/menu.html.twig b/core/modules/system/templates/menu.html.twig
new file mode 100644
index 0000000..f01ce84
--- /dev/null
+++ b/core/modules/system/templates/menu.html.twig
@@ -0,0 +1,44 @@
+{#
+/**
+ * @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.
+ * - url: The menu link url, instance of \Drupal\Core\Url
+ * - localized_options: Menu link localized options.
+ *
+ * @ingroup themeable
+ */
+#}
+{% import _self as menus %}
+
+{#
+ We call a macro which calls itself to render the full tree.
+ @see http://twig.sensiolabs.org/doc/tags/macro.html
+#}
+{{ menus.menu_links(items, attributes, 0) }}
+
+{% macro menu_links(items, attributes, menu_level) %}
+ {% import _self as menus %}
+ {% if items %}
+ {% if menu_level == 0 %}
+
+ {% else %}
+
+ {% endif %}
+{% endmacro %}
diff --git a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php
index 228b839..9441a50 100644
--- a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php
+++ b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php
@@ -7,6 +7,8 @@
namespace Drupal\twig_theme_test;
+use Drupal\Core\Url;
+
/**
* Controller routines for Twig theme test routes.
*/
@@ -37,4 +39,14 @@ public function urlGeneratorRender() {
);
}
+ /**
+ * Renders for testing link_generator functions in a Twig template.
+ */
+ public function linkGeneratorRender() {
+ return array(
+ '#theme' => 'twig_theme_test_link_generator',
+ '#test_url' => new Url('user.register'),
+ );
+ }
+
}
diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig
new file mode 100644
index 0000000..a365859
--- /dev/null
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig
@@ -0,0 +1 @@
+link via the linkgenerator: {{ link("register", test_url) }}
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
index c8db9d6..f023cb1 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
@@ -31,6 +31,10 @@ function twig_theme_test_theme($existing, $type, $theme, $path) {
'variables' => array(),
'template' => 'twig_theme_test.url_generator',
);
+ $items['twig_theme_test_link_generator'] = array(
+ 'variables' => array('test_url' => NULL),
+ 'template' => 'twig_theme_test.link_generator',
+ );
return $items;
}
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
index 75b9bbb..d631898 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
@@ -18,3 +18,10 @@ twig_theme_test_url_generator:
_content: '\Drupal\twig_theme_test\TwigThemeTestController::urlGeneratorRender'
requirements:
_access: 'TRUE'
+
+twig_theme_test_link_generator:
+ path: '/twig-theme-test/link-generator'
+ defaults:
+ _content: '\Drupal\twig_theme_test\TwigThemeTestController::linkGeneratorRender'
+ requirements:
+ _access: 'TRUE'
diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme
index 27f8973..99c3544 100644
--- a/core/themes/bartik/bartik.theme
+++ b/core/themes/bartik/bartik.theme
@@ -135,22 +135,13 @@ function bartik_preprocess_block(&$variables) {
}
/**
- * Implements hook_preprocess_HOOK() for menu-tree.html.twig.
- *
- * @see template_preprocess_menu_tree()
+ * Implements hook_preprocess_HOOK() for menu.html.twig.
*/
-function bartik_preprocess_menu_tree(&$variables) {
+function bartik_preprocess_menu(&$variables) {
$variables['attributes']['class'][] = 'clearfix';
}
/**
- * Implements THEME_menu_tree__MENUNAME().
- */
-function bartik_menu_tree__shortcut_default($variables) {
- return '';
-}
-
-/**
* Implements hook_preprocess_HOOK() for field.html.twig.
*
* @see template_preprocess_field()