diff --git a/includes/menu.inc b/includes/menu.inc index dad65d7..bdbbbb6 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -275,6 +275,20 @@ define('MENU_MAX_DEPTH', 9); */ /** + * Reserved key to identify the most specific menu link for a given path. + * + * The value of this constant is a hash of the constant name. We use the hash + * so that the reserved key is over 32 characters in length and will not + * collide with allowed menu names: + * @code + * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91 + * @endcode + * + * @see menu_link_get_preferred() + */ +define('MENU_PREFERRED_LINK', '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91'); + +/** * Returns the ancestors (and relevant placeholders) for any given path. * * For example, the ancestors of node/12345/edit are: @@ -1241,7 +1255,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = if ($item['access']) { // 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)) { + 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 @@ -2260,6 +2274,13 @@ function theme_menu_local_tasks(&$variables) { /** * Set (or get) the active menu for the current page - determines the active trail. + * + * @return + * An array of menu machine names, in order of preference. The + * 'menu_default_active_menus' variable may be used to assert a menu order + * different from the order of creation, or to prevent a particular menu from + * being used at all in the active trail. + * E.g., $conf['menu_default_active_menus'] = array('navigation', 'main-menu') */ function menu_set_active_menu_names($menu_names = NULL) { $active = &drupal_static(__FUNCTION__); @@ -2390,23 +2411,30 @@ function menu_set_active_trail($new_trail = NULL) { * @param $path * The path, for example 'node/5'. The function will find the corresponding * menu link ('node/5' if it exists, or fallback to 'node/%'). + * @param $selected_menu + * The name of a menu used to restrict the search for a preferred menu link. + * If not specified, all the menus returned by menu_get_active_menu_names() + * will be used. * * @return - * A fully translated menu link, or NULL if no matching menu link was + * A fully translated menu link, or FALSE if no matching menu link was * found. The most specific menu link ('node/5' preferred over 'node/%') in * the most preferred menu (as defined by menu_get_active_menu_names()) is * returned. */ -function menu_link_get_preferred($path = NULL) { +function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { $preferred_links = &drupal_static(__FUNCTION__); if (!isset($path)) { $path = $_GET['q']; } - if (!isset($preferred_links[$path])) { - $preferred_links[$path] = FALSE; + if (empty($selected_menu)) { + // Use an illegal menu name as the key for the preferred menu link. + $selected_menu = MENU_PREFERRED_LINK; + } + if (!isset($preferred_links[$path])) { // Look for the correct menu link by building a list of candidate paths, // which are ordered by priority (translated hrefs are preferred over // untranslated paths). Afterwards, the most relevant path is picked from @@ -2428,6 +2456,8 @@ function menu_link_get_preferred($path = NULL) { // Retrieve a list of menu names, ordered by preference. $menu_names = menu_get_active_menu_names(); + // Put the selected menu at the front of the list. + array_unshift($menu_names, $selected_menu); $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); @@ -2435,7 +2465,6 @@ function menu_link_get_preferred($path = NULL) { // Weight must be taken from {menu_links}, not {menu_router}. $query->addField('ml', 'weight', 'link_weight'); $query->fields('m'); - $query->condition('ml.menu_name', $menu_names, 'IN'); $query->condition('ml.link_path', $path_candidates, 'IN'); // Sort candidates by link path and menu name. @@ -2443,29 +2472,35 @@ function menu_link_get_preferred($path = NULL) { foreach ($query->execute() as $candidate) { $candidate['weight'] = $candidate['link_weight']; $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; + // Add any menus not already in the menu name search list. + if (!in_array($candidate['menu_name'], $menu_names)) { + $menu_names[] = $candidate['menu_name']; + } } - // Pick the most specific link, in the most preferred menu. + // Store the most specific link for each menu. Also save the most specific + // link of the most preferred menu in $preferred_link. foreach ($path_candidates as $link_path) { - if (!isset($candidates[$link_path])) { - continue; - } - foreach ($menu_names as $menu_name) { - if (!isset($candidates[$link_path][$menu_name])) { - continue; - } - $candidate_item = $candidates[$link_path][$menu_name]; - $map = explode('/', $path); - _menu_translate($candidate_item, $map); - if ($candidate_item['access']) { - $preferred_links[$path] = $candidate_item; + if (isset($candidates[$link_path])) { + foreach ($menu_names as $menu_name) { + if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) { + $candidate_item = $candidates[$link_path][$menu_name]; + $map = explode('/', $path); + _menu_translate($candidate_item, $map); + if ($candidate_item['access']) { + $preferred_links[$path][$menu_name] = $candidate_item; + if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) { + // Store the most specific link. + $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item; + } + } + } } - break 2; } } } - return $preferred_links[$path]; + return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE; } /** diff --git a/modules/menu/menu.install b/modules/menu/menu.install index a7e4337..7877b99 100644 --- a/modules/menu/menu.install +++ b/modules/menu/menu.install @@ -183,6 +183,28 @@ function menu_update_7002(&$sandbox) { } } /** + * Add missing custom menus to active menus list. + */ +function menu_update_7003(&$sandbox) { + // Make sure all custom menus are present in the active menus variable so that + // their items may appear in the active trail. + // @see menu_set_active_menu_names() + $active_menus = variable_get('menu_default_active_menus', array_keys(menu_list_system_menus())); + $update_variable = FALSE; + foreach (menu_get_names() as $menu_name) { + if (!in_array($menu_name, $active_menus) && (strpos($menu_name, 'menu-') === 0)) { + $active_menus[] = $menu_name; + $update_variable = TRUE; + } + } + if ($update_variable) { + variable_set('menu_default_active_menus', $active_menus); + } + // Clear the menu cache. + cache_clear_all(NULL, 'cache_menu'); +} + +/** * @} End of "defgroup updates-7.x-extra" * The next series of updates should start at 8000. */ diff --git a/modules/menu/menu.module b/modules/menu/menu.module index c914867..615548c 100644 --- a/modules/menu/menu.module +++ b/modules/menu/menu.module @@ -268,6 +268,15 @@ function menu_save($menu) { switch ($status) { case SAVED_NEW: + // Make sure the menu is present in the active menus variable so that its + // items may appear in the menu active trail. + // @see menu_set_active_menu_names() + $active_menus = variable_get('menu_default_active_menus', array_keys(menu_get_menus())); + if (!in_array($menu['menu_name'], $active_menus)) { + $active_menus[] = $menu['menu_name']; + variable_set('menu_default_active_menus', $active_menus); + } + module_invoke_all('menu_insert', $menu); break; @@ -305,6 +314,15 @@ function menu_delete($menu) { // Delete all links from the menu. menu_delete_links($menu['menu_name']); + // Remove menu from active menus variable. + $active_menus = variable_get('menu_default_active_menus', array_keys(menu_get_menus())); + foreach ($active_menus as $i => $menu_name) { + if ($menu['menu_name'] == $menu_name) { + unset($active_menus[$i]); + variable_set('menu_default_active_menus', $active_menus); + } + } + // Delete the custom menu. db_delete('menu_custom') ->condition('menu_name', $menu['menu_name']) diff --git a/modules/menu/menu.test b/modules/menu/menu.test index 0edfc47..44b5d68 100644 --- a/modules/menu/menu.test +++ b/modules/menu/menu.test @@ -205,7 +205,7 @@ class MenuTestCase extends DrupalWebTestCase { // Add menu links. $item1 = $this->addMenuLink(0, 'node/' . $node1->nid, $menu_name); - $item2 = $this->addMenuLink($item1['mlid'], 'node/' . $node2->nid, $menu_name); + $item2 = $this->addMenuLink($item1['mlid'], 'node/' . $node2->nid, $menu_name, FALSE); $item3 = $this->addMenuLink($item2['mlid'], 'node/' . $node3->nid, $menu_name); $this->assertMenuLink($item1['mlid'], array('depth' => 1, 'has_children' => 1, 'p1' => $item1['mlid'], 'p2' => 0)); $this->assertMenuLink($item2['mlid'], array('depth' => 2, 'has_children' => 1, 'p1' => $item1['mlid'], 'p2' => $item2['mlid'], 'p3' => 0)); @@ -283,7 +283,7 @@ class MenuTestCase extends DrupalWebTestCase { * @param string $menu_name Menu name. * @return array Menu link created. */ - function addMenuLink($plid = 0, $link = '', $menu_name = 'navigation') { + function addMenuLink($plid = 0, $link = '', $menu_name = 'navigation', $expanded = TRUE) { // View add menu link page. $this->drupalGet("admin/structure/menu/manage/$menu_name/add"); $this->assertResponse(200); @@ -294,7 +294,7 @@ class MenuTestCase extends DrupalWebTestCase { 'link_title' => $title, 'description' => '', 'enabled' => TRUE, // Use this to disable the menu and test. - 'expanded' => TRUE, // Setting this to true should test whether it works when we do the std_user tests. + 'expanded' => $expanded, // Setting this to true should test whether it works when we do the std_user tests. 'parent' => $menu_name . ':' . $plid, 'weight' => '0', ); @@ -345,7 +345,7 @@ class MenuTestCase extends DrupalWebTestCase { if (isset($parent)) { // Verify menu link. $title = $parent['link_title']; - $this->assertText($title, 'Parent menu link was displayed'); + $this->assertLink($title, 0, 'Parent menu link was displayed'); // Verify menu link link. $this->clickLink($title); @@ -355,7 +355,7 @@ class MenuTestCase extends DrupalWebTestCase { // Verify menu link. $title = $item['link_title']; - $this->assertText($title, 'Menu link was displayed'); + $this->assertLink($title, 0, 'Menu link was displayed'); // Verify menu link link. $this->clickLink($title);