Issue #2207893 by dawehner, pwolanin, jessebeach, Boobaa: Convert menu tree building to a service.

8.0.x
catch 2014-03-26 20:56:01 +01:00
parent 1a51606bee
commit 4a57034dd9
15 changed files with 1256 additions and 869 deletions

View File

@ -321,594 +321,6 @@ function _menu_link_translate(&$item) {
}
}
/**
* Renders a menu tree based on the current path.
*
* @param $menu_name
* The name of the menu.
*
* @return
* A structured array representing the specified menu on the current page, to
* be rendered by drupal_render().
*/
function menu_tree($menu_name) {
$menu_output = &drupal_static(__FUNCTION__, array());
if (!isset($menu_output[$menu_name])) {
$tree = menu_tree_page_data($menu_name);
$menu_output[$menu_name] = menu_tree_output($tree);
}
return $menu_output[$menu_name];
}
/**
* Returns an output structure for rendering a menu tree.
*
* The menu item's LI element is given one of the following classes:
* - expanded: The menu item is showing its submenu.
* - collapsed: The menu item has a submenu which is not shown.
* - leaf: The menu item has no submenu.
*
* @param $tree
* A data structure representing the tree as returned from menu_tree_data.
*
* @return
* A structured array to be rendered by drupal_render().
*/
function menu_tree_output($tree) {
$build = array();
$items = array();
// Pull out just the menu links we are going to render so that we
// get an accurate count for the first/last classes.
foreach ($tree as $data) {
if ($data['link']['access'] && !$data['link']['hidden']) {
$items[] = $data;
}
}
foreach ($items as $data) {
$class = array();
// Set a class for the <li>-tag. Since $data['below'] may contain local
// tasks, only set 'expanded' class if the link also has children within
// the current menu.
if ($data['link']['has_children'] && $data['below']) {
$class[] = 'expanded';
}
elseif ($data['link']['has_children']) {
$class[] = 'collapsed';
}
else {
$class[] = 'leaf';
}
// Set a class if the link is in the active trail.
if ($data['link']['in_active_trail']) {
$class[] = 'active-trail';
$data['link']['localized_options']['attributes']['class'][] = 'active-trail';
}
// Allow menu-specific theme overrides.
$element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
$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'] ? menu_tree_output($data['below']) : $data['below'];
$element['#original_link'] = $data['link'];
// Index using the link's unique mlid.
$build[$data['link']['mlid']] = $element;
}
if ($build) {
// 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($data['link']['menu_name'], '-', '_');
// Set cache tag.
$menu_name = $data['link']['menu_name'];
$build['#cache']['tags']['menu'][$menu_name] = $menu_name;
}
return $build;
}
/**
* Gets the data structure representing a named menu tree.
*
* Since this can be the full tree including hidden items, the data returned
* may be used for generating an an admin interface or a select.
*
* @param $menu_name
* The named menu links to return
* @param $link
* A fully loaded menu link, or NULL. If a link is supplied, only the
* path to root will be included in the returned tree - as if this link
* represented the current page in a visible menu.
* @param $max_depth
* Optional maximum depth of links to retrieve. Typically useful if only one
* or two levels of a sub tree are needed in conjunction with a non-NULL
* $link, in which case $max_depth should be greater than $link['depth'].
*
* @return
* An tree of menu links in an array, in the order they should be rendered.
*/
function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
$tree = &drupal_static(__FUNCTION__, array());
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Use $mlid as a flag for whether the data being loaded is for the whole tree.
$mlid = isset($link['mlid']) ? $link['mlid'] : 0;
// Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
$cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->id . ':' . (int) $max_depth;
if (!isset($tree[$cid])) {
// If the static variable doesn't have the data, check {cache_data}.
$cache = \Drupal::cache('data')->get($cid);
if ($cache && isset($cache->data)) {
// If the cache entry exists, it contains the parameters for
// menu_build_tree().
$tree_parameters = $cache->data;
}
// If the tree data was not in the cache, build $tree_parameters.
if (!isset($tree_parameters)) {
$tree_parameters = array(
'min_depth' => 1,
'max_depth' => $max_depth,
);
if ($mlid) {
// The tree is for a single item, so we need to match the values in its
// p columns and 0 (the top level) with the plid values of other links.
$parents = array(0);
for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
if (!empty($link["p$i"])) {
$parents[] = $link["p$i"];
}
}
$tree_parameters['expanded'] = $parents;
$tree_parameters['active_trail'] = $parents;
$tree_parameters['active_trail'][] = $mlid;
}
// Cache the tree building parameters using the page-specific cid.
\Drupal::cache('data')->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
}
// Build the tree using the parameters; the resulting tree will be cached
// by _menu_build_tree()).
$tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
}
return $tree[$cid];
}
/**
* Sets the path for determining the active trail of the specified menu tree.
*
* This path will also affect the breadcrumbs under some circumstances.
* Breadcrumbs are built using the preferred link returned by
* menu_link_get_preferred(). If the preferred link is inside one of the menus
* specified in calls to menu_tree_set_path(), the preferred link will be
* overridden by the corresponding path returned by menu_tree_get_path().
*
* Setting this path does not affect the main content; for that use
* menu_set_active_item() instead.
*
* @param $menu_name
* The name of the affected menu tree.
* @param $path
* The path to use when finding the active trail.
*/
function menu_tree_set_path($menu_name, $path = NULL) {
$paths = &drupal_static(__FUNCTION__);
if (isset($path)) {
$paths[$menu_name] = $path;
}
return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL;
}
/**
* Gets the path for determining the active trail of the specified menu tree.
*
* @param $menu_name
* The menu name of the requested tree.
*
* @return
* A string containing the path. If no path has been specified with
* menu_tree_set_path(), NULL is returned.
*/
function menu_tree_get_path($menu_name) {
return menu_tree_set_path($menu_name);
}
/**
* Gets the data structure for a named menu tree, based on the current page.
*
* The tree order is maintained by storing each parent in an individual
* field, see http://drupal.org/node/141866 for more.
*
* @param $menu_name
* The named menu links to return.
* @param $max_depth
* (optional) The maximum depth of links to retrieve.
* @param $only_active_trail
* (optional) Whether to only return the links in the active trail (TRUE)
* instead of all links on every level of the menu link tree (FALSE). Defaults
* to FALSE.
*
* @return
* An array of menu links, in the order they should be rendered. The array
* is a list of associative arrays -- these have two keys, link and below.
* link is a menu item, ready for theming as a link. Below represents the
* submenu below the link if there is one, and it is a subtree that has the
* same structure described for the top-level array.
*/
function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
$tree = &drupal_static(__FUNCTION__, array());
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Check if the active trail has been overridden for this menu tree.
$active_path = menu_tree_get_path($menu_name);
// Load the request corresponding to the current page.
$request = \Drupal::request();
$system_path = NULL;
if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
// @todo https://drupal.org/node/2068471 is adding support so we can tell
// if this is called on a 404/403 page.
$system_path = $request->attributes->get('_system_path');
$page_not_403 = 1;
}
if (isset($system_path)) {
if (isset($max_depth)) {
$max_depth = min($max_depth, MENU_MAX_DEPTH);
}
// Generate a cache ID (cid) specific for this page.
$cid = 'links:' . $menu_name . ':page:' . $system_path . ':' . $language_interface->id . ':' . $page_not_403 . ':' . (int) $max_depth;
// If we are asked for the active trail only, and $menu_name has not been
// built and cached for this page yet, then this likely means that it
// won't be built anymore, as this function is invoked from
// template_preprocess_page(). So in order to not build a giant menu tree
// that needs to be checked for access on all levels, we simply check
// whether we have the menu already in cache, or otherwise, build a minimum
// tree containing the active trail only.
// @see menu_set_active_trail()
if (!isset($tree[$cid]) && $only_active_trail) {
$cid .= ':trail';
}
if (!isset($tree[$cid])) {
// If the static variable doesn't have the data, check {cache_data}.
$cache = \Drupal::cache('data')->get($cid);
if ($cache && isset($cache->data)) {
// If the cache entry exists, it contains the parameters for
// menu_build_tree().
$tree_parameters = $cache->data;
}
// If the tree data was not in the cache, build $tree_parameters.
if (!isset($tree_parameters)) {
$tree_parameters = array(
'min_depth' => 1,
'max_depth' => $max_depth,
);
// Parent mlids; used both as key and value to ensure uniqueness.
// We always want all the top-level links with plid == 0.
$active_trail = array(0 => 0);
// If this page is accessible to the current user, build the tree
// parameters accordingly.
if ($page_not_403) {
// Find a menu link corresponding to the current path. If $active_path
// is NULL, let menu_link_get_preferred() determine the path.
if ($active_link = menu_link_get_preferred($active_path, $menu_name)) {
// The active link may only be taken into account to build the
// active trail, if it resides in the requested menu. Otherwise,
// we'd needlessly re-run _menu_build_tree() queries for every menu
// on every page.
if ($active_link['menu_name'] == $menu_name) {
// Use all the coordinates, except the last one because there
// can be no child beyond the last column.
for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
if ($active_link['p' . $i]) {
$active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
}
}
// If we are asked to build links for the active trail only, skip
// the entire 'expanded' handling.
if ($only_active_trail) {
$tree_parameters['only_active_trail'] = TRUE;
}
}
}
$parents = $active_trail;
$expanded = \Drupal::state()->get('menu_expanded');
// Check whether the current menu has any links set to be expanded.
if (!$only_active_trail && $expanded && in_array($menu_name, $expanded)) {
// Collect all the links set to be expanded, and then add all of
// their children to the list as well.
do {
$query = \Drupal::entityQuery('menu_link')
->condition('menu_name', $menu_name)
->condition('expanded', 1)
->condition('has_children', 1)
->condition('plid', $parents, 'IN')
->condition('mlid', $parents, 'NOT IN');
$result = $query->execute();
$parents += $result;
} while (!empty($result));
}
$tree_parameters['expanded'] = $parents;
$tree_parameters['active_trail'] = $active_trail;
}
// If access is denied, we only show top-level links in menus.
else {
$tree_parameters['expanded'] = $active_trail;
$tree_parameters['active_trail'] = $active_trail;
}
// Cache the tree building parameters using the page-specific cid.
\Drupal::cache('data')->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
}
// Build the tree using the parameters; the resulting tree will be cached
// by _menu_build_tree().
$tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
}
return $tree[$cid];
}
return array();
}
/**
* Builds a menu tree, translates links, and checks access.
*
* @param $menu_name
* The name of the menu.
* @param $parameters
* (optional) An associative array of build parameters. Possible keys:
* - expanded: An array of parent link ids to return only menu links that are
* children of one of the plids in this list. If empty, the whole menu tree
* is built, unless 'only_active_trail' is TRUE.
* - active_trail: An array of mlids, representing the coordinates of the
* currently active menu link.
* - only_active_trail: Whether to only return links that are in the active
* trail. This option is ignored, if 'expanded' is non-empty.
* - min_depth: The minimum depth of menu links in the resulting tree.
* Defaults to 1, which is the default to build a whole tree for a menu
* (excluding menu container itself).
* - max_depth: The maximum depth of menu links in the resulting tree.
* - conditions: An associative array of custom database select query
* condition key/value pairs; see _menu_build_tree() for the actual query.
*
* @return
* A fully built menu tree.
*/
function menu_build_tree($menu_name, array $parameters = array()) {
// Build the menu tree.
$data = _menu_build_tree($menu_name, $parameters);
// Check access for the current user to each item in the tree.
menu_tree_check_access($data['tree'], $data['node_links']);
return $data['tree'];
}
/**
* Builds a menu tree.
*
* This function may be used build the data for a menu tree only, for example
* to further massage the data manually before further processing happens.
* menu_tree_check_access() needs to be invoked afterwards.
*
* @see menu_build_tree()
*/
function _menu_build_tree($menu_name, array $parameters = array()) {
// Static cache of already built menu trees.
$trees = &drupal_static(__FUNCTION__, array());
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Build the cache id; sort parents to prevent duplicate storage and remove
// default parameter values.
if (isset($parameters['expanded'])) {
sort($parameters['expanded']);
}
$tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
// If we do not have this tree in the static cache, check {cache_data}.
if (!isset($trees[$tree_cid])) {
$cache = \Drupal::cache('data')->get($tree_cid);
if ($cache && isset($cache->data)) {
$trees[$tree_cid] = $cache->data;
}
}
if (!isset($trees[$tree_cid])) {
$query = \Drupal::entityQuery('menu_link');
for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
$query->sort('p' . $i, 'ASC');
}
$query->condition('menu_name', $menu_name);
if (!empty($parameters['expanded'])) {
$query->condition('plid', $parameters['expanded'], 'IN');
}
elseif (!empty($parameters['only_active_trail'])) {
$query->condition('mlid', $parameters['active_trail'], 'IN');
}
$min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
if ($min_depth != 1) {
$query->condition('depth', $min_depth, '>=');
}
if (isset($parameters['max_depth'])) {
$query->condition('depth', $parameters['max_depth'], '<=');
}
// Add custom query conditions, if any were passed.
if (isset($parameters['conditions'])) {
foreach ($parameters['conditions'] as $column => $value) {
$query->condition($column, $value);
}
}
// Build an ordered array of links using the query result object.
$links = array();
if ($result = $query->execute()) {
$links = menu_link_load_multiple($result);
}
$active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
$data['tree'] = menu_tree_data($links, $active_trail, $min_depth);
$data['node_links'] = array();
menu_tree_collect_node_links($data['tree'], $data['node_links']);
// Cache the data, if it is not already in the cache.
\Drupal::cache('data')->set($tree_cid, $data, Cache::PERMANENT, array('menu' => $menu_name));
$trees[$tree_cid] = $data;
}
return $trees[$tree_cid];
}
/**
* Collects node links from a given menu tree recursively.
*
* @param $tree
* The menu tree you wish to collect node links from.
* @param $node_links
* An array in which to store the collected node links.
*/
function menu_tree_collect_node_links(&$tree, &$node_links) {
foreach ($tree as $key => $v) {
if ($tree[$key]['link']['router_path'] == 'node/%') {
$nid = substr($tree[$key]['link']['link_path'], 5);
if (is_numeric($nid)) {
$node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
$tree[$key]['link']['access'] = FALSE;
}
}
if ($tree[$key]['below']) {
menu_tree_collect_node_links($tree[$key]['below'], $node_links);
}
}
}
/**
* Checks access and performs dynamic operations for each link in the tree.
*
* @param $tree
* The menu tree you wish to operate on.
* @param $node_links
* A collection of node link references generated from $tree by
* menu_tree_collect_node_links().
*/
function menu_tree_check_access(&$tree, $node_links = array()) {
if ($node_links) {
$nids = array_keys($node_links);
$select = db_select('node_field_data', 'n');
$select->addField('n', 'nid');
// @todo This should be actually filtering on the desired node status field
// language and just fall back to the default language.
$select->condition('n.status', 1);
$select->condition('n.nid', $nids, 'IN');
$select->addTag('node_access');
$nids = $select->execute()->fetchCol();
foreach ($nids as $nid) {
foreach ($node_links[$nid] as $mlid => $link) {
$node_links[$nid][$mlid]['access'] = TRUE;
}
}
}
_menu_tree_check_access($tree);
}
/**
* Sorts the menu tree and recursively checks access for each item.
*/
function _menu_tree_check_access(&$tree) {
$new_tree = array();
foreach ($tree as $key => $v) {
$item = &$tree[$key]['link'];
_menu_link_translate($item);
if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
if ($tree[$key]['below']) {
_menu_tree_check_access($tree[$key]['below']);
}
// The weights are made a uniform 5 digits by adding 50000 as an offset.
// After _menu_link_translate(), $item['title'] has the localized link title.
// Adding the mlid to the end of the index insures that it is unique.
$new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
}
}
// Sort siblings in the tree based on the weights and localized titles.
ksort($new_tree);
$tree = $new_tree;
}
/**
* Sorts and returns the built data representing a menu tree.
*
* @param $links
* A flat array of menu links that are part of the menu. Each array element
* is an associative array of information about the menu link, containing the
* fields from the {menu_links} table, and optionally additional information
* from the {menu_router} table, if the menu item appears in both tables.
* This array must be ordered depth-first. See _menu_build_tree() for a sample
* query.
* @param $parents
* An array of the menu link ID values that are in the path from the current
* page to the root of the menu tree.
* @param $depth
* The minimum depth to include in the returned menu tree.
*
* @return
* An array of menu links in the form of a tree. Each item in the tree is an
* associative array containing:
* - link: The menu link item from $links, with additional element
* 'in_active_trail' (TRUE if the link ID was in $parents).
* - below: An array containing the sub-tree of this item, where each element
* is a tree item array with 'link' and 'below' elements. This array will be
* empty if the menu item has no items in its sub-tree having a depth
* greater than or equal to $depth.
*/
function menu_tree_data(array $links, array $parents = array(), $depth = 1) {
// Reverse the array so we can use the more efficient array_pop() function.
$links = array_reverse($links);
return _menu_tree_data($links, $parents, $depth);
}
/**
* Builds the data representing a menu tree.
*
* The function is a bit complex because the rendering of a link depends on
* the next menu link.
*/
function _menu_tree_data(&$links, $parents, $depth) {
$tree = array();
while ($item = array_pop($links)) {
// We need to determine if we're on the path to root so we can later build
// the correct active trail.
$item['in_active_trail'] = in_array($item['mlid'], $parents);
// Add the current link to the tree.
$tree[$item['mlid']] = array(
'link' => $item,
'below' => array(),
);
// Look ahead to the next link, but leave it on the array so it's available
// to other recursive function calls if we return or build a sub-tree.
$next = end($links);
// Check whether the next link is the first in a new sub-tree.
if ($next && $next['depth'] > $depth) {
// Recursively call _menu_tree_data to build the sub-tree.
$tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']);
// Fetch next link after filling the sub-tree.
$next = end($links);
}
// Determine if we should exit the loop and return.
if (!$next || $next['depth'] < $depth) {
break;
}
}
return $tree;
}
/**
* Implements template_preprocess_HOOK() for theme_menu_tree().
*/
@ -1112,7 +524,9 @@ function menu_navigation_links($menu_name, $level = 0) {
}
// Get the menu hierarchy for the current page.
$tree = menu_tree_page_data($menu_name, $level + 1);
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
$tree = $menu_tree->buildPageData($menu_name, $level + 1);
// Go down the active trail until the right level is reached.
while ($level-- > 0 && $tree) {
@ -1361,6 +775,9 @@ function menu_set_active_item($path) {
function menu_set_active_trail($new_trail = NULL) {
$trail = &drupal_static(__FUNCTION__);
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
if (isset($new_trail)) {
$trail = $new_trail;
}
@ -1384,7 +801,7 @@ function menu_set_active_trail($new_trail = NULL) {
// Pass TRUE for $only_active_trail to make menu_tree_page_data() build
// a stripped down menu tree containing the active trail only, in case
// the given menu has not been built in this request yet.
$tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE);
$tree = $menu_tree->buildPageData($preferred_link['menu_name'], NULL, TRUE);
list($key, $curr) = each($tree);
}
// There is no link for the current path.

View File

@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityFormController;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Language\Language;
use Drupal\menu_link\MenuLinkStorageControllerInterface;
use Drupal\menu_link\MenuTreeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -33,6 +34,13 @@ class MenuFormController extends EntityFormController {
*/
protected $menuLinkStorage;
/**
* The menu tree service.
*
* @var \Drupal\menu_link\MenuTreeInterface
*/
protected $menuTree;
/**
* The overview tree form.
*
@ -47,10 +55,13 @@ class MenuFormController extends EntityFormController {
* The factory for entity queries.
* @param \Drupal\menu_link\MenuLinkStorageControllerInterface $menu_link_storage
* The menu link storage controller.
* @param \Drupal\menu_link\MenuTreeInterface $menu_tree
* The menu tree service.
*/
public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageControllerInterface $menu_link_storage) {
public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageControllerInterface $menu_link_storage, MenuTreeInterface $menu_tree) {
$this->entityQueryFactory = $entity_query_factory;
$this->menuLinkStorage = $menu_link_storage;
$this->menuTree = $menu_tree;
}
/**
@ -59,7 +70,8 @@ class MenuFormController extends EntityFormController {
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query'),
$container->get('entity.manager')->getStorageController('menu_link')
$container->get('entity.manager')->getStorageController('menu_link'),
$container->get('menu_link.tree')
);
}
@ -256,13 +268,9 @@ class MenuFormController extends EntityFormController {
}
$delta = max(count($links), 50);
$tree = menu_tree_data($links);
$node_links = array();
menu_tree_collect_node_links($tree, $node_links);
// We indicate that a menu administrator is running the menu access check.
$this->getRequest()->attributes->set('_menu_admin', TRUE);
menu_tree_check_access($tree, $node_links);
$tree = $this->menuTree->buildTreeData($links);
$this->getRequest()->attributes->set('_menu_admin', FALSE);
$form = array_merge($form, $this->buildOverviewTreeForm($tree, $delta));

View File

@ -263,10 +263,13 @@ function _menu_get_options($menus, $available_menus, $item) {
$limit = _menu_parent_depth_limit($item);
}
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
$options = array();
foreach ($menus as $menu_name => $title) {
if (isset($available_menus[$menu_name])) {
$tree = menu_tree_all_data($menu_name, NULL);
$tree = $menu_tree->buildAllData($menu_name, NULL);
$options[$menu_name . ':0'] = '<' . $title . '>';
_menu_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit);
}

View File

@ -0,0 +1,606 @@
<?php
/**
* @file
* Contains \Drupal\menu_link\MenuTree.
*/
namespace Drupal\menu_link;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\KeyValueStore\StateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Provides the default implementation of a menu tree.
*/
class MenuTree implements MenuTreeInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
* The database connection.
*/
protected $database;
/**
* The cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The menu link storage controller.
*
* @var \Drupal\menu_link\MenuLinkStorageControllerInterface
*/
protected $menuLinkStorage;
/**
* The entity query factory.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $queryFactory;
/**
* The state.
*
* @var \Drupal\Core\KeyValueStore\StateInterface
*/
protected $state;
/**
* A list of active trail paths keyed by $menu_name.
*
* @var array
*/
protected $trailPaths;
/**
* Stores the rendered menu output keyed by $menu_name.
*
* @var array
*/
protected $menuOutput;
/**
* Stores the menu tree used by the doBuildTree method, keyed by a cache ID.
*
* This cache ID is built using the $menu_name, the current language and
* some parameters passed into an entity query.
*/
protected $menuTree;
/**
* Stores the full menu tree data keyed by a cache ID.
*
* This variable distinct from static::$menuTree by having also items without
* access by the current user.
*
* This cache ID is built with the menu name, a passed in root link ID, the
* current language as well as the maximum depth.
*
* @var array
*/
protected $menuFullTrees;
/**
* Stores the menu tree data on the current page keyed by a cache ID.
*
* This contains less information than a tree built with buildAllData.
*
* @var array
*/
protected $menuPageTrees;
/**
* Constructs a new MenuTree.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* The cache backend.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
* The entity query factory.
* @param \Drupal\Core\KeyValueStore\StateInterface $state
* The state.
*/
public function __construct(Connection $database, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, QueryFactory $entity_query_factory, StateInterface $state) {
$this->database = $database;
$this->cache = $cache_backend;
$this->languageManager = $language_manager;
$this->requestStack = $request_stack;
$this->menuLinkStorage = $entity_manager->getStorageController('menu_link');
$this->queryFactory = $entity_query_factory;
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public function buildAllData($menu_name, $link = NULL, $max_depth = NULL) {
$language_interface = $this->languageManager->getCurrentLanguage();
// Use $mlid as a flag for whether the data being loaded is for the whole
// tree.
$mlid = isset($link['mlid']) ? $link['mlid'] : 0;
// Generate a cache ID (cid) specific for this $menu_name, $link, $language,
// and depth.
$cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->id . ':' . (int) $max_depth;
if (!isset($this->menuFullTrees[$cid])) {
// If the static variable doesn't have the data, check {cache_menu}.
$cache = $this->cache->get($cid);
if ($cache && $cache->data) {
// If the cache entry exists, it contains the parameters for
// menu_build_tree().
$tree_parameters = $cache->data;
}
// If the tree data was not in the cache, build $tree_parameters.
if (!isset($tree_parameters)) {
$tree_parameters = array(
'min_depth' => 1,
'max_depth' => $max_depth,
);
if ($mlid) {
// The tree is for a single item, so we need to match the values in
// its p columns and 0 (the top level) with the plid values of other
// links.
$parents = array(0);
for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
if (!empty($link["p$i"])) {
$parents[] = $link["p$i"];
}
}
$tree_parameters['expanded'] = $parents;
$tree_parameters['active_trail'] = $parents;
$tree_parameters['active_trail'][] = $mlid;
}
// Cache the tree building parameters using the page-specific cid.
$this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
}
// Build the tree using the parameters; the resulting tree will be cached
// by $this->doBuildTree()).
$this->menuFullTrees[$cid] = $this->buildTree($menu_name, $tree_parameters);
}
return $this->menuFullTrees[$cid];
}
/**
* {@inheritdoc}
*/
public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
$language_interface = $this->languageManager->getCurrentLanguage();
// Check if the active trail has been overridden for this menu tree.
$active_path = $this->getPath($menu_name);
// Load the request corresponding to the current page.
$request = $this->requestStack->getCurrentRequest();
$system_path = NULL;
if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
// @todo https://drupal.org/node/2068471 is adding support so we can tell
// if this is called on a 404/403 page.
$system_path = $request->attributes->get('_system_path');
$page_not_403 = 1;
}
if (isset($system_path)) {
if (isset($max_depth)) {
$max_depth = min($max_depth, MENU_MAX_DEPTH);
}
// Generate a cache ID (cid) specific for this page.
$cid = 'links:' . $menu_name . ':page:' . $system_path . ':' . $language_interface->id . ':' . $page_not_403 . ':' . (int) $max_depth;
// If we are asked for the active trail only, and $menu_name has not been
// built and cached for this page yet, then this likely means that it
// won't be built anymore, as this function is invoked from
// template_preprocess_page(). So in order to not build a giant menu tree
// that needs to be checked for access on all levels, we simply check
// whether we have the menu already in cache, or otherwise, build a
// minimum tree containing the active trail only.
// @see menu_set_active_trail()
if (!isset($this->menuPageTrees[$cid]) && $only_active_trail) {
$cid .= ':trail';
}
if (!isset($this->menuPageTrees[$cid])) {
// If the static variable doesn't have the data, check {cache_menu}.
$cache = $this->cache->get($cid);
if ($cache && $cache->data) {
// If the cache entry exists, it contains the parameters for
// menu_build_tree().
$tree_parameters = $cache->data;
}
// If the tree data was not in the cache, build $tree_parameters.
if (!isset($tree_parameters)) {
$tree_parameters = array(
'min_depth' => 1,
'max_depth' => $max_depth,
);
// Parent mlids; used both as key and value to ensure uniqueness.
// We always want all the top-level links with plid == 0.
$active_trail = array(0 => 0);
// If this page is accessible to the current user, build the tree
// parameters accordingly.
if ($page_not_403) {
// Find a menu link corresponding to the current path. If
// $active_path is NULL, let menu_link_get_preferred() determine
// the path.
if ($active_link = $this->menuLinkGetPreferred($menu_name, $active_path)) {
// The active link may only be taken into account to build the
// active trail, if it resides in the requested menu.
// Otherwise, we'd needlessly re-run _menu_build_tree() queries
// for every menu on every page.
if ($active_link['menu_name'] == $menu_name) {
// Use all the coordinates, except the last one because
// there can be no child beyond the last column.
for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
if ($active_link['p' . $i]) {
$active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
}
}
// If we are asked to build links for the active trail only,skip
// the entire 'expanded' handling.
if ($only_active_trail) {
$tree_parameters['only_active_trail'] = TRUE;
}
}
}
$parents = $active_trail;
$expanded = $this->state->get('menu_expanded');
// Check whether the current menu has any links set to be expanded.
if (!$only_active_trail && $expanded && in_array($menu_name, $expanded)) {
// Collect all the links set to be expanded, and then add all of
// their children to the list as well.
do {
$query = $this->queryFactory->get('menu_link')
->condition('menu_name', $menu_name)
->condition('expanded', 1)
->condition('has_children', 1)
->condition('plid', $parents, 'IN')
->condition('mlid', $parents, 'NOT IN');
$result = $query->execute();
$parents += $result;
} while (!empty($result));
}
$tree_parameters['expanded'] = $parents;
$tree_parameters['active_trail'] = $active_trail;
}
// If access is denied, we only show top-level links in menus.
else {
$tree_parameters['expanded'] = $active_trail;
$tree_parameters['active_trail'] = $active_trail;
}
// Cache the tree building parameters using the page-specific cid.
$this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
}
// Build the tree using the parameters; the resulting tree will be
// cached by $tihs->buildTree().
$this->menuPageTrees[$cid] = $this->buildTree($menu_name, $tree_parameters);
}
return $this->menuPageTrees[$cid];
}
return array();
}
/**
* {@inheritdoc}
*/
public function setPath($menu_name, $path = NULL) {
if (isset($path)) {
$this->trailPaths[$menu_name] = $path;
}
}
/**
* {@inheritdoc}
*/
public function getPath($menu_name) {
return isset($this->trailPaths[$menu_name]) ? $this->trailPaths[$menu_name] : NULL;
}
/**
* {@inheritdoc}
*/
public function renderMenu($menu_name) {
if (!isset($this->menuOutput[$menu_name])) {
$tree = $this->buildPageData($menu_name);
$this->menuOutput[$menu_name] = $this->renderTree($tree);
}
return $this->menuOutput[$menu_name];
}
/**
* {@inheritdoc}
*/
public function renderTree($tree) {
$build = array();
$items = array();
$menu_name = $tree ? end($tree)['link']['menu_name'] : '';
// Pull out just the menu links we are going to render so that we
// get an accurate count for the first/last classes.
foreach ($tree as $data) {
if ($data['link']['access'] && !$data['link']['hidden']) {
$items[] = $data;
}
}
foreach ($items as $data) {
$class = array();
// Set a class for the <li>-tag. Since $data['below'] may contain local
// tasks, only set 'expanded' class if the link also has children within
// the current menu.
if ($data['link']['has_children'] && $data['below']) {
$class[] = 'expanded';
}
elseif ($data['link']['has_children']) {
$class[] = 'collapsed';
}
else {
$class[] = 'leaf';
}
// Set a class if the link is in the active trail.
if ($data['link']['in_active_trail']) {
$class[] = 'active-trail';
$data['link']['localized_options']['attributes']['class'][] = 'active-trail';
}
// Allow menu-specific theme overrides.
$element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
$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'];
// Index using the link's unique mlid.
$build[$data['link']['mlid']] = $element;
}
if ($build) {
// 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, '-', '_');
// Set cache tag.
$menu_name = $data['link']['menu_name'];
$build['#cache']['tags']['menu'][$menu_name] = $menu_name;
}
return $build;
}
/**
* {@inheritdoc}
*/
public function buildTree($menu_name, array $parameters = array()) {
// Build the menu tree.
$tree = $this->doBuildTree($menu_name, $parameters);
// Check access for the current user to each item in the tree.
$this->checkAccess($tree);
return $tree;
}
/**
* Builds a menu tree.
*
* This function may be used build the data for a menu tree only, for example
* to further massage the data manually before further processing happens.
* menu_tree_check_access() needs to be invoked afterwards.
*
* @param string $menu_name
* The name of the menu.
* @param array $parameters
* The parameters passed into static::buildTree()
*
* @see static::buildTree()
*/
protected function doBuildTree($menu_name, array $parameters = array()) {
$language_interface = $this->languageManager->getCurrentLanguage();
// Build the cache id; sort parents to prevent duplicate storage and remove
// default parameter values.
if (isset($parameters['expanded'])) {
sort($parameters['expanded']);
}
$tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
// If we do not have this tree in the static cache, check {cache_menu}.
if (!isset($this->menuTree[$tree_cid])) {
$cache = $this->cache->get($tree_cid);
if ($cache && $cache->data) {
$this->menuFullTrees[$tree_cid] = $cache->data;
}
}
if (!isset($this->menuTree[$tree_cid])) {
$query = $this->queryFactory->get('menu_link');
for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
$query->sort('p' . $i, 'ASC');
}
$query->condition('menu_name', $menu_name);
if (!empty($parameters['expanded'])) {
$query->condition('plid', $parameters['expanded'], 'IN');
}
elseif (!empty($parameters['only_active_trail'])) {
$query->condition('mlid', $parameters['active_trail'], 'IN');
}
$min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
if ($min_depth != 1) {
$query->condition('depth', $min_depth, '>=');
}
if (isset($parameters['max_depth'])) {
$query->condition('depth', $parameters['max_depth'], '<=');
}
// Add custom query conditions, if any were passed.
if (isset($parameters['conditions'])) {
foreach ($parameters['conditions'] as $column => $value) {
$query->condition($column, $value);
}
}
// Build an ordered array of links using the query result object.
$links = array();
if ($result = $query->execute()) {
$links = $this->menuLinkStorage->loadMultiple($result);
}
$active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
$tree = $this->doBuildTreeData($links, $active_trail, $min_depth);
// Cache the data, if it is not already in the cache.
$this->cache->set($tree_cid, $tree, Cache::PERMANENT, array('menu' => $menu_name));
$this->menuTree[$tree_cid] = $tree;
}
return $this->menuTree[$tree_cid];
}
/**
* Sorts the menu tree and recursively checks access for each item.
*
* @param array $tree
* The menu tree you wish to operate on.
*/
protected function checkAccess(&$tree) {
$new_tree = array();
foreach ($tree as $key => $v) {
$item = &$tree[$key]['link'];
$this->menuLinkTranslate($item);
if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
if ($tree[$key]['below']) {
$this->checkAccess($tree[$key]['below']);
}
// The weights are made a uniform 5 digits by adding 50000 as an offset.
// After _menu_link_translate(), $item['title'] has the localized link
// title. Adding the mlid to the end of the index insures that it is
// unique.
$new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
}
}
// Sort siblings in the tree based on the weights and localized titles.
ksort($new_tree);
$tree = $new_tree;
}
/**
* {@inheritdoc}
*/
public function buildTreeData(array $links, array $parents = array(), $depth = 1) {
$tree = $this->doBuildTreeData($links, $parents, $depth);
$this->checkAccess($tree);
return $tree;
}
/**
* Prepares the data for calling $this->treeDataRecursive().
*/
protected function doBuildTreeData(array $links, array $parents = array(), $depth = 1) {
// Reverse the array so we can use the more efficient array_pop() function.
$links = array_reverse($links);
return $this->treeDataRecursive($links, $parents, $depth);
}
/**
* Builds the data representing a menu tree.
*
* The function is a bit complex because the rendering of a link depends on
* the next menu link.
*
* @param array $links
* A flat array of menu links that are part of the menu. Each array element
* is an associative array of information about the menu link, containing
* the fields from the {menu_links} table, and optionally additional
* information from the {menu_router} table, if the menu item appears in
* both tables. This array must be ordered depth-first.
* See _menu_build_tree() for a sample query.
* @param array $parents
* An array of the menu link ID values that are in the path from the current
* page to the root of the menu tree.
* @param int $depth
* The minimum depth to include in the returned menu tree.
*
* @return array
*/
protected function treeDataRecursive(&$links, $parents, $depth) {
$tree = array();
while ($item = array_pop($links)) {
// We need to determine if we're on the path to root so we can later build
// the correct active trail.
$item['in_active_trail'] = in_array($item['mlid'], $parents);
// Add the current link to the tree.
$tree[$item['mlid']] = array(
'link' => $item,
'below' => array(),
);
// Look ahead to the next link, but leave it on the array so it's
// available to other recursive function calls if we return or build a
// sub-tree.
$next = end($links);
// Check whether the next link is the first in a new sub-tree.
if ($next && $next['depth'] > $depth) {
// Recursively call doBuildTreeData to build the sub-tree.
$tree[$item['mlid']]['below'] = $this->treeDataRecursive($links, $parents, $next['depth']);
// Fetch next link after filling the sub-tree.
$next = end($links);
}
// Determine if we should exit the loop and return.
if (!$next || $next['depth'] < $depth) {
break;
}
}
return $tree;
}
/**
* Wraps menu_link_get_preferred().
*/
protected function menuLinkGetPreferred($menu_name, $active_path) {
return menu_link_get_preferred($active_path, $menu_name);
}
/**
* Wraps _menu_link_translate().
*/
protected function menuLinkTranslate(&$item) {
_menu_link_translate($item);
}
}

View File

@ -0,0 +1,174 @@
<?php
/**
* @file
* Contains \Drupal\menu_link\MenuTreeInterface.
*/
namespace Drupal\menu_link;
/**
* Defines an interface for trees out of menu links.
*/
interface MenuTreeInterface {
/**
* Returns a rendered menu tree.
*
* The menu item's LI element is given one of the following classes:
* - expanded: The menu item is showing its submenu.
* - collapsed: The menu item has a submenu which is not shown.
* - leaf: The menu item has no submenu.
*
* @param array $tree
* A data structure representing the tree as returned from menu_tree_data.
*
* @return array
* A structured array to be rendered by drupal_render().
*/
public function renderTree($tree);
/**
* Sets the path for determining the active trail of the specified menu tree.
*
* This path will also affect the breadcrumbs under some circumstances.
* Breadcrumbs are built using the preferred link returned by
* menu_link_get_preferred(). If the preferred link is inside one of the menus
* specified in calls to static::setPath(), the preferred link will be
* overridden by the corresponding path returned by static::getPath().
*
* Setting this path does not affect the main content; for that use
* menu_set_active_item() instead.
*
* @param string $menu_name
* The name of the affected menu tree.
* @param string $path
* The path to use when finding the active trail.
*/
public function setPath($menu_name, $path = NULL);
/**
* Gets the path for determining the active trail of the specified menu tree.
*
* @param string $menu_name
* The menu name of the requested tree.
*
* @return string
* A string containing the path. If no path has been specified with
* static::setPath(), NULL is returned.
*/
public function getPath($menu_name);
/**
* Sorts and returns the built data representing a menu tree.
*
* @param array $links
* A flat array of menu links that are part of the menu. Each array element
* is an associative array of information about the menu link, containing
* the fields from the {menu_links} table, and optionally additional
* information from the {menu_router} table, if the menu item appears in
* both tables. This array must be ordered depth-first.
* See _menu_build_tree() for a sample query.
* @param array $parents
* An array of the menu link ID values that are in the path from the current
* page to the root of the menu tree.
* @param int $depth
* The minimum depth to include in the returned menu tree.
*
* @return array
* An array of menu links in the form of a tree. Each item in the tree is an
* associative array containing:
* - link: The menu link item from $links, with additional element
* 'in_active_trail' (TRUE if the link ID was in $parents).
* - below: An array containing the sub-tree of this item, where each
* element is a tree item array with 'link' and 'below' elements. This
* array will be empty if the menu item has no items in its sub-tree
* having a depth greater than or equal to $depth.
*/
public function buildTreeData(array $links, array $parents = array(), $depth = 1);
/**
* Gets the data structure for a named menu tree, based on the current page.
*
* The tree order is maintained by storing each parent in an individual
* field, see http://drupal.org/node/141866 for more.
*
* @param string $menu_name
* The named menu links to return.
* @param int $max_depth
* (optional) The maximum depth of links to retrieve.
* @param bool $only_active_trail
* (optional) Whether to only return the links in the active trail (TRUE)
* instead of all links on every level of the menu link tree (FALSE).
* Defaults to FALSE.
*
* @return array
* An array of menu links, in the order they should be rendered. The array
* is a list of associative arrays -- these have two keys, link and below.
* link is a menu item, ready for theming as a link. Below represents the
* submenu below the link if there is one, and it is a subtree that has the
* same structure described for the top-level array.
*/
public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE);
/**
* Gets the data structure representing a named menu tree.
*
* Since this can be the full tree including hidden items, the data returned
* may be used for generating an an admin interface or a select.
*
* @param string $menu_name
* The named menu links to return
* @param array $link
* A fully loaded menu link, or NULL. If a link is supplied, only the
* path to root will be included in the returned tree - as if this link
* represented the current page in a visible menu.
* @param int $max_depth
* Optional maximum depth of links to retrieve. Typically useful if only one
* or two levels of a sub tree are needed in conjunction with a non-NULL
* $link, in which case $max_depth should be greater than $link['depth'].
*
* @return array
* An tree of menu links in an array, in the order they should be rendered.
*/
public function buildAllData($menu_name, $link = NULL, $max_depth = NULL);
/**
* Renders a menu tree based on the current path.
*
* @param string $menu_name
* The name of the menu.
*
* @return array
* A structured array representing the specified menu on the current page,
* to be rendered by drupal_render().
*/
public function renderMenu($menu_name);
/**
* Builds a menu tree, translates links, and checks access.
*
* @param string $menu_name
* The name of the menu.
* @param array $parameters
* (optional) An associative array of build parameters. Possible keys:
* - expanded: An array of parent link ids to return only menu links that
* are children of one of the plids in this list. If empty, the whole menu
* tree is built, unless 'only_active_trail' is TRUE.
* - active_trail: An array of mlids, representing the coordinates of the
* currently active menu link.
* - only_active_trail: Whether to only return links that are in the active
* trail. This option is ignored, if 'expanded' is non-empty.
* - min_depth: The minimum depth of menu links in the resulting tree.
* Defaults to 1, which is the default to build a whole tree for a menu
* (excluding menu container itself).
* - max_depth: The maximum depth of menu links in the resulting tree.
* - conditions: An associative array of custom database select query
* condition key/value pairs; see _menu_build_tree() for the actual query.
*
* @return array
* A fully built menu tree.
*/
public function buildTree($menu_name, array $parameters = array());
}

View File

@ -0,0 +1,4 @@
services:
menu_link.tree:
class: Drupal\menu_link\MenuTree
arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state']

View File

@ -0,0 +1,387 @@
<?php
/**
* @file
* Contains \Drupal\menu_link\Tests\MenuTreeTest.
*/
namespace Drupal\menu_link\Tests;
use Drupal\menu_link\MenuTree;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Tests the menu tree.
*
* @group Drupal
* @group menu_link
*
* @coversDefaultClass \Drupal\menu_link\MenuTree
*/
class MenuTreeTest extends UnitTestCase {
/**
* The tested menu tree.
*
* @var \Drupal\menu_link\MenuTree|\Drupal\menu_link\Tests\TestMenuTree
*/
protected $menuTree;
/**
* The mocked database connection.
*
* @var \Drupal\Core\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject
*/
protected $connection;
/**
* The mocked cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheBackend;
/**
* The mocked language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $languageManager;
/**
* The test request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack.
*/
protected $requestStack;
/**
* The mocked entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* The mocked entity query factor.y
*
* @var \Drupal\Core\Entity\Query\QueryFactory|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityQueryFactory;
/**
* The mocked state.
*
* @var \Drupal\Core\KeyValueStore\StateInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $state;
/**
* Stores some default values for a menu link.
*
* @var array
*/
protected $defaultMenuLink = array(
'menu_name' => 'main-menu',
'mlid' => 1,
'title' => 'Example 1',
'route_name' => 'example1',
'link_path' => 'example1',
'access' => 1,
'hidden' => FALSE,
'has_children' => FALSE,
'in_active_trail' => TRUE,
'localized_options' => array('attributes' => array('title' => '')),
'weight' => 0,
);
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Tests \Drupal\menu_link\MenuTree',
'description' => '',
'group' => 'Menu',
);
}
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
->disableOriginalConstructor()
->getMock();
$this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$this->requestStack = new RequestStack();
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->entityQueryFactory = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryFactory')
->disableOriginalConstructor()
->getMock();
$this->state = $this->getMock('Drupal\Core\KeyValueStore\StateInterface');
$this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state);
}
/**
* Tests active paths.
*
* @covers ::setPath
* @covers ::getPath
*/
public function testActivePaths() {
$this->assertNull($this->menuTree->getPath('test_menu1'));
$this->menuTree->setPath('test_menu1', 'example_path1');
$this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1'));
$this->assertNull($this->menuTree->getPath('test_menu2'));
$this->menuTree->setPath('test_menu2', 'example_path2');
$this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1'));
$this->assertEquals('example_path2', $this->menuTree->getPath('test_menu2'));
}
/**
* Tests buildTreeData with a single level.
*
* @covers ::buildTreeData
* @covers ::doBuildTreeData
*/
public function testBuildTreeDataWithSingleLevel() {
$items = array();
$items[] = array(
'mlid' => 1,
'depth' => 1,
'weight' => 0,
'title' => '',
'route_name' => 'example1',
'access' => TRUE,
);
$items[] = array(
'mlid' => 2,
'depth' => 1,
'weight' => 0,
'title' => '',
'route_name' => 'example2',
'access' => TRUE,
);
$result = $this->menuTree->buildTreeData($items, array(), 1);
$this->assertCount(2, $result);
$result1 = array_shift($result);
$this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']);
$result2 = array_shift($result);
$this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']);
}
/**
* Tests buildTreeData with a single level and one item being active.
*
* @covers ::buildTreeData
* @covers ::doBuildTreeData
*/
public function testBuildTreeDataWithSingleLevelAndActiveItem() {
$items = array();
$items[] = array(
'mlid' => 1,
'depth' => 1,
'weight' => 0,
'title' => '',
'route_name' => 'example1',
'access' => TRUE,
);
$items[] = array(
'mlid' => 2,
'depth' => 1,
'weight' => 0,
'title' => '',
'route_name' => 'example2',
'access' => TRUE,
);
$result = $this->menuTree->buildTreeData($items, array(1), 1);
$this->assertCount(2, $result);
$result1 = array_shift($result);
$this->assertEquals($items[0] + array('in_active_trail' => TRUE), $result1['link']);
$result2 = array_shift($result);
$this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']);
}
/**
* Tests buildTreeData with a single level and none item being active.
*
* @covers ::buildTreeData
* @covers ::doBuildTreeData
*/
public function testBuildTreeDataWithSingleLevelAndNoActiveItem() {
$items = array();
$items[] = array(
'mlid' => 1,
'depth' => 1,
'weight' => 0,
'title' => '',
'route_name' => 'example1',
'access' => TRUE,
);
$items[] = array(
'mlid' => 2,
'depth' => 1,
'weight' => 0,
'title' => '',
'route_name' => 'example2',
'access' => TRUE,
);
$result = $this->menuTree->buildTreeData($items, array(3), 1);
$this->assertCount(2, $result);
$result1 = array_shift($result);
$this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']);
$result2 = array_shift($result);
$this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']);
}
/**
* Tests buildTreeData with a more complex example.
*
* @covers ::buildTreeData
* @covers ::doBuildTreeData
*/
public function testBuildTreeWithComplexData() {
$items = array(
1 => array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'access' => TRUE, 'weight' => 0, 'title' => ''),
2 => array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'access' => TRUE, 'weight' => 0, 'title' => ''),
3 => array('mlid' => 3, 'depth' => 2, 'route_name' => 'example3', 'access' => TRUE, 'weight' => 0, 'title' => ''),
4 => array('mlid' => 4, 'depth' => 3, 'route_name' => 'example4', 'access' => TRUE, 'weight' => 0, 'title' => ''),
5 => array('mlid' => 5, 'depth' => 1, 'route_name' => 'example5', 'access' => TRUE, 'weight' => 0, 'title' => ''),
);
$tree = $this->menuTree->buildTreeData($items);
// Validate that parent items #1, #2, and #5 exist on the root level.
$this->assertEquals($items[1]['mlid'], $tree['50000 1']['link']['mlid']);
$this->assertEquals($items[2]['mlid'], $tree['50000 2']['link']['mlid']);
$this->assertEquals($items[5]['mlid'], $tree['50000 5']['link']['mlid']);
// Validate that child item #4 exists at the correct location in the hierarchy.
$this->assertEquals($items[4]['mlid'], $tree['50000 2']['below']['50000 3']['below']['50000 4']['link']['mlid']);
}
/**
* Tests the output with a single level.
*
* @covers ::output
*/
public function testOutputWithSingleLevel() {
$tree = array(
'1' => array(
'link' => array('mlid' => 1) + $this->defaultMenuLink,
'below' => array(),
),
'2' => array(
'link' => array('mlid' => 2) + $this->defaultMenuLink,
'below' => array(),
),
);
$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');
}
/**
* Tests the output method with a complex example.
*
* @covers ::output
*/
public function testOutputWithComplexData() {
$tree = array(
'1'=> array(
'link' => array('mlid' => 1, 'has_children' => 1, 'title' => 'Item 1', 'link_path' => 'a') + $this->defaultMenuLink,
'below' => array(
'2' => array('link' => array('mlid' => 2, 'title' => 'Item 2', 'link_path' => 'a/b') + $this->defaultMenuLink,
'below' => array(
'3' => array('link' => array('mlid' => 3, 'title' => 'Item 3', 'in_active_trail' => 0, 'link_path' => 'a/b/c') + $this->defaultMenuLink,
'below' => array()),
'4' => array('link' => array('mlid' => 4, 'title' => 'Item 4', 'in_active_trail' => 0, 'link_path' => 'a/b/d') + $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())
);
$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');
// 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');
// 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');
}
/**
* Tests menu tree access check with a single level.
*
* @covers ::checkAccess
*/
public function testCheckAccessWithSingleLevel() {
$items = array(
array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1', 'in_active_trail' => FALSE) + $this->defaultMenuLink,
array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2', 'in_active_trail' => FALSE) + $this->defaultMenuLink,
);
// Register a menuLinkTranslate to mock the access.
$this->menuTree->menuLinkTranslateCallable = function(&$item) {
$item['access'] = $item['mlid'] == 1;
};
// Build the menu tree and check access for all of the items.
$tree = $this->menuTree->buildTreeData($items);
$this->assertCount(1, $tree);
$item = reset($tree);
$this->assertEquals($items[0], $item['link']);
}
}
class TestMenuTree extends MenuTree {
/**
* An alternative callable used for menuLinkTranslate.
* @var callable
*/
public $menuLinkTranslateCallable;
/**
* {@inheritdoc}
*/
protected function menuLinkTranslate(&$item) {
if (isset($this->menuLinkTranslateCallable)) {
call_user_func_array($this->menuLinkTranslateCallable, array(&$item));
}
}
/**
* {@inheritdoc}
*/
protected function menuLinkGetPreferred($menu_name, $active_path) {
}
}

View File

@ -304,8 +304,6 @@ function shortcut_valid_link($path) {
*
* @return \Drupal\shortcut\ShortcutInterface[]
* An array of shortcut links, in the format returned by the menu system.
*
* @see menu_tree()
*/
function shortcut_renderable_links($shortcut_set = NULL) {
$shortcut_links = array();

View File

@ -9,6 +9,10 @@ namespace Drupal\system\Plugin\Block;
use Drupal\Component\Utility\NestedArray;
use Drupal\block\BlockBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\menu_link\MenuTreeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a generic Menu block.
@ -20,14 +24,50 @@ use Drupal\block\BlockBase;
* derivative = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
* )
*/
class SystemMenuBlock extends BlockBase {
class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The menu tree.
*
* @var \Drupal\menu_link\MenuTreeInterface
*/
protected $menuTree;
/**
* Constructs a new SystemMenuBlock.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\menu_link\MenuTreeInterface $menu_tree
* The menu tree.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, MenuTreeInterface $menu_tree) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->menuTree = $menu_tree;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu_link.tree')
);
}
/**
* {@inheritdoc}
*/
public function build() {
$menu = $this->getDerivativeId();
return menu_tree($menu);
return $this->menuTree->renderMenu($menu);
}
/**

View File

@ -1,116 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Menu\TreeAccessTest.
*/
namespace Drupal\system\Tests\Menu;
use Drupal\menu_link\Entity\MenuLink;
use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Tests the access check for menu tree using both menu links and route items.
*/
class TreeAccessTest extends DrupalUnitTestBase {
/**
* A list of menu links used for this test.
*
* @var array
*/
protected $links;
/**
* The route collection used for this test.
*
* @var\ \Symfony\Component\Routing\RouteCollection
*/
protected $routeCollection;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('menu_link');
public static function getInfo() {
return array(
'name' => 'Menu tree access',
'description' => 'Tests the access check for menu tree using both menu links and route items.',
'group' => 'Menu',
);
}
/**
* Overrides \Drupal\simpletest\DrupalUnitTestBase::containerBuild().
*/
public function containerBuild(ContainerBuilder $container) {
parent::containerBuild($container);
$route_collection = $this->getTestRouteCollection();
$container->register('router.route_provider', 'Drupal\system\Tests\Routing\MockRouteProvider')
->addArgument($route_collection);
}
/**
* Generates the test route collection.
*
* @return \Symfony\Component\Routing\RouteCollection
* Returns the test route collection.
*/
protected function getTestRouteCollection() {
if (!isset($this->routeCollection)) {
$route_collection = new RouteCollection();
$route_collection->add('menu_test_1', new Route('/menu_test/test_1',
array(
'_controller' => '\Drupal\menu_test\TestController::test'
),
array(
'_access' => 'TRUE'
)
));
$route_collection->add('menu_test_2', new Route('/menu_test/test_2',
array(
'_controller' => '\Drupal\menu_test\TestController::test'
),
array(
'_access' => 'FALSE'
)
));
$this->routeCollection = $route_collection;
}
return $this->routeCollection;
}
/**
* Tests access check for menu links with a route item.
*/
public function testRouteItemMenuLinksAccess() {
// Add the access checkers to the route items.
$this->container->get('access_manager')->setChecks($this->getTestRouteCollection());
// Setup the links with the route items.
$this->links = array(
new MenuLink(array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1'), 'menu_link'),
new MenuLink(array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2'), 'menu_link'),
);
// Build the menu tree and check access for all of the items.
$tree = menu_tree_data($this->links);
menu_tree_check_access($tree);
$this->assertEqual(count($tree), 1, 'Ensure that just one menu link got access.');
$item = reset($tree);
$this->assertEqual($this->links[0], $item['link'], 'Ensure that the right link got access');
}
}

View File

@ -1,68 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\system\Tests\Menu\TreeDataUnitTest.
*/
namespace Drupal\system\Tests\Menu;
use Drupal\menu_link\Entity\MenuLink;
use Drupal\simpletest\UnitTestBase;
/**
* Menu tree data related tests.
*/
class TreeDataUnitTest extends UnitTestBase {
/**
* Dummy link structure acceptable for menu_tree_data().
*/
protected $links = array();
public static function getInfo() {
return array(
'name' => 'Menu tree generation',
'description' => 'Tests recursive menu tree generation functions.',
'group' => 'Menu',
);
}
/**
* Validate the generation of a proper menu tree hierarchy.
*/
public function testMenuTreeData() {
$this->links = array(
1 => new MenuLink(array('mlid' => 1, 'depth' => 1), 'menu_link'),
2 => new MenuLink(array('mlid' => 2, 'depth' => 1), 'menu_link'),
3 => new MenuLink(array('mlid' => 3, 'depth' => 2), 'menu_link'),
4 => new MenuLink(array('mlid' => 4, 'depth' => 3), 'menu_link'),
5 => new MenuLink(array('mlid' => 5, 'depth' => 1), 'menu_link'),
);
$tree = menu_tree_data($this->links);
// Validate that parent items #1, #2, and #5 exist on the root level.
$this->assertSameLink($this->links[1], $tree[1]['link'], 'Parent item #1 exists.');
$this->assertSameLink($this->links[2], $tree[2]['link'], 'Parent item #2 exists.');
$this->assertSameLink($this->links[5], $tree[5]['link'], 'Parent item #5 exists.');
// Validate that child item #4 exists at the correct location in the hierarchy.
$this->assertSameLink($this->links[4], $tree[2]['below'][3]['below'][4]['link'], 'Child item #4 exists in the hierarchy.');
}
/**
* Check that two menu links are the same by comparing the mlid.
*
* @param $link1
* A menu link item.
* @param $link2
* A menu link item.
* @param $message
* The message to display along with the assertion.
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertSameLink($link1, $link2, $message = '') {
return $this->assert($link1['mlid'] == $link2['mlid'], $message ?: 'First link is identical to second link');
}
}

View File

@ -1,77 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\system\Tests\Menu\TreeOutputTest.
*/
namespace Drupal\system\Tests\Menu;
use Drupal\simpletest\DrupalUnitTestBase;
/**
* Menu tree output related tests.
*/
class TreeOutputTest extends DrupalUnitTestBase {
public static $modules = array('system', 'menu_link', 'field');
/**
* Dummy link structure acceptable for menu_tree_output().
*/
protected $tree_data = array();
public static function getInfo() {
return array(
'name' => 'Menu tree output',
'description' => 'Tests menu tree output functions.',
'group' => 'Menu',
);
}
function setUp() {
parent::setUp();
$this->installSchema('system', array('router'));
}
/**
* Validate the generation of a proper menu tree output.
*/
function testMenuTreeData() {
$storage_controller = $this->container->get('entity.manager')->getStorageController('menu_link');
// @todo Prettify this tree buildup code, it's very hard to read.
$this->tree_data = array(
'1'=> array(
'link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 1, 'hidden' => 0, 'has_children' => 1, 'title' => 'Item 1', 'in_active_trail' => 1, 'access' => 1, 'link_path' => 'a', 'localized_options' => array('attributes' => array('title' =>'')))),
'below' => array(
'2' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 2, 'hidden' => 0, 'has_children' => 1, 'title' => 'Item 2', 'in_active_trail' => 1, 'access' => 1, 'link_path' => 'a/b', 'localized_options' => array('attributes' => array('title' =>'')))),
'below' => array(
'3' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 3, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 3', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'a/b/c', 'localized_options' => array('attributes' => array('title' =>'')))),
'below' => array() ),
'4' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 4, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 4', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'a/b/d', 'localized_options' => array('attributes' => array('title' =>'')))),
'below' => array() )
)
)
)
),
'5' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 5, 'hidden' => 1, 'has_children' => 0, 'title' => 'Item 5', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'e', 'localized_options' => array('attributes' => array('title' =>'')))), 'below' => array()),
'6' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 6, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'link_path' => 'f', 'localized_options' => array('attributes' => array('title' =>'')))), 'below' => array()),
'7' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 7, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 7', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'g', 'localized_options' => array('attributes' => array('title' =>'')))), 'below' => array())
);
$output = menu_tree_output($this->tree_data);
// Validate that the - in main-menu is changed into an underscore
$this->assertEqual($output['1']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link');
$this->assertEqual($output['#theme_wrappers'][0], 'menu_tree__main_menu', 'Hyphen is changed to an underscore on menu_tree wrapper');
// Looking for child items in the data
$this->assertEqual( $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');
// 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');
// 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');
}
}

View File

@ -223,8 +223,10 @@ function menu_test_callback() {
*/
function menu_test_menu_trail_callback() {
$menu_path = \Drupal::state()->get('menu_test.menu_tree_set_path') ?: array();
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
if (!empty($menu_path)) {
menu_tree_set_path($menu_path['menu_name'], $menu_path['path']);
$menu_tree->setPath($menu_path['menu_name'], $menu_path['path']);
}
return 'This is menu_test_menu_trail_callback().';
}

View File

@ -358,6 +358,9 @@ function toolbar_toolbar() {
// Add attributes to the links before rendering.
toolbar_menu_navigation_links($tree);
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
$menu = array(
'#heading' => t('Administration menu'),
'toolbar_administration' => array(
@ -365,7 +368,7 @@ function toolbar_toolbar() {
'#attributes' => array(
'class' => array('toolbar-menu-administration'),
),
'administration_menu' => menu_tree_output($tree),
'administration_menu' => $menu_tree->renderTree($tree),
),
);
@ -415,6 +418,8 @@ function toolbar_toolbar() {
*/
function toolbar_get_menu_tree() {
$tree = array();
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
$query = \Drupal::entityQuery('menu_link')
->condition('menu_name', 'admin')
->condition('module', 'system')
@ -422,7 +427,7 @@ function toolbar_get_menu_tree() {
$result = $query->execute();
if (!empty($result)) {
$admin_link = menu_link_load(reset($result));
$tree = menu_build_tree('admin', array(
$tree = $menu_tree->buildTree('admin', array(
'expanded' => array($admin_link['mlid']),
'min_depth' => $admin_link['depth'] + 1,
'max_depth' => $admin_link['depth'] + 1,
@ -465,6 +470,8 @@ function toolbar_menu_navigation_links(&$tree) {
*/
function toolbar_get_rendered_subtrees() {
$subtrees = array();
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
$tree = toolbar_get_menu_tree();
foreach ($tree as $tree_item) {
$item = $tree_item['link'];
@ -476,9 +483,9 @@ function toolbar_get_rendered_subtrees() {
$query->condition('p' . $i, $item['p' . $i]);
}
$parents = $query->execute();
$subtree = menu_build_tree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1));
$subtree = $menu_tree->buildTree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1));
toolbar_menu_navigation_links($subtree);
$subtree = menu_tree_output($subtree);
$subtree = $menu_tree->renderTree($subtree);
$subtree = drupal_render($subtree);
}
else {

View File

@ -67,7 +67,9 @@ class UserAccountLinksTests extends WebTestBase {
$this->drupalGet('<front>');
// For a logged-out user, expect no secondary links.
$tree = menu_build_tree('account');
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu_link.tree');
$tree = $menu_tree->buildTree('account');
$this->assertEqual(count($tree), 1, 'The secondary links menu contains only one menu link.');
$link = reset($tree);
$link = $link['link'];