? foo
? multiple_menus_0.patch
? multiple_menus_10.patch
? sites/rosalind.local
Index: index.php
===================================================================
RCS file: /cvs/drupal/drupal/index.php,v
retrieving revision 1.93
diff -u -p -r1.93 index.php
--- index.php	6 Apr 2007 13:27:20 -0000	1.93
+++ index.php	7 May 2007 01:59:25 -0000
@@ -12,6 +12,7 @@
 require_once './includes/bootstrap.inc';
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 
+menu_rebuild();
 $return = menu_execute_active_handler();
 
 // Menu status constants are integers; page content is a string.
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.165
diff -u -p -r1.165 menu.inc
--- includes/menu.inc	6 May 2007 05:47:51 -0000	1.165
+++ includes/menu.inc	7 May 2007 01:59:26 -0000
@@ -283,50 +283,27 @@ function menu_unserialize($data, $map) {
 }
 
 /**
- * Replaces the statically cached item for a given path.
+ * Get the menu callback for the a path.
  *
  * @param $path
- *   The path
- * @param $item
- *   The menu item. This is a menu entry, an associative array,
- *   with keys like title, access callback, access arguments etc.
+ *   A path, or NULL for the current path
  */
-function menu_set_item($path, $item) {
-  menu_get_item($path, $item);
-}
-
-function menu_get_item($path = NULL, $item = NULL) {
+function menu_get_item($path = NULL) {
   static $items;
   if (!isset($path)) {
     $path = $_GET['q'];
   }
-  if (isset($item)) {
-    $items[$path] = $item;
-  }
   if (!isset($items[$path])) {
     $original_map = arg(NULL, $path);
     $parts = array_slice($original_map, 0, 6);
     list($ancestors, $placeholders) = menu_get_ancestors($parts);
     $item->active_trail = array();
     if ($item = db_fetch_object(db_query_range('SELECT * FROM {menu} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
-      // We need to access check the parents to match the navigation tree
-      // behaviour. The last parent is always the item itself.
-      $args = explode(',', $item->parents);
-      $placeholders = implode(', ', array_fill(0, count($args), '%d'));
-      $result = db_query('SELECT * FROM {menu} WHERE mid IN ('. $placeholders .') ORDER BY mleft', $args);
-      $item->access = TRUE;
-      while ($item->access && ($parent = db_fetch_object($result)))  {
-        $map = _menu_translate($parent, $original_map);
-        if ($map === FALSE) {
-          $items[$path] = FALSE;
-          return FALSE;
-        }
-        if ($parent->access) {
-          $item->active_trail[] = $parent;
-        }
-        else {
-          $item->access = FALSE;
-        }
+
+      $map = _menu_translate($item, $original_map);
+      if ($map === FALSE) {
+        $items[$path] = FALSE;
+        return FALSE;
       }
       if ($item->access) {
         $item->map = $map;
@@ -339,7 +316,7 @@ function menu_get_item($path = NULL, $it
 }
 
 /**
- * Execute the handler associated with the active menu item.
+ * Execute the page callback associated with the current path
  */
 function menu_execute_active_handler() {
   if ($item = menu_get_item()) {
@@ -349,77 +326,48 @@ function menu_execute_active_handler() {
 }
 
 /**
- * Handles dynamic path translation, title and description translation and
- * menu access control.
- *
- * When a user arrives on a page such as node/5, this function determines
- * what "5" corresponds to, by inspecting the page's menu path definition,
- * node/%node. This will call node_load(5) to load the corresponding node
- * object.
- *
- * It also works in reverse, to allow the display of tabs and menu items which
- * contain these dynamic arguments, translating node/%node to node/5.
- * This operation is called MENU_RENDER_LINK.
- *
- * Translation of menu item titles and descriptions are done here to
- * allow for storage of English strings in the database, and be able to
- * generate menus in the language required to generate the current page.
+ * Loads objects into the map as defined in the
+ * $item->load_functions.
  *
  * @param $item
  *   A menu item object
  * @param $map
  *   An array of path arguments (ex: array('node', '5'))
- * @param $operation
- *   The path translation operation to perform:
- *   - MENU_HANDLE_REQUEST: An incoming page reqest; map with appropriate callback.
- *   - MENU_RENDER_LINK: Render an internal path as a link.
  * @return
- *   Returns the map with objects loaded as defined in the
- *   $item->load_functions. Also, $item->link_path becomes the path ready
- *   for printing, aliased. $item->alias becomes TRUE to mark this, so you can
- *   just pass (array)$item to l() as the third parameter.
- *   $item->access becomes TRUE if the item is accessible, FALSE otherwise.
-  */
-function _menu_translate(&$item, $map, $operation = MENU_HANDLE_REQUEST) {
-  // Check if there are dynamic arguments in the path that need to be calculated.
-  // If there are to_arg_functions, then load_functions is also not empty
-  // because it was built so in menu_rebuild. Therefore, it's enough to test
-  // load_functions.
+ *   Returns the 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);
-    $to_arg_functions = unserialize($item->to_arg_functions);
-    $path_map = ($operation == MENU_HANDLE_REQUEST) ? $map : explode('/', $item->path);
-    foreach ($load_functions as $index => $load_function) {
-      // Translate place-holders into real values.
-      if ($operation == MENU_RENDER_LINK) {
-        if (isset($to_arg_functions[$index])) {
-          $to_arg_function = $to_arg_functions[$index];
-          $return = $to_arg_function(!empty($map[$index]) ? $map[$index] : '');
-          if (!empty($map[$index]) || isset($return)) {
-            $path_map[$index] = $return;
-          }
-          else {
-            unset($path_map[$index]);
-          }
-        }
-        else {
-          $path_map[$index] = isset($map[$index]) ? $map[$index] : '';
-        }
-      }
-      // We now have a real path regardless of operation, map it.
-      if ($load_function) {
-        $return = $load_function(isset($path_map[$index]) ? $path_map[$index] : '');
+    $path_map = $map;
+    foreach ($load_functions as $index => $function) {
+      if ($function) {
+
+        $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;
+          $map = FALSE;
           return FALSE;
         }
         $map[$index] = $return;
       }
     }
-    // Re-join the path with the new replacement value and alias it.
-    $item->link_path = drupal_get_path_alias(implode('/', $path_map));
   }
+  return TRUE;
+}
+
+/**
+ * Check access to a menu item using the access callback
+ *
+ * @param $item
+ *   A menu item object
+ * @param $map
+ *   An array of path arguments (ex: array('node', '5'))
+ * @return
+ *   $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;
@@ -438,7 +386,53 @@ function _menu_translate(&$item, $map, $
       $item->access = call_user_func_array($callback, $arguments);
     }
   }
-  $item->alias = TRUE;
+}
+
+/**
+ * Handles dynamic path translation and menu access control.
+ *
+ * When a user arrives on a page such as node/5, this function determines
+ * what "5" corresponds to, by inspecting the page's menu path definition,
+ * node/%node. This will call node_load(5) to load the corresponding node
+ * object.
+ *
+ * It also works in reverse, to allow the display of tabs and menu items which
+ * contain these dynamic arguments, translating node/%node to node/5.
+ * This operation is called MENU_RENDER_LINK.
+ *
+ * Translation of menu item titles and descriptions are done here to
+ * allow for storage of English strings in the database, and translation
+ * to the language required to generate the current page
+ *
+ * @param $item
+ *   A menu item object
+ * @param $map
+ *   An array of path arguments (ex: array('node', '5'))
+ * @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->link_path is set.
+ */
+
+function _menu_translate(&$item, $map) {
+
+  $path_map = $map;
+  if (!_menu_load_objects($item, $map)) {
+    // An error occured loading an object
+    $item->access = FALSE;
+    return FALSE;
+  }
+
+  // Generate the link path for the page request or local tasks
+  $link_map = explode('/', $item->path);
+  for ($i = 0; $i < $item->number_parts; $i++) {
+    if ($link_map[$i] == '%') {
+      $link_map[$i] = $path_map[$i];
+    }
+  }
+  $item->link_path = implode('/', $link_map);
+  _menu_check_access($item, $map);
 
   // Translate the title to allow storage of English title strings
   // in the database, yet be able to display them in the language
@@ -472,26 +466,125 @@ function _menu_translate(&$item, $map, $
 }
 
 /**
+ * When rendering a menu link, this function is similar to
+ * _menu_translate() but does other link-specific preparation.
+ *
+ * @param $item
+ *   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->link_path is altered if there is a to_arg function.
+ */
+function _menu_link_translate(&$item) {
+  if ($item->external) {
+    $item->access = 1;
+  }
+  else {
+    $map = explode('/', $item->link_path);
+    if ($item->to_arg_functions) {
+      $to_arg_functions = unserialize($item->to_arg_functions);
+      foreach ($to_arg_functions as $index => $function) {
+        // Translate place-holders into real values.
+        $arg = $function(!empty($map[$index]) ? $map[$index] : '');
+        if (!empty($map[$index]) || isset($arg)) {
+          $map[$index] = $arg;
+        }
+        else {
+          unset($map[$index]);
+        }
+      }
+      // Replace the link path using new values
+      $item->link_path = implode('/', $map);
+    }
+    if (!_menu_load_objects($item, $map)) {
+      // An error occured loading an object
+      $item->access = FALSE;
+      return FALSE;
+    }
+    // TODO: menu_tree may set this ahead of time for links to nodes
+    if (!isset($item->access)) {
+      _menu_check_access($item, $map);
+    }
+  }
+  $item->options = unserialize($item->options);
+
+  return $map;
+}
+
+/**
  * Returns a rendered menu tree.
  */
-function menu_tree() {
-  if ($item = menu_get_item()) {
-    if ($item->access) {
-      $args = explode(',', $item->parents);
-      $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+function menu_tree($menu_name = 'navigation') {
+  static $menu_output = array();
+
+  if (!isset($menu_output[$menu_name])) {
+    $tree = menu_tree_data($menu_name);
+    $menu_output[$menu_name] = _menu_tree_output($tree);
+  }
+  return $menu_output[$menu_name];
+}
+
+function _menu_tree_output($tree) {
+  $output = '';
+
+  foreach ($tree as $data) {
+    $link = theme('menu_item_link', $data['link']);
+    if ($data['below']) {
+      $output .= theme('menu_item', $link, $data['link']->has_children, _menu_tree_output($data['below']));
     }
-    // Show the root menu for access denied.
     else {
-      $args = 0;
-      $placeholders = '%d';
+      $output .= theme('menu_item', $link, $data['link']->has_children);
+    }
+  }
+  return $output ? theme('menu_tree', $output) : '';
+}
+
+/**
+ * Get the data representing a named menu tree
+ *
+ * @param $menu_name
+ *   The named menu links to return
+ * @return
+ *   An array of menu links, in the order they should be rendered.
+ */
+
+function menu_tree_data($menu_name = 'navigation') {
+  static $tree = array();
+
+  if ($item = menu_get_item()) {
+    if (!isset($tree[$menu_name])) {
+      if ($item->access) {
+
+        $parents = db_result(db_query("SELECT parents FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item->link_path));
+        // We may be on a local task that's not in the links
+        // TODO how do we hadle the case like a local task on a specific node in the menu?
+        if (empty($parents) && $item->tab_parent) {
+          $parents = db_result(db_query("SELECT parents FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item->tab_root));
+        }
+        if (empty($parents)) {
+          $parents = '0';
+        }
+        $args = explode(',', $parents);
+        $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+        $placeholders = "'%s', ". $placeholders;
+        array_unshift($args, $menu_name);
+      }
+      // Show the root menu for access denied.
+      else {
+        $args = array('navigation', 0);
+        $placeholders = '%d';
+      }
+      list(, $tree[$menu_name]) = _menu_tree_data(db_query("SELECT *, ml.weight FROM {menu_links} ml LEFT JOIN {menu} m ON m.path = ml.router_path WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .") AND disabled = 0 ORDER BY mleft ASC, mright DESC", $args));
     }
-    list(, $menu) = _menu_tree(db_query('SELECT * FROM {menu} WHERE pid IN ('. $placeholders .') AND visible = 1 ORDER BY mleft', $args));
-    return $menu;
+    return $tree[$menu_name];
   }
 }
 
+
 /**
- * Renders a menu tree from a database result resource.
+ * 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
@@ -501,50 +594,45 @@ function menu_tree() {
  *   The database result.
  * @param $depth
  *   The depth of the current menu tree.
- * @param $link
- *   The first link in the current menu tree.
- * @param $has_children
- *  Whether the first link has children.
+ * @param $previous_element
+ *   The previous menu link in the current menu tree.
  * @return
  *   A list, the first element is the first item after the submenu, the second
- *   is the rendered HTML of the children.
+ *   is the data of the child menu items.
  */
-function _menu_tree($result = NULL, $depth = 0, $link = '', $has_children = FALSE) {
-  static $map;
+
+function _menu_tree_data($result = NULL, $depth = 1, $previous_element = '') {
+  $padding = 0;
   $remnant = NULL;
-  $tree = '';
-  // Fetch the current path and cache it.
-  if (!isset($map)) {
-    $map = arg(NULL);
-  }
+  $tree = array();
   while ($item = db_fetch_object($result)) {
     // Access check and handle dynamic path translation.
-    _menu_translate($item, $map, MENU_RENDER_LINK);
+    _menu_link_translate($item);
     if (!$item->access) {
       continue;
     }
-    if ($item->attributes) {
-      $item->attributes = unserialize($item->attributes);
-    }
+    $index = $previous_element ? ($previous_element->weight .' '. $previous_element->title . $padding++) : '';
     // The current item is the first in a new submenu.
     if ($item->depth > $depth) {
-      // _menu_tree returns an item and the HTML of the rendered menu tree.
-      list($item, $menu) = _menu_tree($result, $item->depth, theme('menu_item_link', $item), $item->has_children);
-      // Theme the menu.
-      $menu = $menu ? theme('menu_tree', $menu) : '';
-      // $link is the previous element.
-      $tree .= $link ? theme('menu_item', $link, $has_children, $menu) : $menu;
+      // _menu_tree returns an item and the menu tree structure.
+      list($item, $below) = _menu_tree_data($result, $item->depth, $item);
+      $tree[$index] = array(
+        'link' => $previous_element,
+        'below' => $below,
+        );
       // This will be the link to be output in the next iteration.
-      $link = $item ? theme('menu_item_link', $item) : '';
-      $has_children = $item ? $item->has_children : FALSE;
+      $previous_element = $item;
     }
-    // We are in the same menu. We render the previous element.
+    // We are in the same menu. We render the previous element, $previous_element.
     elseif ($item->depth == $depth) {
-      // $link is the previous element.
-      $tree .= theme('menu_item', $link, $has_children);
+      if (!empty($previous_element)) { // Only the first time
+        $tree[$index] = array(
+          'link' => $previous_element,
+          'below' => '',
+        );
+      }
       // This will be the link to be output in the next iteration.
-      $link = theme('menu_item_link', $item);
-      $has_children = $item->has_children;
+      $previous_element = $item;
     }
     // The submenu ended with the previous item, we need to pass back the
     // current element.
@@ -553,19 +641,23 @@ function _menu_tree($result = NULL, $dep
       break;
     }
   }
-  if ($link) {
+  if ($previous_element) {
     // We have one more link dangling.
-    $tree .= theme('menu_item', $link, $has_children);
+    $tree[$previous_element->weight .' '. $previous_element->title .' '. $padding++] = array(
+      'link' => $previous_element,
+      'below' => '',
+    );
   }
+  ksort($tree);
   return array($remnant, $tree);
 }
 
+
 /**
  * Generate the HTML output for a single menu link.
  */
-function theme_menu_item_link($item) {
-  $link = (array)$item;
-  return l($link['title'], $link['link_path'], $link);
+function theme_menu_item_link($link) {
+  return l($link->link_text, $link->link_path, $link->options);
 }
 
 /**
@@ -616,320 +708,34 @@ function menu_get_active_help() {
   return $output;
 }
 
-function menu_path_is_external($path) {
-  $colonpos = strpos($path, ':');
-  return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path);
-}
-
 /**
- * Populate the database representation of the menu.
+ * Build a list of named menus.
  */
-function menu_rebuild() {
-  // TODO: split menu and menu links storage.
-  $menu = module_invoke_all('menu');
-
-  // Alter the menu as defined in modules, keys are like user/%user.
-  drupal_alter('menu', $menu, MENU_ALTER_MODULE_DEFINED);
-  db_query('DELETE FROM {menu}');
-  $mid = 1;
-
-  // First pass: separate callbacks from pathes, making pathes ready for
-  // matching. Calculate fitness, and fill some default values.
-  foreach ($menu as $path => $item) {
-    $load_functions = array();
-    $to_arg_functions = array();
-    $fit = 0;
-    $move = FALSE;
-    if (!isset($item['_external'])) {
-      $item['_external'] = menu_path_is_external($path);
-    }
-    if ($item['_external']) {
-      $number_parts = 0;
-      $parts = array();
-    }
-    else {
-      $parts = explode('/', $path, 6);
-      $number_parts = count($parts);
-      // We store the highest index of parts here to save some work in the fit
-      // calculation loop.
-      $slashes = $number_parts - 1;
-      // extract functions
-      foreach ($parts as $k => $part) {
-        $match = FALSE;
-        if (preg_match('/^%([a-z_]*)$/', $part, $matches)) {
-          if (empty($matches[1])) {
-            $match = TRUE;
-            $load_functions[$k] = NULL;
-          }
-          else {
-            if (function_exists($matches[1] .'_to_arg')) {
-              $to_arg_functions[$k] = $matches[1] .'_to_arg';
-              $load_functions[$k] = NULL;
-              $match = TRUE;
-            }
-            if (function_exists($matches[1] .'_load')) {
-              $load_functions[$k] = $matches[1] .'_load';
-              $match = TRUE;
-            }
-          }
-        }
-        if ($match) {
-          $parts[$k] = '%';
-        }
-        else {
-          $fit |=  1 << ($slashes - $k);
-        }
-      }
-      if ($fit) {
-        $move = TRUE;
-      }
-      else {
-        // If there is no %, it fits maximally.
-        $fit = (1 << $number_parts) - 1;
-      }
-    }
-    $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
-    $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
-    $item += array(
-      'title' => '',
-      'weight' => 0,
-      'type' => MENU_NORMAL_ITEM,
-      '_number_parts' => $number_parts,
-      '_parts' => $parts,
-      '_fit' => $fit,
-      '_mid' => $mid++,
-      '_children' => array(),
-    );
-    $item += array(
-      '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_TREE),
-      '_tab' => (bool)($item['type'] & MENU_IS_LOCAL_TASK),
-    );
-    if ($move) {
-      $new_path = implode('/', $item['_parts']);
-      unset($menu[$path]);
-    }
-    else {
-      $new_path = $path;
-    }
-    $menu_path_map[$path] = $new_path;
-    $menu[$new_path] = $item;
-  }
-
-  // Alter the menu after the first preprocessing phase, keys are like user/%.
-  drupal_alter('menu', $menu, MENU_ALTER_PREPROCESSED);
-
-  $menu_path_map[''] = '';
-  // Second pass: prepare for sorting and find parents.
-  foreach ($menu as $path => $item) {
-    $item = &$menu[$path];
-    $parent_path = $path;
-    $parents = array($item['_mid']);
-    $depth = 1;
-    if (isset($item['parent']) && isset($menu_path_map[$item['parent']])) {
-      $item['parent'] = $menu_path_map[$item['parent']];
-    }
-    if ($item['_visible'] || $item['_tab']) {
-      while ($parent_path) {
-        if (isset($menu[$parent_path]['parent'])) {
-          if (isset($menu_path_map[$menu[$parent_path]['parent']])) {
-            $menu[$parent_path]['parent'] = $menu_path_map[$menu[$parent_path]['parent']];
-          }
-          $parent_path = $menu[$parent_path]['parent'];
-        }
-        else {
-          $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
-        }
-        if (isset($menu[$parent_path]) && $menu[$parent_path]['_visible']) {
-          $parent = $menu[$parent_path];
-          $parents[] = $parent['_mid'];
-          $depth++;
-          if (!isset($item['_pid'])) {
-            $item['_pid'] = $parent['_mid'];
-            $item['_visible_parent_path'] = $parent_path;
-          }
-        }
-      }
-    }
-    $parents[] = 0;
-    $parents = implode(',', array_reverse($parents));
-    // Store variables and set defaults.
-    $item += array(
-      '_pid' => 0,
-      '_depth' => ($item['_visible'] ? $depth : $item['_number_parts']),
-      '_parents' => $parents,
-      '_slashes' => $slashes,
-    );
-    // This sorting works correctly only with positive numbers,
-    // so we shift negative weights to be positive.
-    $sort[$path] = $item['_depth'] . sprintf('%05d', $item['weight'] + 50000) . $item['title'];
-    unset($item);
-  }
-  array_multisort($sort, $menu);
+function menu_get_names($reset = FALSE) {
+  static $names;
+  // TODO - use cache system to save this
 
-  // We are now sorted, so let's build the tree.
-  $children = array();
-  foreach ($menu as $path => $item) {
-    if (!empty($item['_pid'])) {
-      $menu[$item['_visible_parent_path']]['_children'][] = $path;
-    }
-  }
-  menu_renumber($menu);
-
-  // Apply inheritance rules.
-  foreach ($menu as $path => $item) {
-    if ($item['_external']) {
-      $item['access callback'] = 1;
-    }
-    else {
-      $item = &$menu[$path];
-      for ($i = $item['_number_parts'] - 1; $i; $i--) {
-        $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
-        if (isset($menu[$parent_path])) {
-          $parent = $menu[$parent_path];
-          // If a callback is not found, we try to find the first parent that
-          // has this callback. When found, its callback argument will also be
-          // copied but only if there is none in the current item.
-
-          // Because access is checked for each visible parent as well, we only
-          // inherit if arguments were given without a callback. Otherwise the
-          // inherited check would be identical to that of the parent. We do
-          // not inherit from visible parents which are themselves inherited.
-          if (!isset($item['access callback']) && isset($parent['access callback']) && !(isset($parent['access inherited']) && $parent['_visible'])) {
-            if (isset($item['access arguments'])) {
-              $item['access callback'] = $parent['access callback'];
-            }
-            else {
-              $item['access callback'] = 1;
-              // If a children of this element has an argument, we need to pair
-              // that with a real callback, not the 1 we set above.
-              $item['access inherited'] = TRUE;
-            }
-          }
-
-          // Unlike access callbacks, there are no shortcuts for page callbacks.
-          if (!isset($item['page callback']) && isset($parent['page callback'])) {
-            $item['page callback'] = $parent['page callback'];
-            if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
-              $item['page arguments'] = $parent['page arguments'];
-            }
-          }
-        }
-      }
-      if (!isset($item['access callback'])) {
-        $item['access callback'] = isset($item['access arguments']) ? 'user_access' : 0;
-      }
-      if (is_bool($item['access callback'])) {
-        $item['access callback'] = intval($item['access callback']);
-      }
-      if (empty($item['page callback'])) {
-        $item['access callback'] = 0;
-      }
-    }
-
-    if ($item['_tab']) {
-      if (isset($item['parent'])) {
-        $item['_depth'] = $item['parent'] ? $menu[$item['parent']]['_depth'] + 1 : 1;
-      }
-      else {
-        $item['parent'] = implode('/', array_slice($item['_parts'], 0, $item['_number_parts'] - 1));
-      }
-    }
-    else {
-      // Non-tab items specified the parent for visible links, and it's
-      // stored in parents, parent stores the tab parent.
-      $item['parent'] = $path;
-    }
-
-    $insert_item = $item;
-    unset($item);
-    $item = $insert_item + array(
-      'access arguments' => array(),
-      'access callback' => '',
-      'page arguments' => array(),
-      'page callback' => '',
-      '_mleft' => 0,
-      '_mright' => 0,
-      'block callback' => '',
-      'title arguments' => array(),
-      'title callback' => 't',
-      'description' => '',
-      'position' => '',
-      'attributes' => '',
-      'query' => '',
-      'fragment' => '',
-      'absolute' => '',
-      'html' => '',
-    );
-    $link_path = $item['to_arg_functions'] ? $path : drupal_get_path_alias($path);
-
-    $item['title arguments'] = empty($item['title arguments']) ? '' : serialize($item['title arguments']);
-
-    if ($item['attributes']) {
-      $item['attributes'] = serialize($item['attributes']);
-    }
-
-    // Check for children that are visible in the menu
-    $has_children = FALSE;
-    foreach ($item['_children'] as $child) {
-      if ($menu[$child]['_visible']) {
-        $has_children = TRUE;
-        break;
-      }
-    }
-    // We remove disabled items here -- this way they will be numbered in the
-    // tree so the menu overview screen can show them.
-    if (!empty($item['disabled'])) {
-      $item['_visible'] = FALSE;
-    }
-    db_query("INSERT INTO {menu} (
-      mid, pid, path, load_functions, to_arg_functions,
-      access_callback, access_arguments, page_callback, page_arguments,
-      title_callback, title_arguments, fit, number_parts, visible,
-      parents, depth, has_children, tab, title, parent,
-      type, mleft, mright, block_callback, description, position,
-      link_path, attributes, query, fragment, absolute, html)
-      VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s',
-      %d, %d, %d, '%s', %d, %d, %d, '%s', '%s', '%s', %d, %d, '%s', '%s',
-      '%s', '%s', '%s', '%s', '%s', %d, %d)",
-      $item['_mid'], $item['_pid'], $path, $item['load_functions'],
-      $item['to_arg_functions'], $item['access callback'],
-      serialize($item['access arguments']), $item['page callback'],
-      serialize($item['page arguments']), $item['title callback'],
-      $item['title arguments'], $item['_fit'],
-      $item['_number_parts'], $item['_visible'], $item['_parents'],
-      $item['_depth'], $has_children, $item['_tab'],
-      $item['title'], $item['parent'], $item['type'], $item['_mleft'],
-      $item['_mright'], $item['block callback'], $item['description'],
-      $item['position'], $link_path,
-      $item['attributes'], $item['query'], $item['fragment'],
-      $item['absolute'], $item['html']);
-  }
-}
-
-function menu_renumber(&$tree) {
-  foreach ($tree as $key => $element) {
-    if (!isset($tree[$key]['_mleft'])) {
-      _menu_renumber($tree, $key);
+  if ($reset || empty($names)) {
+    $names = array();
+    $result = db_query("SELECT DISTINCT(menu_name) FROM {menu_links} ORDER BY menu_name");
+    while ($n = db_fetch_array($result)) {
+      $names[] = $n['menu_name'];
     }
   }
+  return $names;
 }
 
-function _menu_renumber(&$tree, $key) {
-  static $counter = 1;
-  if (!isset($tree[$key]['_mleft'])) {
-    $tree[$key]['_mleft'] = $counter++;
-    foreach ($tree[$key]['_children'] as $child_key) {
-      _menu_renumber($tree, $child_key);
-    }
-    $tree[$key]['_mright'] = $counter++;
-  }
-}
-
-// Placeholders.
+// Placeholders - the real code should be in menu.module?
 function menu_primary_links() {
+  if (module_exists('menu')) {
+    return _menu_primary_links();
+  }
+  return array();
 }
 
 function menu_secondary_links() {
+  
+  return array();
 }
 
 /**
@@ -941,59 +747,76 @@ function menu_secondary_links() {
  *   An array of links to the tabs.
  */
 function menu_local_tasks($level = 0) {
-  static $tabs = array(), $parents = array(), $parents_done = array();
+  static $tabs = array();
+
   if (empty($tabs)) {
     $router_item = menu_get_item();
     if (!$router_item || !$router_item->access) {
       return array();
     }
-    $map = arg(NULL);
-    do {
-      // Tabs are router items that have the same parent. If there is a new
-      // parent, let's add it the queue.
-      if (!empty($router_item->parent)) {
-        $parents[] = $router_item->parent;
-        // Do not add the same item twice.
-        $router_item->parent = '';
-      }
-      $parent = array_shift($parents);
-      // Do not process the same parent twice.
-      if (isset($parents_done[$parent])) {
-        continue;
+    // Get all tabs
+    $result = db_query("SELECT * FROM {menu} 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;
+    }
+
+    // Find all tabs below the current path
+    $path = $router_item->path;
+    while (isset($children[$path])) {
+      $tabs_current = '';
+      $next_path = '';
+      foreach ($children[$path] as $item) {
+         _menu_translate($item, $map);
+        if ($item->access) {
+          $link = l($item->title, $item->link_path); // TODO options?
+          // The default task is always active.
+          if ($item->type == MENU_DEFAULT_LOCAL_TASK) {
+            $tabs_current .= theme('menu_local_task', $link, TRUE);
+            $next_path = $item->path;
+          }
+          else {
+            $tabs_current .= theme('menu_local_task', $link);
+          }
+        }
       }
-      // This loads all the tabs.
-      $result = db_query("SELECT * FROM {menu} WHERE parent = '%s' AND tab = 1 ORDER BY mleft", $parent);
+      $path = $next_path;
+      $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;
+    $current = $router_item;
+    while (isset($children[$parent])) {
       $tabs_current = '';
-      while ($item = db_fetch_object($result)) {
-        // This call changes the path from for example user/% to user/123 and
-        // also determines whether we are allowed to access it.
-         _menu_translate($item, $map, MENU_RENDER_LINK);
+      $next_path = '';
+      $next_parent = '';
+      foreach ($children[$parent] as $item) {
+         _menu_translate($item, $map);
         if ($item->access) {
-          $depth = $item->depth;
-          $link = l($item->title, $item->link_path, (array)$item);
+          $link = l($item->title, $item->link_path); // TODO options?
           // We check for the active tab.
-          if ($item->path == $router_item->path || (!$router_item->tab && $item->type == MENU_DEFAULT_LOCAL_TASK)) {
+          if ($item->path == $path) {
             $tabs_current .= theme('menu_local_task', $link, TRUE);
-            // Let's try to find the router item one level up.
-            $next_router_item = db_fetch_object(db_query("SELECT path, tab, parent FROM {menu} WHERE path = '%s'", $item->parent));
-            // We will need to inspect one level down.
-            $parents[] = $item->path;
+            $next_path = $item->tab_parent;
+            if (isset($tab_parent[$next_path])) {
+              $next_parent = $tab_parent[$next_path];
+            }
           }
           else {
             $tabs_current .= theme('menu_local_task', $link);
           }
         }
       }
-      // If there are tabs, let's add them
-      if ($tabs_current) {
-        $tabs[$depth] = $tabs_current;
-      }
-      $parents_done[$parent] = TRUE;
-      if (isset($next_router_item)) {
-        $router_item = $next_router_item;
-      }
-      unset($next_router_item);
-    } while ($parents);
+      $path = $next_path;
+      $parent = $next_parent;
+      $tabs[$item->number_parts] = $tabs_current;
+    }
     // Sort by depth
     ksort($tabs);
     // Remove the depth, we are interested only in their relative placement.
@@ -1010,7 +833,58 @@ function menu_secondary_local_tasks() {
   return menu_local_tasks(1);
 }
 
-function menu_set_active_item() {
+function menu_set_active_menu_name($menu_name = NULL) {
+  static $active;
+
+  if (isset($menu_name)) {
+    $active = $menu_name;
+  }
+  elseif (!isset($active)) {
+    $active = 'navigation';
+  }
+  return $active;
+}
+
+function menu_get_active_menu_name() {
+  return menu_set_active_menu_name();
+}
+
+function menu_set_active_trail($new_trail = NULL) {
+  static $trail;
+
+  if (isset($new_trail)) {
+    $trail = $new_trail;
+  }
+  elseif (!isset($trail)) {
+    $trail = array();
+    $item = menu_get_item();
+    if ($item->tab_parent) { // We are on a tab
+      $link_path = $item->tab_root;
+    }
+    else {
+      $link_path = $item->link_path;
+    }
+    $tree = menu_tree_data(menu_get_active_menu_name());
+    $curr = array_shift($tree);
+    while ($curr) {
+      if ($curr['link']->link_path == $link_path){
+        $trail[] = $curr['link'];
+        $curr = FALSE;
+      }
+      else {
+        if ($curr['below']) {
+          $trail[] = $curr['link'];
+          $tree = $curr['below'];
+        }
+        $curr = array_shift($tree);
+      }
+    }
+  }
+  return $trail;
+}
+
+function menu_get_active_trail() {
+  return menu_set_active_trail();
 }
 
 function menu_set_location() {
@@ -1020,35 +894,37 @@ function menu_get_active_breadcrumb() {
   $breadcrumb = array(l(t('Home'), ''));
   $item = menu_get_item();
   if ($item && $item->access) {
-    foreach ($item->active_trail as $parent) {
-      $breadcrumb[] = l($parent->title, $parent->link_path, (array)$parent);
+    $active_trail = menu_get_active_trail();
+    foreach ($active_trail as $parent) {
+      $breadcrumb[] = l($parent->link_text, $parent->link_path, $parent->options);
     }
   }
   return $breadcrumb;
 }
 
 function menu_get_active_title() {
-  $item = menu_get_item();
-  foreach (array_reverse($item->active_trail) as $item) {
-    if (!($item->type & MENU_IS_LOCAL_TASK)) {
+  $active_trail = menu_get_active_trail();
+
+  foreach (array_reverse($active_trail) as $item) {
+    if (!(bool)($item->type & MENU_IS_LOCAL_TASK)) {
       return $item->title;
     }
   }
 }
 
 /**
- * Get a menu item by its mid, access checked and link translated for
+ * Get a menu item by its mlid, access checked and link translated for
  * rendering.
  *
- * @param $mid
- *   The mid of the menu item.
+ * @param $mlid
+ *   The mlid of the menu item.
  * @return
  *   A menu object, with $item->access filled and link translated for
  *   rendering.
  */
-function menu_get_item_by_mid($mid) {
-  if ($item = db_fetch_object(db_query('SELECT * FROM {menu} WHERE mid = %d', $mid))) {
-    _menu_translate($item, arg(), MENU_RENDER_LINK);
+function menu_get_item_by_mlid($mlid) {
+  if ($item = db_fetch_object(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu} m ON m.path = ml.path WHERE mlid = %d", $mlid))) {
+    _menu_link_translate($item);
     if ($item->access) {
       return $item;
     }
@@ -1057,20 +933,355 @@ function menu_get_item_by_mid($mid) {
 }
 
 /**
- * Returns the rendered local tasks. The default implementation renders
- * them as tabs.
- *
- * @ingroup themeable
- */
+* Returns the rendered local tasks. The default implementation renders
+* them as tabs.
+*
+* @ingroup themeable
+*/
 function theme_menu_local_tasks() {
-  $output = '';
+ $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;
+}
+
+/**
+ * Populate the database representation of the {menu{ table (router items)
+ * and the navigation menu in the {menu_links} table.
+ */
+function menu_rebuild() {
+  $menu = menu_router_build(TRUE);
+  // Clear out the navigation menu
+  db_query("DELETE FROM {menu_links} WHERE menu_name = 'navigation'");
+  menu_navigation_links_rebuild($menu);
+  module_invoke_all('navigation_links', $menu);
+}
+
+function menu_router_build($reset = FALSE) {
+  db_query('DELETE FROM {menu}');
+  $callbacks = module_invoke_all('menu');
+  // Alter the menu as defined in modules, keys are like user/%user.
+  drupal_alter('menu', $callbacks);
+  $menu = _menu_router_build($callbacks);
+  return $menu;
+}
 
-  if ($primary = menu_primary_local_tasks()) {
-    $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
+function menu_navigation_links_rebuild($menu) {
+
+  // Add normal and suggested items as links.
+  $menu_links = array();
+  foreach ($menu as $path => $item) {
+    if ($item['_visible']) {
+      // All visible router items are added to the default menu
+      $item['menu name'] = 'navigation';
+      $item['link text'] = $item['title'];
+      $item['link path'] = $path;
+      $menu_links[$path] = $item;
+      $sort[$path] = $item['_number_parts'];
+    }
+  }
+  if ($menu_links) {
+    // Make sure no children comes before its parent.
+    array_multisort($sort, SORT_NUMERIC, $menu_links);
+    
+    foreach ($menu_links as $link_path => $item) {
+      menu_link_save($item, $menu);
+    }
   }
-  if ($secondary = menu_secondary_local_tasks()) {
-    $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
+}
+
+function menu_link_save($item, $_menu = NULL) {
+  static $menu;
+
+  if (isset($_menu)) {
+    $menu = $_menu;
+  }
+  elseif (!isset($menu)) {
+    $menu = menu_router_build();
+  }
+
+  drupal_alter('menu_link', $item, $menu);
+  $item += array(
+    'menu name' => 'navigation',
+    '_external' => menu_path_is_external($item['path']),
+  );
+  // Load defaults.
+  $item += array(
+    'weight' => 0,
+    'title' => '',
+    'has_children' => 0,
+    'title' => '',
+    'weight' => 0,
+    'options' => array(),
+    'disabled' => FALSE,
+  );
+  if (isset($item['mlid'])) {
+    $existing_item = db_fetch_array(db_query('SELECT * FROM {menu_links} WHERE mlid = %d', $item['mlid']));
   }
+  else {
+    $existing_item = array();
+    $item['mlid'] = db_next_id('{menu_links}_mlid');
+  }
+  $menu_name = $item['menu name'];
 
-  return $output;
+  // Find the access callback.
+  if (empty($item['router_path'])) {
+    if ($item['_external']) {
+      $item['router_path'] = '';
+    }
+    else {
+      // Find the router path which will serve this paths.
+      $item['parts'] = explode('/', $item['link path'], 6);
+      $item['router path'] = $item['link path'];
+      if (!isset($menu[$item['router path']]) || !isset($menu[$item['router path']]['access callback'])) {
+        list($ancestors) = menu_get_ancestors($item['parts']);
+        while ($ancestors && (!isset($menu[$item['router path']]) || !isset($menu[$item['router path']]['access callback']))) {
+          $item['router path'] = array_pop($ancestors);
+        }
+      }
+      if (empty($item['router path'])) {
+        return FALSE;
+      }
+    }
+  }
+
+  // Find the parent.
+  if (isset($item['parent'])) {
+    $parent = db_fetch_object(db_query("SELECT mlid, parents, matrix FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['parent']));
+    $parent_path = $item['parent'];
+  }
+  else {
+    $parent_path = $item['link path'];
+    do {
+      $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
+      $parent = db_fetch_object(db_query("SELECT mlid, parents, matrix, depth FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $parent_path));
+    } while ($parent === FALSE && $parent_path);
+  }
+  $item['parent'] = $parent_path;
+
+  if (!$existing_item || $item['parent'] != $existing_item['parent']) {
+    // Number the item.
+    if ($parent == FALSE) {
+      // We are top level.
+      $counter = db_next_id('{menu_links}_counter');
+      $matrix = array($counter, 1, 1, 0);
+      $plid = 0;
+      $parents = array(0, $item['mlid']);
+      $item['depth'] = 1;
+    }
+    else {
+      db_lock_table('menu_links');
+      if (!is_object($parent)) {var_dump($parent);exit;}
+      $counter = 1 + db_result(db_query('SELECT counter FROM {menu_links} WHERE mlid = %d', $parent->mlid));
+      db_query('UPDATE {menu_links} SET has_children = 1, counter = counter + 1 WHERE mlid = %d', $parent->mlid);
+      if ($existing_item) {
+        $has_children = db_num_rows(db_query_range('SELECT mlid FROM {menu_links} WHERE plid = %d AND mlid != %d', $existing_item['plid'], $existing_item['mlid'], 0, 1));
+        db_query('UPDATE {menu_links} SET has_children = %d WHERE mlid = %d', $has_children, $existing_item['plid']);
+      }
+      db_unlock_tables();
+      $item['depth'] = $parent->depth + 1;
+      $m = unserialize($parent->matrix);
+      $matrix = array($m[0] * $counter + $m[1], $m[0], $m[2] * $counter + $m[3], $m[2]);
+      $plid = $parent->mlid;
+      $parents = explode(',', $parent->parents);
+      $parents[] = $item['mlid'];
+    }
+    $a = $matrix[0] / $matrix[2];
+    $b = ($matrix[0] + $matrix[1]) / ($matrix[2] + $matrix[3]);
+    $item += array(
+      'matrix' => serialize($matrix),
+      'mleft' => min($a, $b),
+      'mright' => max($a, $b),
+      'parents' => implode(',', $parents),
+      'plid' => $plid,
+    );
+  }
+  if ($existing_item) {
+    echo $item['link path'];
+    exit;
+  }
+  else {
+    db_query("INSERT INTO {menu_links} (
+      menu_name, mlid, plid, link_path,
+      parent, disabled, external,
+      router_path, parents, has_children,
+      weight, mleft, mright, link_text,
+      depth, options, matrix) VALUES (
+      '%s', %d, %d, '%s',
+      '%s', %d, %d,
+      '%s', '%s', %d,
+      %d, %f, %f, '%s',
+      %d, '%s', '%s')",
+      $item['menu name'], $item['mlid'], $item['plid'], $item['link path'],
+      $item['parent'], $item['disabled'], $item['_external'],
+      $item['router path'], $item['parents'], $item['has_children'],
+      $item['weight'] + 50000, $item['mleft'], $item['mright'], $item['link text'],
+      $item['depth'], serialize($item['options']), $item['matrix']);
+  }
 }
+
+
+function _menu_router_build($callbacks) {
+  // First pass: separate callbacks from pathes, making pathes ready for
+  // matching. Calculate fitness, and fill some default values.
+  $menu = array();
+  foreach ($callbacks as $path => $item) {
+    $load_functions = array();
+    $to_arg_functions = array();
+    $fit = 0;
+    $move = FALSE;
+
+    $parts = explode('/', $path, 6);
+    $number_parts = count($parts);
+    // We store the highest index of parts here to save some work in the fit
+    // calculation loop.
+    $slashes = $number_parts - 1;
+    // extract functions
+    foreach ($parts as $k => $part) {
+      $match = FALSE;
+      if (preg_match('/^%([a-z_]*)$/', $part, $matches)) {
+        if (empty($matches[1])) {
+          $match = TRUE;
+          $load_functions[$k] = NULL;
+        }
+        else {
+          if (function_exists($matches[1] .'_to_arg')) {
+            $to_arg_functions[$k] = $matches[1] .'_to_arg';
+            $load_functions[$k] = NULL;
+            $match = TRUE;
+          }
+          if (function_exists($matches[1] .'_load')) {
+            $load_functions[$k] = $matches[1] .'_load';
+            $match = TRUE;
+          }
+        }
+      }
+      if ($match) {
+        $parts[$k] = '%';
+      }
+      else {
+        $fit |=  1 << ($slashes - $k);
+      }
+    }
+    if ($fit) {
+      $move = TRUE;
+    }
+    else {
+      // If there is no %, it fits maximally.
+      $fit = (1 << $number_parts) - 1;
+    }
+    $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
+    $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
+    $item += array(
+      'title' => '',
+      'weight' => 0,
+      'type' => MENU_NORMAL_ITEM,
+      '_number_parts' => $number_parts,
+      '_parts' => $parts,
+      '_fit' => $fit,
+    );
+    $item += array(
+      '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_TREE),
+      '_tab' => (bool)($item['type'] & MENU_IS_LOCAL_TASK),
+    );
+    if ($move) {
+      $new_path = implode('/', $item['_parts']);
+      $menu[$new_path] = $item;
+    }
+    else {
+      $menu[$path] = $item;
+    }
+  }
+
+  // Apply inheritance rules.
+  foreach ($menu as $path => $v) {
+    $item = &$menu[$path];
+    if (!isset($item['access callback']) && isset($item['access arguments'])) {
+      $item['access callback'] = 'user_access'; // Default callback
+    }
+    if (!$item['_tab']) {
+      // Non-tab items
+      $item['tab_parent'] = '';
+      $item['tab_root'] = $path;
+    }
+    for ($i = $item['_number_parts'] - 1; $i; $i--) {
+      $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
+      if (isset($menu[$parent_path])) {
+
+        $parent = $menu[$parent_path];
+
+        if (!isset($item['tab_parent'])) {
+          // parent stores the parent of the path.
+          $item['tab_parent'] = $parent_path;
+        }
+        if (!isset($item['tab_root']) && !$parent['_tab']) {
+          $item['tab_root'] = $parent_path;
+        }
+        // If a callback is not found, we try to find the first parent that
+        // has a callback.
+        if (!isset($item['access callback']) && isset($parent['access callback'])) {
+          $item['access callback'] = $parent['access callback'];
+          if (!isset($item['access arguments']) && isset($parent['access arguments'])) {
+            $item['access arguments'] = $parent['access arguments'];
+          }
+        }
+        // Same for page callbacks.
+        if (!isset($item['page callback']) && isset($parent['page callback'])) {
+          $item['page callback'] = $parent['page callback'];
+          if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
+            $item['page arguments'] = $parent['page arguments'];
+          }
+        }
+      }
+    }
+    if (!isset($item['access callback']) || empty($item['page callback'])) {
+      $item['access callback'] = 0;
+    }
+    if (is_bool($item['access callback'])) {
+      $item['access callback'] = intval($item['access callback']);
+    }
+
+    $item += array(
+      'access arguments' => array(),
+      'access callback' => '',
+      'page arguments' => array(),
+      'page callback' => '',
+      'block callback' => '',
+      'title arguments' => array(),
+      'title callback' => 't',
+      'description' => '',
+      'position' => '',
+      'tab_parent' => '',
+      'tab_root' => $path,
+      'path' => $path,
+    );
+    db_query("INSERT INTO {menu}
+      (path, load_functions, to_arg_functions, access_callback,
+      access_arguments, page_callback, page_arguments, fit,
+      number_parts, tab_parent, tab_root,
+      title, title_callback, title_arguments,
+      type, block_callback, description, position, weight)
+      VALUES ('%s', '%s', '%s', '%s',
+      '%s', '%s', '%s', %d,
+      %d, '%s', '%s',
+      '%s', '%s', '%s',
+      %d, '%s', '%s', '%s', %d)",
+      $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'],
+      serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'],
+      $item['_number_parts'], $item['tab_parent'], $item['tab_root'],
+      $item['title'], $item['title callback'], serialize($item['title arguments']),
+      $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight']);
+  }
+  return $menu;
+}
+
+function menu_path_is_external($path) {
+  $colonpos = strpos($path, ':');
+  return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path);
+}
\ No newline at end of file
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.102
diff -u -p -r1.102 system.install
--- modules/system/system.install	6 May 2007 05:50:43 -0000	1.102
+++ modules/system/system.install	7 May 2007 01:59:26 -0000
@@ -336,8 +336,6 @@ function system_install() {
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
       db_query("CREATE TABLE {menu} (
-        mid int NOT NULL default 0,
-        pid int NOT NULL default 0,
         path varchar(255) NOT NULL default '',
         load_functions varchar(255) NOT NULL default '',
         to_arg_functions varchar(255) NOT NULL default '',
@@ -347,32 +345,43 @@ function system_install() {
         page_arguments text,
         fit int NOT NULL default 0,
         number_parts int NOT NULL default 0,
-        mleft int NOT NULL default 0,
-        mright int NOT NULL default 0,
-        visible int NOT NULL default 0,
-        parents varchar(255) NOT NULL default '',
-        depth int NOT NULL default 0,
-        has_children int NOT NULL default 0,
-        tab 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 '',
-        parent varchar(255) NOT NULL default '',
         type int NOT NULL default 0,
         block_callback varchar(255) NOT NULL default '',
         description varchar(255) NOT NULL default '',
         position varchar(255) NOT NULL default '',
-        link_path varchar(255) NOT NULL default '',
-        attributes varchar(255) NOT NULL default '',
-        query varchar(255) NOT NULL default '',
-        fragment varchar(255) NOT NULL default '',
-        absolute INT NOT NULL default 0,
-        html INT NOT NULL default 0,
+        weight int NOT NULL default 0,
         PRIMARY KEY  (path),
         KEY fit (fit),
-        KEY visible (visible),
-        KEY pid (pid),
-        KEY parent (parent)
+        KEY tab_parent (tab_parent)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+
+      db_query("CREATE TABLE {menu_links} (
+        menu_name varchar(64) NOT NULL default '',
+        mlid int NOT NULL default 0,
+        plid int NOT NULL default 0,
+        link_path varchar(255) NOT NULL default '',
+        parent varchar(255) NOT NULL default '',
+        router_path varchar(255) NOT NULL default '',
+        link_text varchar(255) NOT NULL default '',
+        disabled smallint NOT NULL default 0,
+        external smallint NOT NULL default 0,
+        mleft double NOT NULL default 0,
+        mright double NOT NULL default 0,
+        parents varchar(255) NOT NULL default '',
+        has_children int NOT NULL default 0,
+        depth int NOT NULL default 0,
+        counter int NOT NULL default 0,
+        weight int NOT NULL default 0,
+        options text,
+        matrix varchar(255) NOT NULL,
+        PRIMARY KEY (mlid),
+        KEY menu_name(menu_name),
+        KEY plid (plid)
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
       db_query("CREATE TABLE {node} (
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.474
diff -u -p -r1.474 system.module
--- modules/system/system.module	6 May 2007 05:47:52 -0000	1.474
+++ modules/system/system.module	7 May 2007 01:59:27 -0000
@@ -394,11 +394,9 @@ function system_main_admin_page($arg = N
   if (system_status(TRUE)) {
     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');
   }
-
-  $map = arg(NULL);
-  $result = db_query("SELECT * FROM {menu} WHERE path LIKE 'admin/%%' AND depth = 2 AND visible = 1 AND path != 'admin/help' ORDER BY mleft");
+  $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu} m ON ml.router_path = m.path WHERE ml.parent = 'admin' AND link_path != 'admin/help' AND menu_name = 'navigation' ORDER BY mleft ASC, mright DESC");
   while ($item = db_fetch_object($result)) {
-    _menu_translate($item, $map, MENU_RENDER_LINK);
+    _menu_link_translate($item);
     if (!$item->access) {
       continue;
     }
@@ -418,11 +416,10 @@ function system_main_admin_page($arg = N
  * Provide a single block on the administration overview page.
  */
 function system_admin_menu_block($item) {
-  $map = arg(NULL);
   $content = array();
-  $result = db_query('SELECT * FROM {menu} WHERE depth = %d AND %d < mleft AND mright < %d AND visible = 1 ORDER BY mleft', $item->depth + 1, $item->mleft, $item->mright);
+  $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu} m ON ml.router_path = m.path WHERE parent = '%s' AND menu_name = 'navigation' ORDER BY mleft ASC, mright DESC", $item->path);
   while ($item = db_fetch_object($result)) {
-    _menu_translate($item, $map, MENU_RENDER_LINK);
+    _menu_link_translate($item);
     if (!$item->access) {
       continue;
     }
@@ -2401,14 +2398,14 @@ function theme_admin_block_content($cont
         $item['attributes'] = array();
       }
       $item['attributes'] += array('title' => $item['description']);
-      $output .= '<li class="leaf">'. l($item['title'], $item['path'], $item) .'</li>';
+      $output .= '<li class="leaf">'. l($item['title'], $item['link_path'], $item['options']) .'</li>';
     }
     $output .= '</ul>';
   }
   else {
     $output = '<dl class="admin-list">';
     foreach ($content as $item) {
-      $output .= '<dt>'. l($item['title'], $item['path'], $item) .'</dt>';
+      $output .= '<dt>'. l($item['title'], $item['link_path'], $item['options']) .'</dt>';
       $output .= '<dd>'. $item['description'] .'</dd>';
     }
     $output .= '</dl>';
