Index: token.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/token/token.module,v
retrieving revision 1.7.4.46
diff -u -p -r1.7.4.46 token.module
--- token.module	13 Dec 2010 18:31:23 -0000	1.7.4.46
+++ token.module	13 Dec 2010 21:00:20 -0000
@@ -39,7 +39,7 @@ function token_help($path, $arg) {
  * Return an array of the core modules supported by token.module.
  */
 function _token_core_supported_modules() {
-  return array('node', 'user', 'taxonomy', 'comment');
+  return array('node', 'user', 'taxonomy', 'comment', 'menu', 'book');
 }
 
 /**
@@ -767,3 +767,143 @@ function token_find_duplicate_tokens() {
 
   return $all_tokens;
 }
+
+/**
+ * Get a translated menu link by its mlid, without access checking.
+ *
+ * This function is a copy of menu_link_load() but with its own cache and a
+ * simpler query to load the link. This also skips normal menu link access
+ * checking by using _token_menu_link_translate().
+ *
+ * @param $mlid
+ *   The mlid of the menu item.
+ *
+ * @return
+ *   A menu link translated for rendering.
+ *
+ * @see menu_link_load()
+ * @see _token_menu_link_translate()
+ */
+function token_menu_link_load($mlid) {
+  static $cache = array();
+
+  if (!is_numeric($mlid)) {
+    return FALSE;
+  }
+
+  if (!isset($cache[$mlid])) {
+    $item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid));
+    if (!empty($item)) {
+      _token_menu_link_translate($item);
+    }
+    $cache[$mlid] = $item;
+  }
+
+  return $cache[$mlid];
+}
+
+/**
+ * Get a translated book menu link by its mlid, without access checking.
+ *
+ * This function is a copy of book_link_load() but with its own cache and a
+ * simpler query to load the link. This also skips normal menu link access
+ * checking by using _token_menu_link_translate().
+ *
+ * @param $mlid
+ *   The mlid of the book menu item.
+ *
+ * @return
+ *   A book menu link translated for rendering.
+ *
+ * @see book_link_load()
+ * @see _token_menu_link_translate()
+ */
+function token_book_link_load($mlid) {
+  static $cache = array();
+
+  if (!is_numeric($mlid)) {
+    return FALSE;
+  }
+
+  if (!isset($cache[$mlid])) {
+    $item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid));
+    if (!empty($item)) {
+      _token_menu_link_translate($item);
+    }
+    $cache[$mlid] = $item;
+  }
+
+  return $cache[$mlid];
+}
+
+function _token_menu_link_translate(&$item) {
+  $map = array();
+
+  if (!is_array($item['options'])) {
+    $item['options'] = unserialize($item['options']);
+  }
+
+  if ($item['external']) {
+    $item['access'] = 1;
+    $item['href'] = $item['link_path'];
+    $item['title'] = $item['link_title'];
+    $item['localized_options'] = $item['options'];
+  }
+  else {
+    $map = explode('/', $item['link_path']);
+    _menu_link_map_translate($map, $item['to_arg_functions']);
+    $item['href'] = implode('/', $map);
+
+    // Note - skip callbacks without real values for their arguments.
+    if (strpos($item['href'], '%') !== FALSE) {
+      $item['access'] = FALSE;
+      return FALSE;
+    }
+
+    $item['access'] = TRUE;
+    _menu_item_localize($item, $map, TRUE);
+  }
+
+  // Allow other customizations - e.g. adding a page-specific query string to the
+  // options array. For performance reasons we only invoke this hook if the link
+  // has the 'alter' flag set in the options array.
+  if (!empty($item['options']['alter'])) {
+    drupal_alter('translated_menu_link', $item, $map);
+  }
+
+  return $map;
+}
+
+/**
+ * Find all ancestors of a given menu link ID.
+ *
+ * @param $mlid
+ *   A menu link ID.
+ *
+ * @return
+ *   An array of menu links from token_menu_link_load() with the root link
+ *   first, and the menu link with ID $mlid last.
+ */
+function token_menu_link_get_parents_all($mlid) {
+  $parents = array();
+
+  while (!empty($mlid)) {
+    $link = token_menu_link_load($mlid);
+    array_unshift($parents, $link);
+    $mlid = $link['plid'];
+  }
+
+  return $parents;
+}
+
+/**
+ * Deprecated. Use the raw return value of token_menu_link_get_parents_all() instead.
+ */
+function _menu_titles($menu_link, $nid) {
+  $titles = array();
+  $parents = token_menu_link_get_parents_all($menu_link['mlid']);
+  foreach ($parents as $mlid => $parent) {
+    $titles[] = $parent['title'];
+  }
+  return $titles;
+}
Index: token.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/token/token.test,v
retrieving revision 1.1.2.19
diff -u -p -r1.1.2.19 token.test
--- token.test	30 Nov 2010 22:55:27 -0000	1.1.2.19
+++ token.test	13 Dec 2010 21:00:20 -0000
@@ -317,16 +317,25 @@ class TokenMenuTestCase extends TokenTes
 
   function testMenuTokens() {
     $root_link = array(
-      'link_path' => '<front>',
-      'link_title' => 'Front link',
+      'link_path' => 'root',
+      'link_title' => 'Root link',
       'menu_name' => 'primary-links',
     );
     menu_link_save($root_link);
 
+    // Add another link with the root link as the parent
+    $parent_link = array(
+      'link_path' => 'root/parent',
+      'link_title' => 'Parent link',
+      'menu_name' => 'primary-links',
+      'plid' => $root_link['mlid'],
+    );
+    menu_link_save($parent_link);
+
     $node_link = array(
       'enabled' => TRUE,
       'link_title' => 'Node link',
-      'plid' => $root_link['mlid'],
+      'plid' => $parent_link['mlid'],
       'customized' => 0,
     );
     $node = $this->drupalCreateNode(array('menu' => $node_link));
@@ -334,10 +343,14 @@ class TokenMenuTestCase extends TokenTes
     // Test [node:menu] tokens.
     $tokens = array(
       'menu' => 'Primary links',
+      'menu-raw' => 'Primary links',
       'menu-link-title' => 'Node link',
+      'menu-link-title-raw' => 'Node link',
       'menu-link-mlid' => $node->menu['mlid'],
       'menu-link-plid' => $node->menu['plid'],
-      'menu-link-plid' => $root_link['mlid'],
+      'menu-link-plid' => $parent_link['mlid'],
+      'menupath' => 'Root link/Parent link/Node link',
+      'menupath-raw' => 'Root link/Parent link/Node link',
     );
     $this->assertTokens('node', $node, $tokens);
   }
Index: token_node.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/token/Attic/token_node.inc,v
retrieving revision 1.5.4.35
diff -u -p -r1.5.4.35 token_node.inc
--- token_node.inc	13 Dec 2010 18:31:23 -0000	1.5.4.35
+++ token_node.inc	13 Dec 2010 21:00:21 -0000
@@ -64,41 +64,6 @@ function node_token_values($type, $objec
         $values += token_get_date_token_values($node->changed, 'mod-');
       }
 
-      // Try to get the menu data.
-      $mlid = db_result(db_query("SELECT mlid FROM {menu_links} WHERE link_path = '%s'", 'node/'. $node->nid));
-
-      // Now get the menu related information.
-      if (!empty($mlid) || !empty($node->menu['mlid']) || !empty($node->menu['plid'])) {
-        $menu_link = menu_link_load($mlid);
-        $menus = menu_get_menus();
-        $menu = isset($menus[$menu_link['menu_name']]) ? $menus[$menu_link['menu_name']] : '';
-        $trail_raw = _menu_titles($menu_link, $node->nid);
-
-        $trail = array();
-        foreach ($trail_raw as $title) {
-          $trail[] = check_plain($title);
-        }
-
-        $values['menupath']            = !empty($options['pathauto']) ? $trail : implode('/', $trail);
-        $values['menupath-raw']        = !empty($options['pathauto']) ? $trail_raw : implode('/', $trail_raw);
-        $values['menu']                = check_plain($menu);
-        $values['menu-raw']            = $menu;
-        $values['menu-link-title']     = check_plain($menu_link['title']);
-        $values['menu-link-title-raw'] = $menu_link['link_title'];
-        $values['menu-link-mlid']      = $menu_link['mlid'];
-        $values['menu-link-plid']      = $menu_link['plid'];
-      }
-      else {
-        $values['menu']                = '';
-        $values['menu-raw']            = '';
-        $values['menupath']            = '';
-        $values['menupath-raw']        = '';
-        $values['menu-link-title']     = '';
-        $values['menu-link-title-raw'] = '';
-        $values['menu-link-mlid']      = '';
-        $values['menu-link-plid']      = '';
-      }
-
       // And now taxonomy, which is a bit more work. This code is adapted from
       // pathauto's handling code; it's intended for compatibility with it.
       if (module_exists('taxonomy') && !empty($node->taxonomy) && is_array($node->taxonomy)) {
@@ -209,106 +174,134 @@ function node_token_list($type = 'all') 
       // $tokens['node']['catpath']        = t("Full taxonomy tree for the topmost term");
     }
 
-    if (module_exists('menu')) {
-      $tokens['node']['menu']                = t("The name of the menu the node belongs to.");
-      $tokens['node']['menu-raw']            = t("The name of the menu the node belongs to.");
-      $tokens['node']['menupath']            = t("The menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /.");
-      $tokens['node']['menupath-raw']        = t("The unfiltered menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /.");
-      $tokens['node']['menu-link-title']     = t("The text used in the menu as link text for this item.");
-      $tokens['node']['menu-link-title-raw'] = t("The unfiltered text used in the menu as link text for this item.");
-      $tokens['node']['menu-link-mlid']      = t("The unique ID of the node's menu link.");
-      $tokens['node']['menu-link-plid']      = t("The unique ID of the node's menu link parent.");
-    }
-
     return $tokens;
   }
 }
 
 /**
- * Return an array of titles for a menu, from the top down to the specified node
- *
- * @param $menu_link
- *   Fully loaded menu link, of node to which a path is desired.
- * @param $nid
- *   Id of node.
- * @return
- *   An array of titles through the specified node
+ * Implements hook_token_list() on behalf of menu.module.
  */
-function _menu_titles($menu_link, $nid) {
-  $tree = menu_tree_all_data($menu_link['menu_name'], $menu_link);
+function menu_token_list($type = 'all') {
+  $tokens = array();
 
-  // Get mlid of all nodes in path - top-most parent to leaf node.
-  $parents = array();
-  for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
-    if ($menu_link["p$i"]) {
-      $parents[] = $menu_link["p$i"];
-    }
+  if ($type == 'node' || $type == 'all') {
+    $tokens['node']['menu']                = t("The name of the menu the node belongs to.");
+    $tokens['node']['menu-raw']            = t("The name of the menu the node belongs to.");
+    $tokens['node']['menupath']            = t("The menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /.");
+    $tokens['node']['menupath-raw']        = t("The unfiltered menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /.");
+    $tokens['node']['menu-link-title']     = t("The text used in the menu as link text for this item.");
+    $tokens['node']['menu-link-title-raw'] = t("The unfiltered text used in the menu as link text for this item.");
+    $tokens['node']['menu-link-mlid']      = t("The unique ID of the node's menu link.");
+    $tokens['node']['menu-link-plid']      = t("The unique ID of the node's menu link parent.");
   }
 
-  // Build the titles in this hierarchy.
-  $titles = array();
-  $current = array_shift($tree);
-  while ($current) {
-    if (in_array($current['link']['mlid'], $parents)) {
-      $titles[] = $current['link']['title'];
-      if ($current['link']['href'] == "node/". $nid) {
-        break;
-      }
-      // Go deeper in tree hierarchy.
-      $tree = $current['below'];
-    }
-    // Go to next sibling at same level in tree hierarchy.
-    $current = $tree ? array_shift($tree) : NULL;
-  }
-  return $titles;
+  return $tokens;
 }
 
 /**
- * Implementation of hook_token_values() for book nodes
+ * Implements hook_token_values() on behalf of menu.module.
  */
-function book_token_values($type, $object = NULL, $options = array()) {
-  if ($type == 'node') {
+function menu_token_values($type, $object = NULL, $options = array()) {
+  $values = array();
+
+  if ($type == 'node' && !empty($object)) {
     $node = $object;
 
-    // Initialize some variables to empty.
-    $tokens['book'] = '';
-    $tokens['book-raw'] = '';
-    $tokens['book_id'] = '';
-    $tokens['bookpath'] = '';
-    $tokens['bookpath-raw'] = '';
-
-    if (!empty($node->book['menu_name'])) {
-      $trail_raw = _menu_titles($node->book, $node->nid);
-      $book_raw = $trail_raw[0];
-      $book = check_plain($book_raw);
-      // For book paths, we don't include the current node's title (last in
-      // the array) in the trail.
-      array_pop($trail_raw);
-      $trail = array();
-      foreach ($trail_raw as $title) {
-        $trail[] = check_plain($title);
-      }
-      $tokens = array();
-      $tokens['book'] = $book;
-      $tokens['book-raw'] = $book_raw;
-      $tokens['book_id'] = $node->book['bid'];
-      $tokens['bookpath'] = !empty($options['pathauto']) ? $trail : implode('/', $trail);
-      $tokens['bookpath-raw'] = !empty($options['pathauto']) ? $trail_raw : implode('/', $trail_raw);
+    // Initialize tokens to empty strings.
+    $values['menu']                = '';
+    $values['menu-raw']            = '';
+    $values['menupath']            = '';
+    $values['menupath-raw']        = '';
+    $values['menu-link-title']     = '';
+    $values['menu-link-title-raw'] = '';
+    $values['menu-link-mlid']      = '';
+    $values['menu-link-plid']      = '';
+
+    if (!isset($node->menu)) {
+      // Nodes do not have their menu links loaded via menu_nodeapi($node, 'load').
+      menu_nodeapi($node, 'prepare');
+    }
+
+    // Now get the menu related information.
+    if (!empty($node->menu['mlid'])) {
+      $menu_link = token_menu_link_load($node->menu['mlid']);
+      $menus = menu_get_menus();
+      $menu = isset($menus[$menu_link['menu_name']]) ? $menus[$menu_link['menu_name']] : '';
+
+      $parents = token_menu_link_get_parents_all($menu_link['mlid']);
+      $trail_raw = array();
+      foreach ($parents as $parent) {
+        $trail_raw[] = $parent['title'];
+      }
+      $trail = array_map('check_plain', $trail_raw);
+
+      $values['menu']                = check_plain($menu);
+      $values['menu-raw']            = $menu;
+      $values['menupath']            = !empty($options['pathauto']) ? $trail : implode('/', $trail);
+      $values['menupath-raw']        = !empty($options['pathauto']) ? $trail_raw : implode('/', $trail_raw);
+      $values['menu-link-title']     = check_plain($menu_link['title']);
+      $values['menu-link-title-raw'] = $menu_link['title'];
+      $values['menu-link-mlid']      = $menu_link['mlid'];
+      $values['menu-link-plid']      = $menu_link['plid'];
     }
-
-    return $tokens;
   }
+
+  return $values;
 }
 
+/**
+ * Implements hook_token_list() on behalf of book.module.
+ */
 function book_token_list($type) {
+  $tokens = array();
+
   if ($type == 'node' || $type == 'all') {
-    $list['book']['book']           = t("The title of the node's book parent.");
-    $list['book']['book_id']        = t("The id of the node's book parent.");
-    $list['book']['bookpath']       = t("The titles of all parents in the node's book hierarchy.");
+    $tokens['book']['book']         = t("The title of the node's book parent.");
+    $tokens['book']['book-raw']     = t("The title of the node's book parent.");
+    $tokens['book']['book_id']      = t("The id of the node's book parent.");
+    $tokens['book']['bookpath']     = t("The titles of all parents in the node's book hierarchy.");
+    $tokens['book']['bookpath-raw'] = t("The titles of all parents in the node's book hierarchy.");
+  }
 
-    $list['book']['book-raw']       = t("The unfiltered title of the node's book parent.");
-    $list['book']['bookpath-raw']   = t("The unfiltered titles of all parents in the node's book hierarchy.");
+  return $tokens;
+}
 
-    return $list;
+/**
+ * Implements hook_token_values() on behalf of book.module.
+ */
+function book_token_values($type, $object = NULL, $options = array()) {
+  $values = array();
+
+  if ($type == 'node' && !empty($object)) {
+    $node = $object;
+
+    // Initialize tokens to empty strings.
+    $values['book'] = '';
+    $values['book-raw'] = '';
+    $values['book_id'] = '';
+    $values['bookpath'] = '';
+    $values['bookpath-raw'] = '';
+
+    if (!empty($node->book['mlid'])) {
+      // Exclude the current node's title from the book path trail (start with
+      // the book link's plid rather than mlid).
+      $parents = token_menu_link_get_parents_all($node->book['plid']);
+      $trail_raw = array();
+      foreach ($parents as $parent) {
+        $trail_raw[] = $parent['title'];
+      }
+      $trail = array_map('check_plain', $trail_raw);
+
+      // Load the root book page.
+      $root = token_menu_link_load($node->book['p1']);
+
+      $values['book'] = check_plain($root['title']);
+      $values['book-raw'] = $root['title'];
+      $values['book_id'] = $node->book['bid'];
+      $values['bookpath'] = !empty($options['pathauto']) ? $trail : implode('/', $trail);
+      $values['bookpath-raw'] = !empty($options['pathauto']) ? $trail_raw : implode('/', $trail_raw);
+    }
   }
+
+  return $values;
 }
