Issue #2207893 by dawehner, pwolanin, jessebeach, Boobaa: Convert menu tree building to a service.
parent
1a51606bee
commit
4a57034dd9
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
|
||||
}
|
|
@ -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']
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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().';
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'];
|
||||
|
|
Loading…
Reference in New Issue