? modules/system/menu-update.php
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.167
diff -u -p -r1.167 menu.inc
--- includes/menu.inc	22 May 2007 05:52:16 -0000	1.167
+++ includes/menu.inc	24 May 2007 12:05:12 -0000
@@ -149,11 +149,6 @@ define('MENU_SITE_OFFLINE', 4);
  * @} End of "Menu status codes".
  */
 
-
-/**
- * @} End of "Menu operations."
- */
-
 /**
  * @Name Menu tree parameters
  * @{
@@ -284,21 +279,21 @@ function menu_get_item($path = NULL) {
     $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
     list($ancestors, $placeholders) = menu_get_ancestors($parts);
 
-    if ($item = db_fetch_object(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
+    if ($item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
 
       $map = _menu_translate($item, $original_map);
       if ($map === FALSE) {
         $items[$path] = FALSE;
         return FALSE;
       }
-      if ($item->access) {
-        $item->map = $map;
-        $item->page_arguments = array_merge(menu_unserialize($item->page_arguments, $map), array_slice($parts, $item->number_parts));
+      if ($item['access']) {
+        $item['map'] = $map;
+        $item['page_arguments'] = array_merge(menu_unserialize($item['page_arguments'], $map), array_slice($parts, $item['number_parts']));
       }
     }
     $items[$path] = $item;
   }
-  return drupal_clone($items[$path]);
+  return $items[$path];
 }
 
 /**
@@ -306,11 +301,11 @@ function menu_get_item($path = NULL) {
  */
 function menu_execute_active_handler() {
   if ($item = menu_get_item()) {
-    if ($item->access) {
-      if ($item->file) {
-        include_once($item->file);
+    if ($item['access']) {
+      if ($item['file']) {
+        include_once($item['file']);
       }
-      return call_user_func_array($item->page_callback, $item->page_arguments);
+      return call_user_func_array($item['page_callback'], $item['page_arguments']);
     }
     else {
       return MENU_ACCESS_DENIED;
@@ -330,8 +325,8 @@ function menu_execute_active_handler() {
  *   Returns TRUE for success, FALSE if an object cannot be loaded
  */
 function _menu_load_objects($item, &$map) {
-  if ($item->load_functions) {
-    $load_functions = unserialize($item->load_functions);
+  if ($item['load_functions']) {
+    $load_functions = unserialize($item['load_functions']);
     $path_map = $map;
     foreach ($load_functions as $index => $function) {
       if ($function) {
@@ -339,7 +334,7 @@ function _menu_load_objects($item, &$map
         $return = $function(isset($path_map[$index]) ? $path_map[$index] : '');
         // If callback returned an error or there is no callback, trigger 404.
         if ($return === FALSE) {
-          $item->access = FALSE;
+          $item['access'] = FALSE;
           $map = FALSE;
           return FALSE;
         }
@@ -354,29 +349,29 @@ function _menu_load_objects($item, &$map
  * Check access to a menu item using the access callback
  *
  * @param $item
- *   A menu item object
+ *   A menu item or menu link
  * @param $map
  *   An array of path arguments (ex: array('node', '5'))
  * @return
- *   $item->access becomes TRUE if the item is accessible, FALSE otherwise.
+ *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
  */
 function _menu_check_access(&$item, $map) {
   // Determine access callback, which will decide whether or not the current user has
   // access to this path.
-  $callback = $item->access_callback;
+  $callback = trim($item['access_callback']);
   // Check for a TRUE or FALSE value.
   if (is_numeric($callback)) {
-    $item->access = $callback;
+    $item['access'] = $callback;
   }
   else {
-    $arguments = menu_unserialize($item->access_arguments, $map);
+    $arguments = menu_unserialize($item['access_arguments'], $map);
     // As call_user_func_array is quite slow and user_access is a very common
     // callback, it is worth making a special case for it.
     if ($callback == 'user_access') {
-      $item->access = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
+      $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
     }
     else {
-      $item->access = call_user_func_array($callback, $arguments);
+      $item['access'] = call_user_func_array($callback, $arguments);
     }
   }
 }
@@ -385,29 +380,29 @@ function _menu_item_localize(&$item) {
   // Translate the title to allow storage of English title strings
   // in the database, yet display of them in the language required
   // by the current user.
-  $callback = $item->title_callback;
+  $callback = $item['title_callback'];
   // t() is a special case. Since it is used very close to all the time,
   // we handle it directly instead of using indirect, slower methods.
   if ($callback == 't') {
-    if (empty($item->title_arguments)) {
-      $item->title = t($item->title);
+    if (empty($item['title_arguments'])) {
+      $item['title'] = t($item['title']);
     }
     else {
-      $item->title = t($item->title, unserialize($item->title_arguments));
+      $item['title'] = t($item['title'], unserialize($item['title_arguments']));
     }
   }
   else {
-    if (empty($item->title_arguments)) {
-      $item->title = $callback($item->title);
+    if (empty($item['title_arguments'])) {
+      $item['title'] = $callback($item['title']);
     }
     else {
-      $item->title = call_user_func_array($callback, unserialize($item->title_arguments));
+      $item['title'] = call_user_func_array($callback, unserialize($item['title_arguments']));
     }
   }
 
   // Translate description, see the motivation above.
-  if (!empty($item->description)) {
-    $item->description = t($item->description);
+  if (!empty($item['description'])) {
+    $item['description'] = t($item['description']);
   }
 }
 
@@ -427,16 +422,16 @@ function _menu_item_localize(&$item) {
  * to the language required to generate the current page
  *
  * @param $item
- *   A menu item object
+ *   A menu item
  * @param $map
  *   An array of path arguments (ex: array('node', '5'))
  * @param $to_arg
- *   Execute $item->to_arg_functions or not. Use only if you want to render a
+ *   Execute $item['to_arg_functions'] or not. Use only if you want to render a
  *   path from the menu table, for example tabs.
  * @return
  *   Returns the map with objects loaded as defined in the
- *   $item->load_functions. $item->access becomes TRUE if the item is
- *   accessible, FALSE otherwise. $item->href is set according to the map.
+ *   $item['load_functions. $item['access'] becomes TRUE if the item is
+ *   accessible, FALSE otherwise. $item['href'] is set according to the map.
  *   If an error occurs during calling the load_functions (like trying to load
  *   a non existing node) then this function return FALSE.
  */
@@ -444,21 +439,21 @@ function _menu_translate(&$item, $map, $
   $path_map = $map;
   if (!_menu_load_objects($item, $map)) {
     // An error occurred loading an object.
-    $item->access = FALSE;
+    $item['access'] = FALSE;
     return FALSE;
   }
   if ($to_arg) {
-    _menu_link_map_translate($path_map, $item->to_arg_functions);
+    _menu_link_map_translate($path_map, $item['to_arg_functions']);
   }
 
   // Generate the link path for the page request or local tasks.
-  $link_map = explode('/', $item->path);
-  for ($i = 0; $i < $item->number_parts; $i++) {
+  $link_map = explode('/', $item['path']);
+  for ($i = 0; $i < $item['number_parts']; $i++) {
     if ($link_map[$i] == '%') {
       $link_map[$i] = $path_map[$i];
     }
   }
-  $item->href = implode('/', $link_map);
+  $item['href'] = implode('/', $link_map);
   _menu_check_access($item, $map);
 
   _menu_item_localize($item);
@@ -500,41 +495,44 @@ function _menu_link_map_translate(&$map,
  *   A menu item object
  * @return
  *   Returns the map of path arguments with objects loaded as defined in the
- *   $item->load_functions.
- *   $item->access becomes TRUE if the item is accessible, FALSE otherwise.
- *   $item->href is altered if there is a to_arg function.
+ *   $item['load_functions'].
+ *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
+ *   $item['href'] is generated from link_path, possibly by to_arg functions.
+ *   $item['title'] is generated from link_title, and may be localized.
  */
 function _menu_link_translate(&$item) {
-  if ($item->external) {
-    $item->access = 1;
+  if ($item['external']) {
+    $item['access'] = 1;
     $map = array();
   }
   else {
-    $map = explode('/', $item->href);
-    _menu_link_map_translate($map, $item->to_arg_functions);
-    $item->href = implode('/', $map);
+    $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;
-    }
-    if (!_menu_load_objects($item, $map)) {
-      // An error occured loading an object
-      $item->access = FALSE;
+    if (strpos($item['href'], '%') !== FALSE) {
+      $item['access'] = FALSE;
       return FALSE;
     }
-    // TODO: menu_tree may set this ahead of time for links to nodes
-    if (!isset($item->access)) {
+    // TODO: menu_tree_data may set this ahead of time for links to nodes
+    if (!isset($item['access'])) {
+      if (!_menu_load_objects($item, $map)) {
+        // An error occured loading an object
+        $item['access'] = FALSE;
+        return FALSE;
+      }
       _menu_check_access($item, $map);
     }
     // If the link title matches that of a router item, localize it.
-    if (isset($item->title) && ($item->title == $item->link_title)) {
+    if (isset($item['title']) && ($item['title'] == $item['link_title'])) {
       _menu_item_localize($item);
-      $item->link_title = $item->title;
+    }
+    else {
+      $item['title'] = $item['link_title'];
     }
   }
-  $item->options = unserialize($item->options);
+  $item['options'] = unserialize($item['options']);
 
   return $map;
 }
@@ -554,7 +552,7 @@ function menu_tree($menu_name = 'navigat
   static $menu_output = array();
 
   if (!isset($menu_output[$menu_name])) {
-    $tree = menu_tree_data($menu_name);
+    $tree = menu_tree_page_data($menu_name);
     $menu_output[$menu_name] = menu_tree_output($tree);
   }
   return $menu_output[$menu_name];
@@ -572,13 +570,13 @@ function menu_tree_output($tree) {
   $output = '';
 
   foreach ($tree as $data) {
-    if (!$data['link']->hidden) {
+    if (!$data['link']['hidden']) {
       $link = theme('menu_item_link', $data['link']);
       if ($data['below']) {
-        $output .= theme('menu_item', $link, $data['link']->has_children, menu_tree_output($data['below']));
+        $output .= theme('menu_item', $link, $data['link']['has_children'], $data['link']['in_active_trail'], menu_tree_output($data['below']));
       }
       else {
-        $output .= theme('menu_item', $link, $data['link']->has_children);
+        $output .= theme('menu_item', $link, $data['link']['has_children'], $data['link']['in_active_trail']);
       }
     }
   }
@@ -586,6 +584,67 @@ function menu_tree_output($tree) {
 }
 
 /**
+ * Get the data structure representing a named menu tree. Since this can be
+ * the full tree including hidden items, the data returned may be used for 
+ * generating an an admin interface or a select.
+ *
+ * @param $menu_name
+ *   The named menu links to return
+ * @param $item
+ *   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
+ *   represented the current page in a visible menu.
+ * @param $show_hidden
+ *   Show disabled links (includes suggested menu items).
+ * @return
+ *   An array of menu links, in the order they should be rendered. 
+ */
+function menu_tree_all_data($menu_name = 'navigation', $item = NULL, $show_hidden = FALSE) {
+  static $tree = array();
+
+  $mlid = isset($item['mlid']) ? $item['mlid'] : 0;
+  $cid = 'links:'. $menu_name .':all:'. $mlid .':'. (int)$show_hidden;
+
+  if (!isset($tree[$cid])) {
+    $cache = cache_get($cid, 'cache_menu');
+    if ($cache && isset($cache->data)) {
+      $tree[$cid] = $cache->data;
+    }
+    else {
+      if ($mlid) {
+        $args = array(0, $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5']);
+        $args = array_unique($args);
+        $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+        $where = ' AND ml.plid IN ('. $placeholders .')';
+        $parents = $args;
+        $parents[] = $item['mlid'];
+      }
+      else {
+        $where = '';
+        $args = array();
+        $parents = array();
+      }
+      if (!$show_hidden) {
+        $where .= ' AND ml.hidden = 0';
+      }
+      else {
+        $where .= ' AND ml.hidden > 0';
+      }
+      array_unshift($args, $menu_name);
+      list(, $tree[$cid]) = _menu_tree_data(db_query("
+        SELECT *, ml.weight + 50000 AS weight FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
+        WHERE ml.menu_name = '%s'". $where ."
+        ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC", $args), $parents);
+      cache_set($cid, $tree[$cid], 'cache_menu');
+    }
+    // TODO: special case node links and access check via db_rewite_sql()
+    _menu_tree_check_access($tree[$cid]);
+  }
+
+  return $tree[$cid];
+}
+
+/**
  * Get the data structure representing a named menu tree, based on the current
  * page. The tree order is maintained by storing each parent in an invidual
  * field, see http://drupal.org/node/141866 for more.
@@ -599,54 +658,73 @@ function menu_tree_output($tree) {
  *   submenu below the link if there is one and it is a similar list that was
  *   described so far.
  */
-function menu_tree_data($menu_name = 'navigation') {
+function menu_tree_page_data($menu_name = 'navigation') {
   static $tree = array();
 
   if ($item = menu_get_item()) {
-    if (!isset($tree[$menu_name])) {
-      if ($item->access) {
-        $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5 FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $menu_name, $item->href));
-        // We may be on a local task that's not in the links
-        // TODO how do we handle the case like a local task on a specific node in the menu?
-        if (empty($parents)) {
-          $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5 FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $menu_name, $item->tab_root));
-        }
-        $parents[] = '0';
+    $cid = 'links:'. $menu_name .':page:'. $item['href'] .':'. (int)$item['access'];
 
-        $args = $parents = array_unique($parents);
-        $placeholders = implode(', ', array_fill(0, count($args), '%d'));
-        $expanded = variable_get('menu_expanded', array());
-        if (in_array($menu_name, $expanded)) {
-          do {
-            $result = db_query("SELECT mlid FROM {menu_links} WHERE expanded != 0 AND AND has_children != 0 AND menu_name = '%s' AND plid IN (". $placeholders .') AND mlid NOT IN ('. $placeholders .')', array_merge(array($menu_name), $args, $args));
-            while ($item = db_fetch_array($result)) {
-              $args[] = $item['mlid'];
-            }
-            $placeholders = implode(', ', array_fill(0, count($args), '%d'));
-          } while (db_num_rows($result));
-        }
-        array_unshift($args, $menu_name);
+    if (!isset($tree[$cid])) {
+      $cache = cache_get($cid, 'cache_menu');
+      if ($cache && isset($cache->data)) {
+        $tree[$cid] = $cache->data;
       }
-      // Show the root menu for access denied.
       else {
-        $args = array('navigation', '0');
-        $placeholders = '%d';
+        if ($item['access']) {
+          $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['href']));
+          // We may be on a local task that's not in the links
+          // TODO how do we handle the case like a local task on a specific node in the menu?
+          if (empty($parents)) {
+            $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['tab_root']));
+          }
+          $parents[] = '0';
+  
+          $args = $parents = array_unique($parents);
+          $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+          $expanded = variable_get('menu_expanded', array());
+          if (in_array($menu_name, $expanded)) {
+            do {
+              $result = db_query("SELECT mlid FROM {menu_links} WHERE expanded != 0 AND has_children != 0 AND menu_name = '%s' AND plid IN (". $placeholders .') AND mlid NOT IN ('. $placeholders .')', array_merge(array($menu_name), $args, $args));
+              while ($item = db_fetch_array($result)) {
+                $args[] = $item['mlid'];
+              }
+              $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+            } while (db_num_rows($result));
+          }
+          array_unshift($args, $menu_name);
+        }
+        // Show the root menu for access denied.
+        else {
+          $args = array('navigation', '0');
+          $placeholders = '%d';
+        }
+        // LEFT JOIN since there is no match in {menu_router} for an external link.
+        // No need to order by p6 - there is a sort by weight later.
+        list(, $tree[$cid]) = _menu_tree_data(db_query("
+          SELECT *, ml.weight + 50000 AS weight FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
+          WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .") AND ml.hidden = 0
+          ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC", $args), $parents);
+        cache_set($cid, $tree[$cid], 'cache_menu');
       }
-      // LEFT JOIN since there is no match in {menu_router} for an external link.
-      // No need to order by p6 - there is a sort by weight later.
-      list(, $tree[$menu_name]) = _menu_tree_data(db_query("
-        SELECT *, ml.weight + 50000 AS weight FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
-        WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .")
-        ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC", $args), $parents);
-
-      // TODO: cache_set() for the untranslated links
       // TODO: special case node links and access check via db_rewite_sql()
-      // TODO: access check / _menu_link_translate on each here
+      _menu_tree_check_access($tree[$cid]);
     }
-    return $tree[$menu_name];
+    return $tree[$cid];
   }
 }
 
+function _menu_tree_check_access(&$tree) {
+  foreach ($tree as $key => $v) {
+    $item = &$tree[$key]['link'];
+    _menu_link_translate($item);
+    if (!$item['access']) {
+      unset($tree[$key]);
+    }
+    elseif ($tree[$key]['below']) {
+      _menu_tree_check_access($tree[$key]['below']);
+    }
+  }
+}
 
 /**
  * Build the data representing a menu tree.
@@ -670,30 +748,23 @@ function menu_tree_data($menu_name = 'na
 function _menu_tree_data($result = NULL, $parents = array(), $depth = 1, $previous_element = '') {
   $remnant = NULL;
   $tree = array();
-  while ($item = db_fetch_object($result)) {
-    // Access check and handle dynamic path translation.
-    // TODO - move this to the parent function, so the untranslated link data
-    // can be cached.
-    _menu_link_translate($item);
-    if (!$item->access) {
-      continue;
-    }
+  while ($item = db_fetch_array($result)) {
     // We need to determine if we're on the path to root so we can later build
     // the correct active trail and breadcrumb.
-    $item->path_to_root = in_array($item->mlid, $parents);
+    $item['in_active_trail'] = in_array($item['mlid'], $parents);
     // The weights are uniform 5 digits because of the 50000 offset in the
     // query. We add mlid at the end of the index to insure uniqueness.
-    $index = $previous_element ? ($previous_element->weight .' '. $previous_element->title . $previous_element->mlid) : '';
+    $index = $previous_element ? ($previous_element['weight'] .' '. $previous_element['title'] . $previous_element['mlid']) : '';
     // The current item is the first in a new submenu.
-    if ($item->depth > $depth) {
+    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);
+      list($item, $below) = _menu_tree_data($result, $parents, $item['depth'], $item);
       $tree[$index] = array(
         'link' => $previous_element,
         'below' => $below,
       );
       // We need to fall back one level.
-      if ($item->depth < $depth) {
+      if (!isset($item) || $item['depth'] < $depth) {
         ksort($tree);
         return array($item, $tree);
       }
@@ -701,7 +772,7 @@ function _menu_tree_data($result = NULL,
       $previous_element = $item;
     }
     // We are in the same menu. We render the previous element, $previous_element.
-    elseif ($item->depth == $depth) {
+    elseif ($item['depth'] == $depth) {
       if ($previous_element) { // Only the first time
         $tree[$index] = array(
           'link' => $previous_element,
@@ -719,7 +790,7 @@ function _menu_tree_data($result = NULL,
   }
   if ($previous_element) {
     // We have one more link dangling.
-    $tree[$previous_element->weight .' '. $previous_element->title .' '. $previous_element->mlid] = array(
+    $tree[$previous_element['weight'] .' '. $previous_element['title'] .' '. $previous_element['mlid']] = array(
       'link' => $previous_element,
       'below' => '',
     );
@@ -732,7 +803,7 @@ function _menu_tree_data($result = NULL,
  * Generate the HTML output for a single menu link.
  */
 function theme_menu_item_link($link) {
-  return l($link->link_title, $link->href, $link->options);
+  return l($link['title'], $link['href'], $link['options']);
 }
 
 /**
@@ -745,8 +816,12 @@ function theme_menu_tree($tree) {
 /**
  * Generate the HTML output for a menu item and submenu.
  */
-function theme_menu_item($link, $has_children, $menu = '') {
-  return '<li class="'. ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf')) .'">'. $link . $menu .'</li>'."\n";
+function theme_menu_item($link, $has_children, $in_active_trail = FALSE, $menu = '') {
+  $class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf'));
+  if ($in_active_trail) {
+    $class .= ' active-trail';
+  }
+  return '<li class="'. $class .'">'. $link . $menu .'</li>'."\n";
 }
 
 function theme_menu_local_task($link, $active = FALSE) {
@@ -761,11 +836,11 @@ function menu_get_active_help() {
   $output = '';
   $item = menu_get_item();
 
-  if (!$item || !$item->access) {
+  if (!$item || !$item['access']) {
     // Don't return help text for areas the user cannot access.
     return;
   }
-  $path = ($item->type == MENU_DEFAULT_LOCAL_TASK) ? $item->tab_parent : $item->path;
+  $path = ($item['type'] == MENU_DEFAULT_LOCAL_TASK) ? $item['tab_parent'] : $item['path'];
 
   foreach (module_list() as $name) {
     if (module_hook($name, 'help')) {
@@ -789,7 +864,6 @@ function menu_get_active_help() {
  */
 function menu_get_names($reset = FALSE) {
   static $names;
-  // TODO - use cache system to save this
 
   if ($reset || empty($names)) {
     $names = array();
@@ -802,13 +876,27 @@ function menu_get_names($reset = FALSE) 
 }
 
 function menu_primary_links() {
-  $tree = menu_tree_data('primary links');
-  return array();
+  $tree = menu_tree_page_data('primary-links');
+  $links = array();
+  foreach ($tree as $item) {
+    $l = $item['link']['options'];
+    $l['href'] = $item['link']['href'];
+    $l['title'] = $item['link']['title'];
+    $links[] = $l;
+  }
+  return $links;
 }
 
 function menu_secondary_links() {
-  $tree = menu_tree_data('secondary links');
-  return array();
+  $tree = menu_tree_page_data('secondary-links');
+  $links = array();
+  foreach ($tree as $item) {
+    $l = $item['link']['options'];
+    $l['href'] = $item['link']['href'];
+    $l['title'] = $item['link']['title'];
+    $links[] = $l;
+  }
+  return $links;
 }
 
 /**
@@ -824,33 +912,33 @@ function menu_local_tasks($level = 0) {
 
   if (empty($tabs)) {
     $router_item = menu_get_item();
-    if (!$router_item || !$router_item->access) {
+    if (!$router_item || !$router_item['access']) {
       return array();
     }
     // Get all tabs
-    $result = db_query("SELECT * FROM {menu_router} WHERE tab_root = '%s' AND tab_parent != '' ORDER BY weight, title", $router_item->tab_root);
+    $result = db_query("SELECT * FROM {menu_router} WHERE tab_root = '%s' AND tab_parent != '' ORDER BY weight, title", $router_item['tab_root']);
     $map = arg();
     $children = array();
     $tab_parent = array();
 
-    while ($item = db_fetch_object($result)) {
-      $children[$item->tab_parent][$item->path] = $item;
-      $tab_parent[$item->path] = $item->tab_parent;
+    while ($item = db_fetch_array($result)) {
+      $children[$item['tab_parent']][$item['path']] = $item;
+      $tab_parent[$item['path']] = $item['tab_parent'];
     }
 
     // Find all tabs below the current path
-    $path = $router_item->path;
+    $path = $router_item['path'];
     while (isset($children[$path])) {
       $tabs_current = '';
       $next_path = '';
       foreach ($children[$path] as $item) {
          _menu_translate($item, $map, TRUE);
-        if ($item->access) {
-          $link = l($item->title, $item->href); // TODO options?
+        if ($item['access']) {
+          $link = l($item['title'], $item['href']); // TODO options?
           // The default task is always active.
-          if ($item->type == MENU_DEFAULT_LOCAL_TASK) {
+          if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) {
             $tabs_current .= theme('menu_local_task', $link, TRUE);
-            $next_path = $item->path;
+            $next_path = $item['path'];
           }
           else {
             $tabs_current .= theme('menu_local_task', $link);
@@ -858,12 +946,12 @@ function menu_local_tasks($level = 0) {
         }
       }
       $path = $next_path;
-      $tabs[$item->number_parts] = $tabs_current;
+      $tabs[$item['number_parts']] = $tabs_current;
     }
 
     // Find all tabs at the same level or above the current one
-    $parent = $router_item->tab_parent;
-    $path = $router_item->path;
+    $parent = $router_item['tab_parent'];
+    $path = $router_item['path'];
     $current = $router_item;
     while (isset($children[$parent])) {
       $tabs_current = '';
@@ -871,12 +959,12 @@ function menu_local_tasks($level = 0) {
       $next_parent = '';
       foreach ($children[$parent] as $item) {
          _menu_translate($item, $map, TRUE);
-        if ($item->access) {
-          $link = l($item->title, $item->href); // TODO options?
+        if ($item['access']) {
+          $link = l($item['title'], $item['href']); // TODO options?
           // We check for the active tab.
-          if ($item->path == $path) {
+          if ($item['path'] == $path) {
             $tabs_current .= theme('menu_local_task', $link, TRUE);
-            $next_path = $item->tab_parent;
+            $next_path = $item['tab_parent'];
             if (isset($tab_parent[$next_path])) {
               $next_parent = $tab_parent[$next_path];
             }
@@ -888,7 +976,7 @@ function menu_local_tasks($level = 0) {
       }
       $path = $next_path;
       $parent = $next_parent;
-      $tabs[$item->number_parts] = $tabs_current;
+      $tabs[$item['number_parts']] = $tabs_current;
     }
     // Sort by depth
     ksort($tabs);
@@ -906,6 +994,25 @@ function menu_secondary_local_tasks() {
   return menu_local_tasks(1);
 }
 
+/**
+ * Returns the rendered local tasks. The default implementation renders
+ * them as tabs.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_local_tasks() {
+ $output = '';
+
+ if ($primary = menu_primary_local_tasks()) {
+   $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
+ }
+ if ($secondary = menu_secondary_local_tasks()) {
+   $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
+ }
+
+ return $output;
+}
+
 function menu_set_active_menu_name($menu_name = NULL) {
   static $active;
 
@@ -933,26 +1040,25 @@ function menu_set_active_trail($new_trai
   }
   elseif (!isset($trail)) {
     $trail = array();
-    $h = array('link_title' => t('Home'), 'href' => '<front>', 'options' => array(), 'type' => 0, 'title' => '');
-    $trail[] = (object)$h;
+    $trail[] = array('link_title' => t('Home'), 'href' => '<front>', 'options' => array(), 'type' => 0, 'title' => '');
     $item = menu_get_item();
     // We are on a tab.
-    if ($item->tab_parent) {
-      $href = $item->tab_root;
+    if ($item['tab_parent']) {
+      $href = $item['tab_root'];
     }
     else {
-      $href = $item->href;
+      $href = $item['href'];
     }
-    $tree = menu_tree_data(menu_get_active_menu_name());
+    $tree = menu_tree_page_data(menu_get_active_menu_name());
     $curr = array_shift($tree);
 
     while ($curr) {
-      if ($curr['link']->href == $href){
+      if ($curr['link']['href'] == $href) {
         $trail[] = $curr['link'];
         $curr = FALSE;
       }
       else {
-        if ($curr['below'] && $curr['link']->path_to_root) {
+        if ($curr['below'] && $curr['link']['in_active_trail']) {
           $trail[] = $curr['link'];
           $tree = $curr['below'];
         }
@@ -973,16 +1079,16 @@ function menu_set_location() {
 function menu_get_active_breadcrumb() {
   $breadcrumb = array();
   $item = menu_get_item();
-  if ($item && $item->access) {
+  if ($item && $item['access']) {
     $active_trail = menu_get_active_trail();
 
     foreach ($active_trail as $parent) {
-      $breadcrumb[] = l($parent->link_title, $parent->href, $parent->options);
+      $breadcrumb[] = l($parent['title'], $parent['href'], $parent['options']);
     }
     $end = end($active_trail);
 
     // Don't show a link to the current page in the breadcrumb trail.
-    if ($item->href == $end->href || ($item->type == MENU_DEFAULT_LOCAL_TASK && $end->href != '<front>')) {
+    if ($item['href'] == $end['href'] || ($item['type'] == MENU_DEFAULT_LOCAL_TASK && $end['href'] != '<front>')) {
       array_pop($breadcrumb);
     }
   }
@@ -993,55 +1099,33 @@ function menu_get_active_title() {
   $active_trail = menu_get_active_trail();
 
   foreach (array_reverse($active_trail) as $item) {
-    if (!(bool)($item->type & MENU_IS_LOCAL_TASK)) {
-      return $item->title;
+    if (!(bool)($item['type'] & MENU_IS_LOCAL_TASK)) {
+      return $item['title'];
     }
   }
 }
 
 /**
- * Get a menu item by its mlid, access checked and link translated for
+ * Get a menu link by its mlid, access checked and link translated for
  * rendering.
  *
  * @param $mlid
  *   The mlid of the menu item.
  * @return
- *   A menu object, with $item->access filled and link translated for
+ *   A menu link, with $item['access'] filled and link translated for
  *   rendering.
  */
-function menu_get_item_by_mlid($mlid) {
-  if ($item = db_fetch_object(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE mlid = %d", $mlid))) {
+function menu_link_load($mlid) {
+  if ($item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE mlid = %d", $mlid))) {
     _menu_link_translate($item);
-    if ($item->access) {
-      return $item;
-    }
+    return $item;
   }
   return FALSE;
 }
 
-/**
- * Returns the rendered local tasks. The default implementation renders
- * them as tabs.
- *
- * @ingroup themeable
- */
-function theme_menu_local_tasks() {
- $output = '';
-
- if ($primary = menu_primary_local_tasks()) {
-   $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
- }
- if ($secondary = menu_secondary_local_tasks()) {
-   $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
- }
-
- return $output;
-}
-
 function menu_cache_clear($menu_name = 'navigation') {
-  // TODO: starting stub.  This will be called whenever an item is added to or
-  // moved from a named menu
 
+  cache_clear_all('links:'. $menu_name .':', 'cache_menu', TRUE);
 }
 
 /**
@@ -1049,8 +1133,7 @@ function menu_cache_clear($menu_name = '
  * router items or menu links.
  */
 function menu_cache_clear_all() {
-  cache_clear_all('*', 'menu_links', TRUE);
-  cache_clear_all('*', 'menu_router', TRUE);
+  cache_clear_all('*', 'cache_menu', TRUE);
 }
 
 /**
@@ -1066,22 +1149,33 @@ function menu_rebuild() {
 /**
  * Collect, alter and store the menu definitions.
  */
-function menu_router_build() {
-  db_query('DELETE FROM {menu_router}');
-  // We need to manually call each module so that we can know which module a given item came from.
-  $callbacks = array();
-  foreach (module_implements('menu') as $module) {
-    $items = call_user_func($module . '_menu');
-    if (isset($items) && is_array($items)) {
-      foreach (array_keys($items) as $path) {
-        $items[$path]['module'] = $module;
-      }
-      $callbacks = array_merge($callbacks, $items);
-    }
-  }
-  // Alter the menu as defined in modules, keys are like user/%user.
-  drupal_alter('menu', $callbacks);
-  $menu = _menu_router_build($callbacks);
+function menu_router_build($reset = FALSE) {
+  static $menu;
+  
+  if (!isset($menu) || $reset) {
+    $cache = cache_get('router:', 'cache_menu');
+    if (!$reset && $cache && isset($cache->data)) {
+      $menu = $cache->data;
+    }
+    else {
+      db_query('DELETE FROM {menu_router}');
+      // We need to manually call each module so that we can know which module a given item came from.
+      $callbacks = array();
+      foreach (module_implements('menu') as $module) {
+        $items = call_user_func($module . '_menu');
+        if (isset($items) && is_array($items)) {
+          foreach (array_keys($items) as $path) {
+            $items[$path]['module'] = $module;
+          }
+          $callbacks = array_merge($callbacks, $items);
+        }
+      }
+      // Alter the menu as defined in modules, keys are like user/%user.
+      drupal_alter('menu', $callbacks);
+      $menu = _menu_router_build($callbacks);
+      cache_set('router:', $menu, 'cache_menu');
+    }
+  }
   return $menu;
 }
 
@@ -1089,18 +1183,24 @@ function _menu_navigation_links_rebuild(
   // Add normal and suggested items as links.
   $menu_links = array();
   foreach ($menu as $path => $item) {
-    if ($item['type'] == MENU_CALLBACK || $item['type'] == MENU_SUGGESTED_ITEM) {
-      $item['hidden'] = $item['type'];
+    if ($item['type'] == MENU_CALLBACK) {
+      $item['hidden'] = -1;
+    }
+    elseif  ($item['type'] == MENU_SUGGESTED_ITEM) {
+      $item['hidden'] = 1;
     }
+    // Note, we set this as 'system', so that we can be sure to distinguish all
+    // the menu links generated automatically from entries in {menu_router}.
+    $item['module'] = 'system';
     $item += array(
-      'menu name' => 'navigation',
+      'menu_name' => 'navigation',
       'link_title' => $item['title'],
-      'href' => $path,
-      'module' => 'system',
+      'link_path' => $path,
       'hidden' => 0,
+      'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
     );
     // We add nonexisting items.
-    if ($item['_visible'] && !db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $item['menu name'], $item['href']))) {
+    if ($item['_visible'] && !db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $item['menu_name'], $item['link_path']))) {
       $menu_links[$path] = $item;
       $sort[$path] = $item['_number_parts'];
     }
@@ -1110,7 +1210,7 @@ function _menu_navigation_links_rebuild(
     array_multisort($sort, SORT_NUMERIC, $menu_links);
 
     foreach ($menu_links as $item) {
-      menu_link_save($item, $menu);
+      menu_link_save($item);
     }
   }
   $placeholders = implode(', ', array_fill(0, count($menu), "'%s'"));
@@ -1119,12 +1219,34 @@ function _menu_navigation_links_rebuild(
 }
 
 /**
+ * Delete a menu link.
+ *
+ * @param $mlid
+ *   A valid menu link mlid
+ */
+function menu_link_delete($mlid) {
+
+  $item = menu_link_load($mlid);
+  
+  // System-created items get automatically deleted, but only on menu rebuild.
+  if ($item && $item['module'] != 'system') {
+
+    if ($item['has_children']) {
+      // TODO - reparent children?
+    }
+    db_query('DELETE FROM {menu_links} WHERE mild = %d', $mlid);  
+    menu_cache_clear($item['menu_name']);
+  }
+}
+
+
+/**
  * Save a menu link.
  *
  * @param $item
- *   An array representing a menu link item. The only mandatory keys are href
- *   and link_title. Possible keys are
- *     menu name   default is navigation
+ *   An array representing a menu link item. The only mandatory keys are
+ *   link_path and link_title. Possible keys are
+ *     menu_name   default is navigation
  *     weight      default is 0
  *     expanded    whether the item is expanded.
  *     options     An array of options, @see l for more.
@@ -1133,58 +1255,49 @@ function _menu_navigation_links_rebuild(
  *     plid        The mlid of the parent.
  *     router_path The path of the relevant router item.
  */
-function menu_link_save(&$item, $_menu = NULL) {
-  static $menu;
-
-  if (isset($_menu)) {
-    $menu = $_menu;
-  }
-  elseif (!isset($menu)) {
-    $menu = menu_router_build();
-  }
+function menu_link_save(&$item) {
+  $menu = menu_router_build();
 
   drupal_alter('menu_link', $item, $menu);
 
-  $item['_external'] = menu_path_is_external($item['href']);
+  $item['_external'] = menu_path_is_external($item['link_path']);
   // Load defaults.
   $item += array(
-    'menu name' => 'navigation',
+    'menu_name' => 'navigation',
     'weight' => 0,
     'link_title' => '',
     'hidden' => 0,
     'has_children' => 0,
     'expanded' => 0,
-    'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
+    'options' => array(),
   );
-  $existing_item = array();
+  $menu_name = $item['menu_name'];
+  $existing_item = FALSE;
   if (isset($item['mlid'])) {
     $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['mlid']));
   }
   else {
-    $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $item['menu name'], $item['href']));
+    $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['link_path']));
   }
 
-  if (empty($existing_item)) {
+  if (!$existing_item) {
     $item['mlid'] = db_next_id('{menu_links}_mlid');
   }
 
-  $menu_name = $item['menu name'];
-  $new_path = !$existing_item || ($existing_item['href'] != $item['href']);
-
-  // Find the parent.
+  // Find the parent - it must be in the same menu.
   if (isset($item['plid'])) {
-    $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
+    $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE menu_name = '%s' AND mlid = %d", $menu_name, $item['plid']));
   }
-  else { //
-    $parent_path = $item['href'];
+  else {
+    $parent_path = $item['link_path'];
     do {
       $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
-      $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $menu_name, $parent_path));
+      $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $parent_path));
     } while ($parent === FALSE && $parent_path);
   }
   // 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'] == MENU_CALLBACK) {
+  if (empty($parent['mlid']) || $parent['hidden'] < 0) {
     $item['plid'] =  0;
   }
   else {
@@ -1198,28 +1311,31 @@ function menu_link_save(&$item, $_menu =
   }
   else {
     // Cannot add beyond the maximum depth.
-    if ($parent['depth'] >= (MENU_MAX_DEPTH)) {
+    if ($item['has_children'] && $existing_item) {
+      $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
+    }
+    else {
+      $limit = MENU_MAX_DEPTH - 1;
+    }
+    if ($parent['depth'] > $limit) {
       return FALSE;
     }
     $item['depth'] = $parent['depth'] + 1;
-    _menu_parents_copy($item, $parent);
-    $item['p'. $item['depth']] = $item['mlid'];
+    _menu_link_parents_set($item, $parent);
   }
-
-  if ($item['plid'] != $existing_item['plid']) {
-
-    // TODO: UPDATE the parents of the children of the current item
-    // TODO: check the has_children status of the previous parent
+  // Need to check both plid and menu_name, since plid can be 0 in any menu.
+  if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
+    _menu_link_move_children($item, $existing_item);
   }
   // Find the callback.
-  if (empty($item['router_path']) || $new_path) {
+  if (empty($item['router_path']) || !$existing_item || ($existing_item['link_path'] != $item['link_path'])) {
     if ($item['_external']) {
       $item['router_path'] = '';
     }
     else {
       // Find the router path which will serve this path.
-      $item['parts'] = explode('/', $item['href'], MENU_MAX_PARTS);
-      $item['router_path'] = $item['href'];
+      $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
+      $item['router_path'] = $item['link_path'];
       if (!isset($menu[$item['router_path']])) {
         list($ancestors) = menu_get_ancestors($item['parts']);
         while ($ancestors && (!isset($menu[$item['router_path']]))) {
@@ -1231,21 +1347,21 @@ function menu_link_save(&$item, $_menu =
       }
     }
   }
-  if (!empty($existing_item)) {
-    db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, href = '%s',
+  if ($existing_item) {
+    db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, link_path = '%s',
       router_path = '%s', hidden = %d, external = %d, has_children = %d,
       expanded = %d, weight = %d,  depth = %d,
       p1 = %d, p2 = %d, p3 = %d, p4 = %d, p5 = %d, p6 = %d,
       module = '%s', link_title = '%s', options = '%s' WHERE mlid = %d",
-      $item['menu name'], $item['plid'], $item['href'],
+      $item['menu_name'], $item['plid'], $item['link_path'],
       $item['router_path'], $item['hidden'], $item['_external'], $item['has_children'],
-      $item['expanded'],$item['weight'],  $item['depth'],
+      $item['expanded'], $item['weight'],  $item['depth'],
       $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'],
       $item['module'],  $item['link_title'], serialize($item['options']), $item['mlid']);
   }
   else {
     db_query("INSERT INTO {menu_links} (
-      menu_name, mlid, plid, href,
+      menu_name, mlid, plid, link_path,
       router_path, hidden, external, has_children,
       expanded, weight, depth,
       p1, p2, p3, p4, p5, p6,
@@ -1255,18 +1371,22 @@ function menu_link_save(&$item, $_menu =
       %d, %d, %d,
       %d, %d, %d, %d, %d, %d,
       '%s', '%s', '%s')",
-      $item['menu name'], $item['mlid'], $item['plid'], $item['href'],
+      $item['menu_name'], $item['mlid'], $item['plid'], $item['link_path'],
       $item['router_path'], $item['hidden'], $item['_external'], $item['has_children'],
-      $item['expanded'],$item['weight'],  $item['depth'],
+      $item['expanded'], $item['weight'],  $item['depth'],
       $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'],
       $item['module'],  $item['link_title'], serialize($item['options']));
- }
-
-  if ($item['plid'] && !$item['hidden']) {
-    db_query("UPDATE {menu_links} SET has_children = 1 WHERE mlid = %d", $item['plid']);
   }
-
-  // Keep track of which menus have expanded items
+  // Check the has_children status of the parent.
+  if ($item['plid']) {
+    $parent_has_children = (bool)db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE plid = %d AND hidden = 0", $item['plid']));
+    db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $item['plid']);
+  }
+  menu_cache_clear($menu_name);
+  if ($existing_item && $menu_name != $existing_item['menu_name']) {
+    menu_cache_clear($existing_item['menu_name']);
+  }
+  // Keep track of which menus have expanded items.
   $names = array();
   $result = db_query("SELECT menu_name FROM {menu_links} WHERE expanded != 0 GROUP BY menu_name");
   while ($n = db_fetch_array($result)) {
@@ -1276,13 +1396,96 @@ function menu_link_save(&$item, $_menu =
   return TRUE;
 }
 
-function _menu_parents_copy(&$dest, $source, $offset = 0){
-  $i = 1 + $offset;
-  $depth = 0;
-  for ($j = 1; $i <= MENU_MAX_DEPTH; $i++) {
-    $dest['p'. $i] = $source['p'. $j];
-    $j++;
-  }
+/**
+ * Find the depth of an item's children relative to its depth.  For example, if
+ * the item has a depth of 2, and the maximum of any child in the menu link tree
+ * is 5, the relative depth is 3.
+ *
+ * @param $item
+ *   An array representing a menu link item.
+ * @return
+ *   The relative depth, or zero.
+ *
+ */
+function menu_link_children_relative_depth($item) {
+  $i = 1;
+  $match = '';
+  $args[] = $item['menu_name'];
+  $p = 'p1';
+  while ($i <= MENU_MAX_DEPTH && $item[$p]) {
+    $match .= " AND $p = %d";
+    $args[] = $item[$p];
+    $p = 'p'. ++$i;
+  }
+  
+  $max_depth = db_result(db_query_range("SELECT depth FROM {menu_links} WHERE menu_name = '%s'". $match ." ORDER BY depth DESC", $args, 0, 1));
+  
+  return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0;
+}
+
+/** 
+ * Update the menu name, parents (p1 - p6), and depth for the children of 
+ * a menu link that's being moved in the tree and check the has_children status
+ * of the previous parent. 
+ */
+function _menu_link_move_children($item, $existing_item) {
+
+  $args[] = $item['menu_name'];
+  $set = '';
+  $shift = $item['depth'] - $existing_item['depth'];
+  if ($shift < 0) {
+    $args[] = -$shift;
+    $set = ', depth = depth - %d';
+  }
+  elseif ($shift > 0) {
+    $args[] = $shift;
+    $set = ', depth = depth + %d';
+  }
+  $i = 1;
+  while ($i <= $item['depth']) {
+    $p = 'p'. $i++;  
+    $set .= ", $p = %d";
+    $args[] = $item[$p];    
+  }
+  $j = $existing_item['depth'] + 1;
+  while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
+    $set .= ', p'. $i++ .' = p'. $j++;
+  }
+  while ($i <= MENU_MAX_DEPTH) {
+    $set .= ', p'. $i++ .' = 0';
+  }
+
+  $args[] = $existing_item['menu_name'];
+  $i = 1;
+  $match = '';
+  $p = 'p1';
+  while ($i <= MENU_MAX_DEPTH && $existing_item[$p]) {
+    $match .= " AND $p = %d";
+    $args[] = $existing_item[$p];
+    $p = 'p'. ++$i;
+  } 
+
+  db_query("UPDATE {menu_links} SET menu_name = '%s'". $set ." WHERE menu_name = '%s'". $match, $args);
+  
+  if ($existing_item['plid']) {
+    $parent_has_children = (bool)db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE plid = %d AND hidden = 0 AND mlid != %d", $existing_item['plid'], $existing_item['mlid']));
+    db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $existing_item['plid']);
+  }
+}
+
+function _menu_link_parents_set(&$item, $parent) {
+  $i = 1;
+  while ($i < $item['depth']) {
+    $p = 'p'. $i++;
+    $item[$p] = $parent[$p];
+  }
+  $p = 'p'. $i++;
+  // The parent (p1 - p6) corresponding to the depth always equals the mlid.
+  $item[$p] = $item['mlid'];
+  while ($i <= MENU_MAX_DEPTH) {
+    $p = 'p'. $i++;
+    $item[$p] = 0;
+  }  
 }
 
 function _menu_router_build($callbacks) {
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.1
diff -u -p -r1.1 system.admin.inc
--- modules/system/system.admin.inc	22 May 2007 05:52:17 -0000	1.1
+++ modules/system/system.admin.inc	24 May 2007 12:05:12 -0000
@@ -16,17 +16,17 @@ function system_main_admin_page($arg = N
     drupal_set_message(t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/logs/status'))), 'error');
   }
   $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path
-                      WHERE ml.href like 'admin/%' AND ml.href != 'admin/help' AND ml.depth = 2 AND ml.menu_name = 'navigation'
+                      WHERE ml.link_path like 'admin/%' AND ml.link_path != 'admin/help' AND ml.depth = 2 AND ml.menu_name = 'navigation'
                       ORDER BY p1 ASC, p2 ASC, p3 ASC");
-  while ($item = db_fetch_object($result)) {
+  while ($item = db_fetch_array($result)) {
     _menu_link_translate($item);
-    if (!$item->access) {
+    if (!$item['access']) {
       continue;
     }
-    $block = (array)$item;
+    $block = $item;
     $block['content'] = '';
-    if ($item->block_callback && function_exists($item->block_callback)) {
-      $function = $item->block_callback;
+    if ($item['block_callback'] && function_exists($item['block_callback'])) {
+      $function = $item['block_callback'];
       $block['content'] .= $function();
     }
     $block['content'] .= theme('admin_block_content', system_admin_menu_block($item));
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.115
diff -u -p -r1.115 system.install
--- modules/system/system.install	22 May 2007 05:52:17 -0000	1.115
+++ modules/system/system.install	24 May 2007 12:05:13 -0000
@@ -265,6 +265,17 @@ function system_install() {
         INDEX expire (expire)
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
+      db_query("CREATE TABLE {cache_menu} (
+        cid varchar(255) BINARY NOT NULL default '',
+        data longblob,
+        expire int NOT NULL default '0',
+        created int NOT NULL default '0',
+        headers text,
+        serialized int(1) NOT NULL default '0',
+        PRIMARY KEY (cid),
+        INDEX expire (expire)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+      
       db_query("CREATE TABLE {comments} (
         cid int NOT NULL auto_increment,
         pid int NOT NULL default '0',
@@ -379,9 +390,9 @@ function system_install() {
 
       db_query("CREATE TABLE {menu_links} (
         menu_name varchar(64) NOT NULL default '',
-        mlid int NOT NULL default '0',
+        mlid int unsigned NOT NULL auto_increment,
         plid int NOT NULL default '0',
-        href varchar(255) NOT NULL default '',
+        link_path varchar(255) NOT NULL default '',
         router_path varchar(255) NOT NULL default '',
         hidden smallint NOT NULL default '0',
         external smallint NOT NULL default '0',
@@ -399,8 +410,10 @@ function system_install() {
         link_title varchar(255) NOT NULL default '',
         options text,
         PRIMARY KEY (mlid),
-        KEY parents (plid, p1, p2, p3, p4, p5),
-        KEY menu_name_path (menu_name, href),
+        KEY router_path (router_path),
+        KEY plid (plid),
+        KEY parents (p1, p2, p3, p4, p5),
+        KEY menu_name_path (menu_name, link_path),
         KEY menu_expanded_children (expanded, has_children)
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
@@ -781,10 +794,20 @@ function system_install() {
         serialized smallint NOT NULL default '0',
         PRIMARY KEY (cid)
       )");
+      db_query("CREATE TABLE {cache_menu} (
+        cid varchar(255) NOT NULL default '',
+        data bytea,
+        expire int NOT NULL default '0',
+        created int NOT NULL default '0',
+        headers text,
+        serialized smallint NOT NULL default '0',
+        PRIMARY KEY (cid)
+      )");      
       db_query("CREATE INDEX {cache}_expire_idx ON {cache} (expire)");
       db_query("CREATE INDEX {cache_filter}_expire_idx ON {cache_filter} (expire)");
       db_query("CREATE INDEX {cache_page}_expire_idx ON {cache_page} (expire)");
       db_query("CREATE INDEX {cache_form}_expire_idx ON {cache_form} (expire)");
+      db_query("CREATE INDEX {cache_menu}_expire_idx ON {cache_form} (expire)");
 
       db_query("CREATE TABLE {comments} (
         cid serial,
@@ -900,9 +923,9 @@ function system_install() {
 
       db_query("CREATE TABLE {menu_links} (
         menu_name varchar(64) NOT NULL default '',
-        mlid serial,
+        mlid serial CHECK (mlid >= 0),
         plid int NOT NULL default '0',
-        href varchar(255) NOT NULL default '',
+        link_path varchar(255) NOT NULL default '',
         router_path varchar(255) NOT NULL default '',
         hidden smallint NOT NULL default '0',
         external smallint NOT NULL default '0',
@@ -921,8 +944,11 @@ function system_install() {
         options text,
         PRIMARY KEY (mlid)
       )");
-      db_query("CREATE INDEX {menu_links}_parents_idx ON {menu_links} (plid, p1, p2, p3, p4, p5)");
-      db_query("CREATE INDEX {menu_links}_menu_name_idx ON {menu_links} (menu_name, href)");
+
+      db_query("CREATE INDEX {menu_links}_router_path_idx ON {menu_links} (router_path)");
+      db_query("CREATE INDEX {menu_links}_plid_idx ON {menu_links} (plid)");
+      db_query("CREATE INDEX {menu_links}_parents_idx ON {menu_links} (p1, p2, p3, p4, p5)");
+      db_query("CREATE INDEX {menu_links}_menu_name_idx ON {menu_links} (menu_name, link_path)");
       db_query("CREATE INDEX {menu_links}_expanded_children_idx ON {menu_links} (expanded, has_children)");
 
       db_query("CREATE TABLE {node} (
@@ -3925,14 +3951,15 @@ function system_update_6012() {
       db_add_column($ret, 'cache', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
       db_add_column($ret, 'cache_filter', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
       db_add_column($ret, 'cache_page', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
+      db_add_column($ret, 'cache_menu', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
       break;
     case 'mysql':
     case 'mysqli':
       $ret[] = update_sql("ALTER TABLE {cache} ADD serialized int(1) NOT NULL default '0'");
       $ret[] = update_sql("ALTER TABLE {cache_filter} ADD serialized int(1) NOT NULL default '0'");
       $ret[] = update_sql("ALTER TABLE {cache_page} ADD serialized int(1) NOT NULL default '0'");
+      $ret[] = update_sql("ALTER TABLE {cache_menu} ADD serialized int(1) NOT NULL default '0'");
       break;
-
   }
 
   return $ret;
@@ -4093,6 +4120,130 @@ function system_update_6018() {
 }
 
 /**
+ * Install menu_router and menu_links tables.
+ */
+function system_update_6019() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      $ret[] = update_sql("CREATE TABLE {menu_router} (
+        path varchar(255) NOT NULL default '',
+        load_functions varchar(255) NOT NULL default '',
+        to_arg_functions varchar(255) NOT NULL default '',
+        access_callback varchar(255) NOT NULL default '',
+        access_arguments text,
+        page_callback varchar(255) NOT NULL default '',
+        page_arguments text,
+        fit int NOT NULL default 0,
+        number_parts int NOT NULL default 0,
+        tab_parent varchar(255) NOT NULL default '',
+        tab_root varchar(255) NOT NULL default '',
+        title varchar(255) NOT NULL default '',
+        title_callback varchar(255) NOT NULL default '',
+        title_arguments varchar(255) NOT NULL default '',
+        type int NOT NULL default 0,
+        block_callback varchar(255) NOT NULL default '',
+        description TEXT,
+        position varchar(255) NOT NULL default '',
+        weight int NOT NULL default 0,
+        file text NOT NULL default '',
+        PRIMARY KEY (path)
+      )");
+      $ret[] = update_sql("CREATE INDEX {menu_router}_fit_idx ON {menu_router} (fit)");
+      $ret[] = update_sql("CREATE INDEX {menu_router}_tab_parent_idx ON {menu_router} (tab_parent)");
+
+      $ret[] = update_sql("CREATE TABLE {menu_links} (
+        menu_name varchar(64) NOT NULL default '',
+        mlid serial CHECK (mlid >= 0),
+        plid int NOT NULL default '0',
+        link_path varchar(255) NOT NULL default '',
+        router_path varchar(255) NOT NULL default '',
+        hidden smallint NOT NULL default '0',
+        external smallint NOT NULL default '0',
+        has_children int NOT NULL default '0',
+        expanded smallint NOT NULL default '0',
+        weight int NOT NULL default '0',
+        depth int NOT NULL default '0',
+        p1 int NOT NULL default '0',
+        p2 int NOT NULL default '0',
+        p3 int NOT NULL default '0',
+        p4 int NOT NULL default '0',
+        p5 int NOT NULL default '0',
+        p6 int NOT NULL default '0',
+        module varchar(255) NOT NULL default 'system',
+        link_title varchar(255) NOT NULL default '',
+        options text,
+        PRIMARY KEY (mlid)
+      )");
+      $ret[] = update_sql("CREATE INDEX {menu_links}_router_path_idx ON {menu_links} (router_path)");
+      $ret[] = update_sql("CREATE INDEX {menu_links}_plid_idx ON {menu_links} (plid)");
+      $ret[] = update_sql("CREATE INDEX {menu_links}_parents_idx ON {menu_links} (p1, p2, p3, p4, p5)");
+      $ret[] = update_sql("CREATE INDEX {menu_links}_menu_name_idx ON {menu_links} (menu_name, link_path)");
+      $ret[] = update_sql("CREATE INDEX {menu_links}_expanded_children_idx ON {menu_links} (expanded, has_children)");
+
+      break;
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("CREATE TABLE {menu_router} (
+        path varchar(255) NOT NULL default '',
+        load_functions varchar(255) NOT NULL default '',
+        to_arg_functions varchar(255) NOT NULL default '',
+        access_callback varchar(255) NOT NULL default '',
+        access_arguments text,
+        page_callback varchar(255) NOT NULL default '',
+        page_arguments text,
+        fit int NOT NULL default 0,
+        number_parts int NOT NULL default 0,
+        tab_parent varchar(255) NOT NULL default '',
+        tab_root varchar(255) NOT NULL default '',
+        title varchar(255) NOT NULL default '',
+        title_callback varchar(255) NOT NULL default '',
+        title_arguments varchar(255) NOT NULL default '',
+        type int NOT NULL default 0,
+        block_callback varchar(255) NOT NULL default '',
+        description TEXT,
+        position varchar(255) NOT NULL default '',
+        weight int NOT NULL default 0,
+        file mediumtext NOT NULL default '',
+        PRIMARY KEY  (path),
+        KEY fit (fit),
+        KEY tab_parent (tab_parent)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+
+      $ret[] = update_sql("CREATE TABLE {menu_links} (
+        menu_name varchar(64) NOT NULL default '',
+        mlid int unsigned NOT NULL auto_increment,
+        plid int NOT NULL default '0',
+        link_path varchar(255) NOT NULL default '',
+        router_path varchar(255) NOT NULL default '',
+        hidden smallint NOT NULL default '0',
+        external smallint NOT NULL default '0',
+        has_children int NOT NULL default '0',
+        expanded smallint NOT NULL default '0',
+        weight int NOT NULL default '0',
+        depth int NOT NULL default '0',
+        p1 int NOT NULL default '0',
+        p2 int NOT NULL default '0',
+        p3 int NOT NULL default '0',
+        p4 int NOT NULL default '0',
+        p5 int NOT NULL default '0',
+        p6 int NOT NULL default '0',
+        module varchar(255) NOT NULL default 'system',
+        link_title varchar(255) NOT NULL default '',
+        options text,
+        PRIMARY KEY (mlid),
+        KEY router_path (router_path),
+        KEY plid (plid),
+        KEY parents (p1, p2, p3, p4, p5),
+        KEY menu_name_path (menu_name, link_path),
+        KEY menu_expanded_children (expanded, has_children)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+    break;
+  }
+}
+
+/**
  * @} End of "defgroup updates-5.x-to-6.x"
  * The next series of updates should start at 7000.
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.481
diff -u -p -r1.481 system.module
--- modules/system/system.module	23 May 2007 08:00:46 -0000	1.481
+++ modules/system/system.module	24 May 2007 12:05:13 -0000
@@ -144,6 +144,7 @@ function system_menu() {
   $items['admin/by-task'] = array(
     'title' => 'By task',
     'page callback' => 'system_main_admin_page',
+    'file' => 'system.admin.inc',
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
   $items['admin/by-module'] = array(
@@ -392,14 +393,14 @@ function system_user($type, $edit, &$use
  */
 function system_admin_menu_block($item) {
   $content = array();
-  if (!isset($item->mlid)) {
-    $item->mlid = db_result(db_query("SELECT mlid FROM {menu_links} ml WHERE ml.router_path = '%s' AND menu_name = 'navigation'", $item->path));
+  if (!isset($item['mlid'])) {
+    $item['mlid'] = db_result(db_query("SELECT mlid FROM {menu_links} ml WHERE ml.router_path = '%s' AND menu_name = 'navigation'", $item['path']));
   }
   $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path
-                      WHERE ml.plid = '%s' AND ml.menu_name = 'navigation' ORDER BY m.weight, m.title", $item->mlid);
-  while ($item = db_fetch_object($result)) {
+                      WHERE ml.plid = %d AND ml.menu_name = 'navigation' ORDER BY m.weight, m.title", $item['mlid']);
+  while ($item = db_fetch_array($result)) {
     _menu_link_translate($item);
-    if (!$item->access) {
+    if (!$item['access']) {
       continue;
     }
     $content[] = (array)$item;
