Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.498
diff -u -r1.498 book.module
--- modules/book/book.module	2 Jul 2009 04:27:22 -0000	1.498
+++ modules/book/book.module	4 Jul 2009 06:07:03 -0000
@@ -1190,8 +1190,8 @@
       for ($i = 1; $i <= MENU_MAX_DEPTH; ++$i) {
         $query->orderBy("p$i");
       }
-
-      $data['tree'] = menu_tree_data($query->execute(), array(), $item['depth']);
+      $items = $query->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
+      $data['tree'] = menu_tree_data($items, $item['plid']);
       $data['node_links'] = array();
       menu_tree_collect_node_links($data['tree'], $data['node_links']);
       // Compute the real cid for book subtree data.
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.328
diff -u -r1.328 menu.inc
--- includes/menu.inc	10 Jun 2009 21:52:36 -0000	1.328
+++ includes/menu.inc	4 Jul 2009 06:07:03 -0000
@@ -370,6 +370,7 @@
       ->execute()->fetchAssoc();
     if ($router_item) {
       $map = _menu_translate($router_item, $original_map);
+      $router_item['original_map'] = $original_map;
       if ($map === FALSE) {
         $router_items[$path] = FALSE;
         return FALSE;
@@ -699,6 +700,16 @@
   }
   else {
     $map = explode('/', $item['link_path']);
+
+    // Replace wildcards in the active trail map using the current path.
+    if (!empty($item['in_active_trail']) && isset($item['active_map'])) {
+      foreach ($map as $index => $part) {
+        if ($part == '%') {
+          $map[$index] = $item['active_map'][$index];
+        }
+      }
+    }
+
     _menu_link_map_translate($map, $item['to_arg_functions']);
     $item['href'] = implode('/', $map);
 
@@ -864,7 +875,9 @@
     }
     // 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.
+      // 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');
       $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
       $query->fields('ml');
@@ -902,10 +915,10 @@
         // Get all links in this menu.
         $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.
-      $data['tree'] = menu_tree_data($query->execute(), $parents);
+
+      // Run the query and build the tree.
+      $items = $query->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
+      $data['tree'] = menu_tree_data($items, 0, $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.
@@ -963,6 +976,7 @@
         // Build and run the query, and build the tree.
         if ($item['access']) {
           // Check whether a menu link exists that corresponds to the current path.
+          $args[] = $item['path'];
           $args[] = $item['href'];
           if (drupal_is_front_page()) {
             $args[] = '<front>';
@@ -980,9 +994,10 @@
             ))
             ->condition('menu_name', $menu_name)
             ->condition('link_path', $args, 'IN')
+            ->orderBy('link_path', 'DESC')
             ->execute()->fetchAssoc();
 
-          if (empty($parents)) {
+          if (empty($parents) && ($item['type'] & MENU_IS_LOCAL_TASK)) {
             // 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')
@@ -1056,7 +1071,8 @@
         }
         $query->condition('ml.menu_name', $menu_name);
         $query->condition('ml.plid', $args, 'IN');
-        $data['tree'] = menu_tree_data($query->execute(), $parents);
+        $items = $query->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
+        $data['tree'] = menu_tree_data($items, 0, $parents, $item['original_map']);
         $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.
@@ -1163,82 +1179,50 @@
 /**
  * Build the data representing a menu tree.
  *
- * @param $result
- *   The database result.
+ * @param $links
+ *   An array of all links within the menu.
+ * @param $plid
+ *   (optional) A parent menu link ID that that will be the root of the returned
+ *   tree. Defaults to 0, which is the root of the menu.
  * @param $parents
- *   An array of the plid values that represent the path from the current page
- *   to the root of the menu tree.
- * @param $depth
- *   The depth of the current menu tree.
+ *   (optional) An array of the plid values that represent the active path from
+ *   the current page to the root of the menu tree. These parents are used to
+ *   calculate the active trail used by the breadcrumb.
+ * @param $map
+ *   (optional) An array of arguments that map to the active trail within this
+ *   tree. These values are used to expand the titles and URLs within the active
+ *   trail when one of the parents contains a wildcard placeholder.
  * @return
  *   See menu_tree_page_data for a description of the data structure.
  */
-function menu_tree_data($result = NULL, $parents = array(), $depth = 1) {
-  list(, $tree) = _menu_tree_data($result, $parents, $depth);
+function menu_tree_data($links, $plid = 0, $parents = array(), $map = array()) {
+  $tree = array();
+  _menu_tree_data($tree, $links, $plid, $parents, $map);
   return $tree;
 }
 
 /**
  * Recursive helper function to build the data representing a menu tree.
- *
- * The function is a bit complex because the rendering of an item depends on
- * the next menu item. So we are always rendering the element previously
- * processed not the current one.
  */
-function _menu_tree_data($result, $parents, $depth, $previous_element = '') {
-  $remnant = NULL;
-  $tree = array();
-  foreach ($result as $item) {
+function _menu_tree_data(&$tree, $links, $plid, $parents, $map) {
+  foreach ($links as $item) {
     $item = is_object($item) ? get_object_vars($item) : $item;
     // We need to determine if we're on the path to root so we can later build
     // the correct active trail and breadcrumb.
     $item['in_active_trail'] = in_array($item['mlid'], $parents);
-    // The current item is the first in a new submenu.
-    if ($item['depth'] > $depth) {
-      // _menu_tree returns an item and the menu tree structure.
-      list($item, $below) = _menu_tree_data($result, $parents, $item['depth'], $item);
-      if ($previous_element) {
-        $tree[$previous_element['mlid']] = array(
-          'link' => $previous_element,
-          'below' => $below,
-        );
-      }
-      else {
-        $tree = $below;
-      }
-      // We need to fall back one level.
-      if (!isset($item) || $item['depth'] < $depth) {
-        return array($item, $tree);
-      }
-      // This will be the link to be output in the next iteration.
-      $previous_element = $item;
-    }
-    // We are at the same depth, so we use the previous element.
-    elseif ($item['depth'] == $depth) {
-      if ($previous_element) {
-        // Only the first time.
-        $tree[$previous_element['mlid']] = array(
-          'link' => $previous_element,
-          'below' => FALSE,
-        );
-      }
-      // This will be the link to be output in the next iteration.
-      $previous_element = $item;
+
+    if ($item['in_active_trail']) {
+      $item['active_map'] = $map;
     }
-    // The submenu ended with the previous item, so pass back the current item.
-    else {
-      $remnant = $item;
-      break;
+
+    if ($item['plid'] == $plid) {
+      $tree[$item['mlid']] = array(
+        'link' => $item,
+        'below' => array(),
+      );
+      _menu_tree_data($tree[$item['mlid']]['below'], $links, $item['mlid'], $parents, $map);
     }
   }
-  if ($previous_element) {
-    // We have one more link dangling.
-    $tree[$previous_element['mlid']] = array(
-      'link' => $previous_element,
-      'below' => FALSE,
-    );
-  }
-  return array($remnant, $tree);
 }
 
 /**
@@ -1687,16 +1671,24 @@
     // Determine if the current page is a link in any of the active menus.
     if ($menu_names) {
       $query = db_select('menu_links', 'ml');
-      $query->fields('ml', array('menu_name'));
-      $query->condition('ml.link_path', $item['href']);
+      $query->fields('ml', array('menu_name', 'link_path'));
+      $query->condition(db_or()
+        ->condition('ml.link_path', $item['path'])
+        ->condition('ml.link_path', $item['href'])
+      );
       $query->condition('ml.menu_name', $menu_names, 'IN');
       $result = $query->execute();
-      $found = array();
+
+      // We match on both dynamic paths containing % wildcards and on exact
+      // paths. Exact paths will take precedence over dynamic values.
+      $menu_matches = array('exact' => array(), 'dynamic' => array());
       foreach ($result as $menu) {
-        $found[] = $menu->menu_name;
+        $match_type = (strpos($menu->link_path, '%') === FALSE) ? 'exact' : 'dynamic';
+        $menu_matches[$match_type][] = $menu->menu_name;
       }
+      $menu_matches = empty($menu_matches['exact']) ? $menu_matches['dynamic'] : $menu_matches['exact'];
       // The $menu_names array is ordered, so take the first one that matches.
-      $name = current(array_intersect($menu_names, $found));
+      $name = current(array_intersect($menu_names, $menu_matches));
       if ($name !== FALSE) {
         $tree = menu_tree_page_data($name);
         list($key, $curr) = each($tree);
@@ -1749,9 +1741,12 @@
   $item = menu_get_item();
   if ($item && $item['access']) {
     $active_trail = menu_get_active_trail();
-
+    // Do not include the front page twice in the breadcrumb.
+    $front_url = variable_get('site_frontpage', 'node');
     foreach ($active_trail as $parent) {
-      $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
+      if ($parent['href'] != $front_url) {
+        $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
+      }
     }
     $end = end($active_trail);
 
@@ -2134,6 +2129,11 @@
       $query->condition('menu_name', $item['menu_name']);
     }
 
+    // Ensure that the parent is not a hidden menu entry when editing items.
+    if (isset($item['mlid'])) {
+      $query->condition('hidden', 0, '>=');
+    }
+
     // Find the parent - it must be unique.
     $parent_path = $item['link_path'];
     do {
@@ -2153,7 +2153,7 @@
   $menu_name = $item['menu_name'];
   // Menu callbacks need to be in the links table for breadcrumbs, but can't
   // be parents if they are generated directly from a router item.
-  if (empty($parent['mlid']) || $parent['hidden'] < 0) {
+  if (empty($parent['mlid'])) {
     $item['plid'] =  0;
   }
   else {
Index: modules/menu/menu.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v
retrieving revision 1.52
diff -u -r1.52 menu.admin.inc
--- modules/menu/menu.admin.inc	29 Jun 2009 14:24:56 -0000	1.52
+++ modules/menu/menu.admin.inc	4 Jul 2009 06:07:04 -0000
@@ -47,8 +47,9 @@
     FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
     WHERE ml.menu_name = :menu
     ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
-  $result = db_query($sql, array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
-  $tree = menu_tree_data($result);
+  $result = db_query($sql, array(':menu' => $menu['menu_name']));
+  $items = $result->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
+  $tree = menu_tree_data($items);
   $node_links = array();
   menu_tree_collect_node_links($tree, $node_links);
   // We indicate that a menu administrator is running the menu access check.
