diff --git includes/menu.inc includes/menu.inc
index 1daee3c..080cdac 100644
--- includes/menu.inc
+++ includes/menu.inc
@@ -370,6 +370,7 @@ function menu_get_item($path = NULL, $router_item = NULL) {
       ->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;
@@ -836,20 +837,25 @@ function menu_tree_output($tree) {
  *
  * @param $menu_name
  *   The named menu links to return
- * @param $item
+ * @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
+ *   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, $item = NULL) {
+function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
   $tree = &drupal_static(__FUNCTION__, array());
 
   // Use $mlid as a flag for whether the data being loaded is for the whole tree.
-  $mlid = isset($item['mlid']) ? $item['mlid'] : 0;
-  // Generate a cache ID (cid) specific for this $menu_name and $item.
-  $cid = 'links:' . $menu_name . ':all-cid:' . $mlid;
+  $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
+  // Generate a cache ID (cid) specific for this $menu_name, $item, and depth.
+  $cid = 'links:' . $menu_name . ':all-cid:' . $mlid . ':' . (int)$max_depth;
 
   if (!isset($tree[$cid])) {
     // If the static variable doesn't have the data, check {cache_menu}.
@@ -885,18 +891,20 @@ function menu_tree_all_data($menu_name, $item = NULL) {
         $query->orderBy('p' . $i, 'ASC');
       }
       $query->condition('ml.menu_name', $menu_name);
-
+      if (isset($max_depth)) {
+        $query->condition('ml.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);
         for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
-          $args[] = $item["p$i"];
+          $args[] = $link["p$i"];
         }
         $args = array_unique($args);
         $query->condition('ml.plid', $args, 'IN');
         $parents = $args;
-        $parents[] = $item['mlid'];
+        $parents[] = $link['mlid'];
       }
       else {
         // Get all links in this menu.
@@ -905,7 +913,11 @@ function menu_tree_all_data($menu_name, $item = NULL) {
       // 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);
+      $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.
@@ -932,6 +944,9 @@ function menu_tree_all_data($menu_name, $item = NULL) {
  *
  * @param $menu_name
  *   The named menu links to return
+ * @param $max_depth
+ *   Optional maximum depth of links to retrieve.
+ *
  * @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.
@@ -939,13 +954,16 @@ function menu_tree_all_data($menu_name, $item = NULL) {
  *   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) {
+function menu_tree_page_data($menu_name, $max_depth = NULL) {
   $tree = &drupal_static(__FUNCTION__, array());
 
   // Load the menu item corresponding to the current page.
   if ($item = menu_get_item()) {
+    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-cid:' . $item['href'] . ':' . (int)$item['access'];
+    $cid = 'links:' . $menu_name . ':page-cid:' . $item['href'] . ':' . (int)$item['access'] . ':' . (int)$max_depth;
 
     if (!isset($tree[$cid])) {
       // If the static variable doesn't have the data, check {cache_menu}.
@@ -1056,7 +1074,15 @@ function menu_tree_page_data($menu_name) {
         }
         $query->condition('ml.menu_name', $menu_name);
         $query->condition('ml.plid', $args, 'IN');
-        $data['tree'] = menu_tree_data($query->execute(), $parents);
+        if (isset($max_depth)) {
+          $query->condition('ml.depth', $max_depth, '<=');
+        }
+
+        $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.
@@ -1162,8 +1188,8 @@ function _menu_tree_check_access(&$tree) {
 /**
  * Build the data representing a menu tree.
  *
- * @param $result
- *   The database result.
+ * @param $links
+ *   An array of links (associative arrays) ordered by p1..p9.
  * @param $parents
  *   An array of the plid values that represent the path from the current page
  *   to the root of the menu tree.
@@ -1172,71 +1198,41 @@ function _menu_tree_check_access(&$tree) {
  * @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);
-  return $tree;
+function menu_tree_data($links, $parents = array(), $depth = 1) {
+  $links = array_reverse($links);
+  return _menu_tree_data($links, $parents, $depth);
 }
 
 /**
  * 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.
+ * The function is a bit complex because the rendering of a link depends on
+ * the next menu link.
  */
-function _menu_tree_data($result, $parents, $depth, $previous_element = '') {
+function _menu_tree_data(&$links, $parents, $depth) {
   $remnant = NULL;
   $tree = array();
-  foreach ($result as $item) {
+  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 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;
-      }
+    $next = end($links);
+    $tree[$item['mlid']] = array(
+      'link' => $item,
+      'below' => array(),
+    );
+    // The next item is the first in a new submenu.
+    if ($next && $next['depth'] > $depth) {
+      // _menu_tree_data returns the sub-tree.
+      $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']);
       // 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;
     }
-    // The submenu ended with the previous item, so pass back the current item.
-    else {
-      $remnant = $item;
+    elseif (!$next || $next['depth'] < $depth) {
+      // Exit loop and return.
       break;
     }
   }
-  if ($previous_element) {
-    // We have one more link dangling.
-    $tree[$previous_element['mlid']] = array(
-      'link' => $previous_element,
-      'below' => FALSE,
-    );
-  }
-  return array($remnant, $tree);
+  return $tree;
 }
 
 /**
@@ -1404,7 +1400,7 @@ function menu_navigation_links($menu_name, $level = 0) {
   }
 
   // Get the menu hierarchy for the current page.
-  $tree = menu_tree_page_data($menu_name);
+  $tree = menu_tree_page_data($menu_name, $level + 1);
 
   // Go down the active trail until the right level is reached.
   while ($level-- > 0 && $tree) {
diff --git modules/book/book.module modules/book/book.module
index bbd900c..e0e0e26 100644
--- modules/book/book.module
+++ modules/book/book.module
@@ -592,14 +592,15 @@ function book_update_bid($book_link) {
  *   A linear array of menu links in the order that the links are shown in the
  *   menu, so the previous and next pages are the elements before and after the
  *   element corresponding to $node. The children of $node (if any) will come
- *   immediately after it in the array.
+ *   immediately after it in the array, and links will only be fetched as deep
+ *   as one level deeper than $book_link.
  */
 function book_get_flat_menu($book_link) {
   $flat = &drupal_static(__FUNCTION__, array());
 
   if (!isset($flat[$book_link['mlid']])) {
     // Call menu_tree_all_data() to take advantage of the menu system's caching.
-    $tree = menu_tree_all_data($book_link['menu_name'], $book_link);
+    $tree = menu_tree_all_data($book_link['menu_name'], $book_link, $book_link['depth'] + 1);
     $flat[$book_link['mlid']] = array();
     _book_flatten_menu($tree, $flat[$book_link['mlid']]);
   }
@@ -1148,16 +1149,16 @@ function book_link_load($mlid) {
  * The root of the subtree will be the link passed as a parameter, so the
  * returned tree will contain this item and all its descendents in the menu tree.
  *
- * @param $item
+ * @param $link
  *   A fully loaded menu link.
  * @return
  *   An subtree of menu links in an array, in the order they should be rendered.
  */
-function book_menu_subtree_data($item) {
+function book_menu_subtree_data($link) {
   $tree = &drupal_static(__FUNCTION__, array());
 
-  // Generate a cache ID (cid) specific for this $menu_name and $item.
-  $cid = 'links:' . $item['menu_name'] . ':subtree-cid:' . $item['mlid'];
+  // Generate a cache ID (cid) specific for this $menu_name and $link.
+  $cid = 'links:' . $link['menu_name'] . ':subtree-cid:' . $link['mlid'];
 
   if (!isset($tree[$cid])) {
     $cache = cache_get($cid, 'cache_menu');
@@ -1180,15 +1181,18 @@ function book_menu_subtree_data($item) {
       $query->fields($book_alias);
       $query->fields($menu_router_alias, array('load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'title', 'title_callback', 'title_arguments', 'type'));
       $query->fields('ml');
-      $query->condition('menu_name', $item['menu_name']);
-      for ($i = 1; $i <= MENU_MAX_DEPTH && $item["p$i"]; ++$i) {
-        $query->condition("p$i", $item["p$i"]);
+      $query->condition('menu_name', $link['menu_name']);
+      for ($i = 1; $i <= MENU_MAX_DEPTH && $link["p$i"]; ++$i) {
+        $query->condition("p$i", $link["p$i"]);
       }
       for ($i = 1; $i <= MENU_MAX_DEPTH; ++$i) {
         $query->orderBy("p$i");
       }
-
-      $data['tree'] = menu_tree_data($query->execute(), array(), $item['depth']);
+      $links = array();
+      foreach ($query->execute() as $item) {
+        $links[] = $item;
+      }
+      $data['tree'] = menu_tree_data($links, array(), $link['depth']);
       $data['node_links'] = array();
       menu_tree_collect_node_links($data['tree'], $data['node_links']);
       // Compute the real cid for book subtree data.
diff --git modules/book/book.test modules/book/book.test
index 0e8e37b..ccf3d3e 100644
--- modules/book/book.test
+++ modules/book/book.test
@@ -110,7 +110,7 @@ class BookTestCase extends DrupalWebTestCase {
 
     // Check previous, up, and next links.
     if ($previous) {
-      $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => 'page-previous', 'title' => t('Go to previous page')))), t('Prevoius page link found.'));
+      $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => 'page-previous', 'title' => t('Go to previous page')))), t('Previous page link found.'));
     }
 
     if ($up) {
diff --git modules/menu/menu.admin.inc modules/menu/menu.admin.inc
index 4640990..a86c9dc 100644
--- modules/menu/menu.admin.inc
+++ modules/menu/menu.admin.inc
@@ -48,7 +48,11 @@ function menu_overview_form(&$form_state, $menu) {
     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);
+  $links = array();
+  foreach ($result as $item) {
+    $links[] = $item;
+  }
+  $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.
