From 5b55646e2a5266c084d5e4af77ecb0d63d648d50 Mon Sep 17 00:00:00 2001 From: Dries Buytaert <dries@buytaert.net> Date: Fri, 2 Jul 2010 02:22:26 +0000 Subject: [PATCH] - Patch #620618 by sun, Damien Tournoud, JohnAlbin, makara: optimize menu tree building and use it for toolbar. --- includes/menu.inc | 306 ++++++++++++++++++--------------- modules/toolbar/toolbar.module | 13 +- 2 files changed, 172 insertions(+), 147 deletions(-) diff --git a/includes/menu.inc b/includes/menu.inc index 83cff5f4e5b7..40f98fe290cb 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -979,80 +979,37 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) { // If the static variable doesn't have the data, check {cache_menu}. $cache = cache_get($cid, 'cache_menu'); if ($cache && isset($cache->data)) { - // If the cache entry exists, it will just be the cid for the actual data. - // This avoids duplication of large amounts of data. - $cache = cache_get($cache->data, 'cache_menu'); - if ($cache && isset($cache->data)) { - $data = $cache->data; - } - } - // If the tree data was not in the cache, $data will be NULL. - if (!isset($data)) { - // Build the query using a LEFT JOIN since there is no match in - // {menu_router} for an external link. - $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $query->addTag('translatable'); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - $query->fields('m', array( - 'load_functions', - 'to_arg_functions', - 'access_callback', - 'access_arguments', - 'page_callback', - 'page_arguments', - 'delivery_callback', - 'title', - 'title_callback', - 'title_arguments', - 'theme_callback', - 'theme_arguments', - 'type', - 'description', - )); - for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { - $query->orderBy('p' . $i, 'ASC'); - } - $query->condition('ml.menu_name', $menu_name); - if (isset($max_depth)) { - $query->condition('ml.depth', $max_depth, '<='); - } + // 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. - $args = array(0); + $parents = array(0); for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { - $args[] = $link["p$i"]; + if (!empty($link["p$i"])) { + $parents[] = $link["p$i"]; + } } - $args = array_unique($args); - $query->condition('ml.plid', $args, 'IN'); - $parents = $args; - $parents[] = $link['mlid']; - } - else { - // Get all links in this menu. - $parents = array(); + $tree_parameters['expanded'] = $parents; + $tree_parameters['active_trail'] = $parents; + $tree_parameters['active_trail'][] = $mlid; } - // Select the links from the table, and build an ordered array of links - // using the query result object. - $links = array(); - foreach ($query->execute() as $item) { - $links[] = $item; - } - $data['tree'] = menu_tree_data($links, $parents); - $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. - $tree_cid = _menu_tree_cid($menu_name, $data); - if (!cache_get($tree_cid, 'cache_menu')) { - cache_set($tree_cid, $data, 'cache_menu'); - } - // Cache the cid of the (shared) data using the menu and item-specific cid. - cache_set($cid, $tree_cid, 'cache_menu'); + + // Cache the tree building parameters using the page-specific cid. + cache_set($cid, $tree_parameters, 'cache_menu'); } - // Check access for the current user to each item in the tree. - menu_tree_check_access($data['tree'], $data['node_links']); - $tree[$cid] = $data['tree']; + + // 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]; @@ -1091,23 +1048,25 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { // If the static variable doesn't have the data, check {cache_menu}. $cache = cache_get($cid, 'cache_menu'); if ($cache && isset($cache->data)) { - // If the cache entry exists, it will just be the cid for the actual data. - // This avoids duplication of large amounts of data. - $cache = cache_get($cache->data, 'cache_menu'); - if ($cache && isset($cache->data)) { - $data = $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, $data will be NULL. - if (!isset($data)) { - // Build and run the query, and build the tree. + // 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 the item for the current page is accessible, build the tree + // parameters accordingly. if ($item['access']) { // Check whether a menu link exists that corresponds to the current path. $args[] = $item['href']; if (drupal_is_front_page()) { $args[] = '<front>'; } - $parents = db_select('menu_links') + $active_link = db_select('menu_links') ->fields('menu_links', array( 'p1', 'p2', @@ -1122,10 +1081,10 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { ->condition('link_path', $args, 'IN') ->execute()->fetchAssoc(); - if (empty($parents)) { + if (empty($active_link)) { // If no link exists, we may be on a local task that's not in the links. // TODO: Handle the case like a local task on a specific node in the menu. - $parents = db_select('menu_links') + $active_link = db_select('menu_links') ->fields('menu_links', array( 'p1', 'p2', @@ -1140,11 +1099,13 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { ->condition('link_path', $item['tab_root']) ->execute()->fetchAssoc(); } + // We always want all the top-level links with plid == 0. - $parents[] = '0'; + $active_link[] = '0'; + + // Use array_values() so that the indices are numeric. + $parents = $active_link = array_unique(array_values($active_link)); - // Use array_values() so that the indices are numeric for array_merge(). - $args = $parents = array_unique(array_values($parents)); $expanded = variable_get('menu_expanded', array()); // Check whether the current menu has any links set to be expanded. if (in_array($menu_name, $expanded)) { @@ -1156,72 +1117,31 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { ->condition('menu_name', $menu_name) ->condition('expanded', 1) ->condition('has_children', 1) - ->condition('plid', $args, 'IN') - ->condition('mlid', $args, 'NOT IN') + ->condition('plid', $parents, 'IN') + ->condition('mlid', $parents, 'NOT IN') ->execute(); $num_rows = FALSE; foreach ($result as $item) { - $args[] = $item['mlid']; + $parents[] = $item['mlid']; $num_rows = TRUE; } } while ($num_rows); } + $tree_parameters['expanded'] = $parents; + $tree_parameters['active_trail'] = $active_link; } + // Otherwise, only show the top-level menu items when access is denied. else { - // Show only the top-level menu items when access is denied. - $args = array(0); - $parents = array(); - } - // Select the links from the table, and recursively build the tree. We - // LEFT JOIN since there is no match in {menu_router} for an external - // link. - $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $query->addTag('translatable'); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - $query->fields('m', array( - 'load_functions', - 'to_arg_functions', - 'access_callback', - 'access_arguments', - 'page_callback', - 'page_arguments', - 'delivery_callback', - 'title', - 'title_callback', - 'title_arguments', - 'theme_callback', - 'theme_arguments', - 'type', - 'description', - )); - for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { - $query->orderBy('p' . $i, 'ASC'); + $tree_parameters['expanded'] = array(0); } - $query->condition('ml.menu_name', $menu_name); - $query->condition('ml.plid', $args, 'IN'); - if (isset($max_depth)) { - $query->condition('ml.depth', $max_depth, '<='); - } - // Build an ordered array of links using the query result object. - $links = array(); - foreach ($query->execute() as $item) { - $links[] = $item; - } - $data['tree'] = menu_tree_data($links, $parents); - $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. - $tree_cid = _menu_tree_cid($menu_name, $data); - if (!cache_get($tree_cid, 'cache_menu')) { - cache_set($tree_cid, $data, 'cache_menu'); - } - // Cache the cid of the (shared) data using the page-specific cid. - cache_set($cid, $tree_cid, 'cache_menu'); + + // Cache the tree building parameters using the page-specific cid. + cache_set($cid, $tree_parameters, 'cache_menu'); } - // Check access for the current user to each item in the tree. - menu_tree_check_access($data['tree'], $data['node_links']); - $tree[$cid] = $data['tree']; + + // 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]; } @@ -1230,10 +1150,116 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { } /** - * Helper function - compute the real cache ID for menu tree data. + * Build a menu tree, translate links, and check 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. + * - active_trail: An array of mlids, representing the coordinates of the + * currently active menu link. + * - 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, i.e. + * excluding menu container itself. + * - max_depth: The maximum depth of menu links in the resulting tree. + * + * @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']; +} + +/** + * Build 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_tree_cid($menu_name, $data) { - return 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($data)); +function _menu_build_tree($menu_name, array $parameters = array()) { + // Static cache of already built menu trees. + $trees = &drupal_static(__FUNCTION__, array()); + + // 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:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($parameters)); + + // If we do not have this tree in the static cache, check {cache_menu}. + if (!isset($trees[$tree_cid])) { + $cache = cache_get($tree_cid, 'cache_menu'); + if ($cache && isset($cache->data)) { + $trees[$tree_cid] = $cache->data; + } + } + + if (!isset($trees[$tree_cid])) { + // Select the links from the table, and recursively build the tree. We + // LEFT JOIN since there is no match in {menu_router} for an external + // link. + $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); + $query->addTag('translatable'); + $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); + $query->fields('ml'); + $query->fields('m', array( + 'load_functions', + 'to_arg_functions', + 'access_callback', + 'access_arguments', + 'page_callback', + 'page_arguments', + 'delivery_callback', + 'title', + 'title_callback', + 'title_arguments', + 'theme_callback', + 'theme_arguments', + 'type', + 'description', + )); + for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { + $query->orderBy('p' . $i, 'ASC'); + } + $query->condition('ml.menu_name', $menu_name); + if (!empty($parameters['expanded'])) { + $query->condition('ml.plid', $parameters['expanded'], 'IN'); + } + $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); + if ($min_depth != 1) { + $query->condition('ml.depth', $min_depth, '>='); + } + if (isset($parameters['max_depth'])) { + $query->condition('ml.depth', $parameters['max_depth'], '<='); + } + + // Build an ordered array of links using the query result object. + $links = array(); + foreach ($query->execute() as $item) { + $links[] = $item; + } + $active_link = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array()); + $data['tree'] = menu_tree_data($links, $active_link, $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. + cache_set($tree_cid, $data, 'cache_menu'); + $trees[$tree_cid] = $data; + } + + return $trees[$tree_cid]; } /** diff --git a/modules/toolbar/toolbar.module b/modules/toolbar/toolbar.module index 348c0a29e9c3..93f798288fdf 100644 --- a/modules/toolbar/toolbar.module +++ b/modules/toolbar/toolbar.module @@ -279,14 +279,13 @@ function toolbar_view() { */ function toolbar_get_menu_tree() { $tree = array(); - $admin_link = db_query("SELECT * FROM {menu_links} WHERE menu_name = 'management' AND module = 'system' AND link_path = 'admin'")->fetchAssoc(); + $admin_link = db_query('SELECT * FROM {menu_links} WHERE menu_name = :menu_name AND module = :module AND link_path = :path', array(':menu_name' => 'management', ':module' => 'system', ':path' => 'admin'))->fetchAssoc(); if ($admin_link) { - // @todo Use a function like book_menu_subtree_data(). - $tree = menu_tree_all_data('management', $admin_link, $admin_link['depth'] + 1); - // The tree will be a sub-tree with the admin link as a single root item. - // @todo It is wrong to assume it's the last. - $admin_link = array_pop($tree); - $tree = $admin_link['below'] ? $admin_link['below'] : array(); + $tree = menu_build_tree('management', array( + 'expanded' => array($admin_link['mlid']), + 'min_depth' => $admin_link['depth'] + 1, + 'max_depth' => $admin_link['depth'] + 1, + )); } return $tree; -- GitLab