diff --git a/core/includes/menu.inc b/core/includes/menu.inc index fe65d0a..987d9d0 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -322,594 +322,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
  • -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(). */ function template_preprocess_menu_tree(&$variables) { diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml index 8a2c5b2..5793fa0 100644 --- a/core/modules/menu_link/menu_link.services.yml +++ b/core/modules/menu_link/menu_link.services.yml @@ -1,4 +1,4 @@ services: menu_link.tree: class: Drupal\menu_link\MenuTree - arguments: ['@database', '@cache.menu', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state'] + arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state']