diff --git a/core/core.services.yml b/core/core.services.yml index 173f608..cf50781 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1022,6 +1022,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 3f6d623..7442761 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2325,12 +2325,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..0294e18 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_from_url', array($this, 'getLinkFromUrl')), ); } @@ -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 getLinkFromUrl($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/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 }} - {%- else -%} - {{ heading.text }} - {%- endif -%} - {%- endif -%} - - {{ tree }} - -{%- 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..4f6daed --- /dev/null +++ b/core/modules/system/templates/menu.html.twig @@ -0,0 +1,45 @@ +{# +/** + * @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 %} +{# @TODO Add a nav element around. #} + +{# + 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..e5bf772 --- /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_from_url("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'