Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.255.2.37
diff -u -p -r1.255.2.37 menu.inc
--- includes/menu.inc	4 Nov 2010 09:58:34 -0000	1.255.2.37
+++ includes/menu.inc	21 Nov 2010 04:57:16 -0000
@@ -1790,12 +1790,56 @@ function _menu_navigation_links_rebuild(
       $sort[$path] = $item['_number_parts'];
     }
   }
+
+  // Load up all existing router item links so we can handle CRUD properly.
+  $items_cache = array(
+    'rows' => array(),
+    'mlids' => array(),
+  );
+
+  // @todo: Reduce memory footprint somehow? Loading up all system menu links should be about the same size as $menu?
+  $result = db_query("SELECT * FROM {menu_links} WHERE module = '%s' ORDER BY depth DESC", 'system');
+  while ($row = db_fetch_array($result)) {
+    $items_cache['rows'][$row['mlid']] = $row;
+    $items_cache['mlids'][$row['link_path']][] = $row['mlid'];
+  }
+
   if ($menu_links) {
     // Make sure no child comes before its parent.
     array_multisort($sort, SORT_NUMERIC, $menu_links);
 
+    // Create a db map.
+    $db_map = array(
+      'menu_name' => 'menu_name',
+      'mlid' => 'mlid',
+      'plid' => 'plid',
+      'link_path' => 'link_path',
+      'router_path' => 'router_path',
+      'link_title' => 'link_title',
+      'options' => 'options',
+      'module' => 'module',
+      'hidden' => 'hidden',
+      '_external' => 'external',
+      'has_children' => 'has_children',
+      'expanded' => 'expanded',
+      'weight' => 'weight',
+      'depth' => 'depth',
+      'customized' => 'customized',
+      'p1' => 'p1',
+      'p2' => 'p2',
+      'p3' => 'p3',
+      'p4' => 'p4',
+      'p5' => 'p5',
+      'p6' => 'p6',
+      'p7' => 'p7',
+      'p8' => 'p8',
+      'p9' => 'p9',
+    );
+
+    $parent_updates = array();
     foreach ($menu_links as $item) {
-      $existing_item = db_fetch_array(db_query("SELECT mlid, menu_name, plid, customized, has_children, updated FROM {menu_links} WHERE link_path = '%s' AND module = '%s'", $item['link_path'], 'system'));
+      $existing_item = isset($items_cache['mlids'][$item['link_path']]) ? $items_cache['rows'][reset($items_cache['mlids'][$item['link_path']])] : FALSE;
+
       if ($existing_item) {
         $item['mlid'] = $existing_item['mlid'];
         // A change in hook_menu may move the link to a different menu
@@ -1807,7 +1851,114 @@ function _menu_navigation_links_rebuild(
         $item['updated'] = $existing_item['updated'];
       }
       if (!$existing_item || !$existing_item['customized']) {
-        menu_link_save($item);
+        $parent = ($item['plid'] && isset($items_cache['rows'][$item['plid']])) ? $items_cache['rows'][$item['plid']] : FALSE;
+        // Try and find the parent via the link path.
+        if (empty($parent)) {
+          $parent_path = $item['link_path'];
+          do {
+            $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
+            if (!$parent_path) {
+              break;
+            }
+            if (isset($items_cache['mlids'][$parent_path])) {
+              if (count($items_cache['mlids'][$parent_path]) == 1) {
+                $parent = $items_cache['rows'][reset($items_cache['mlids'][$parent_path])];
+                break;
+              }
+            }
+          } while (TRUE);
+        }
+
+        _menu_save_item($item, $existing_item, $parent, $menu);
+
+        // Save the item in the cache for parent lookups.
+        if (!empty($item['_existing_item'])) {
+          $items_cache['rows'][$item['_existing_item']['mlid']] = $item['_existing_item'];
+          $items_cache['mlids'][$item['_existing_item']['link_path']][] = $item['_existing_item']['mlid'];
+        }
+
+        // We only do an update if something changed.
+        $old_item = array(
+          'menu_name' => $existing_item['menu_name'],
+          'mlid' => $existing_item['mlid'],
+          'plid' => $existing_item['plid'],
+          'link_path' => $existing_item['link_path'],
+          'router_path' => $existing_item['router_path'],
+          'link_title' => $existing_item['link_title'],
+          'options' => $existing_item['options'],
+          'module' => $existing_item['module'],
+          'hidden' => $existing_item['hidden'],
+          'external' => (bool)$existing_item['external'],
+          'has_children' => $existing_item['has_children'],
+          'expanded' => $existing_item['expanded'],
+          'weight' => $existing_item['weight'],
+          'depth' => $existing_item['depth'],
+          'customized' => $existing_item['customized'],
+          'p1' => $existing_item['p1'],
+          'p2' => $existing_item['p2'],
+          'p3' => $existing_item['p3'],
+          'p4' => $existing_item['p4'],
+          'p5' => $existing_item['p5'],
+          'p6' => $existing_item['p6'],
+          'p7' => $existing_item['p7'],
+          'p8' => $existing_item['p8'],
+          'p9' => $existing_item['p9'],
+        );
+        $new_item = array(
+          'menu_name' => $item['menu_name'],
+          'mlid' => $item['mlid'],
+          'plid' => $item['plid'],
+          'link_path' => $item['link_path'],
+          'router_path' => $item['router_path'],
+          'link_title' => $item['link_title'],
+          'options' => serialize($item['options']),
+          'module' => $item['module'],
+          'hidden' => $item['hidden'],
+          'external' => $item['_external'],
+          'has_children' => $item['has_children'],
+          'expanded' => $item['expanded'],
+          'weight' => $item['weight'],
+          'depth' => $item['depth'],
+          'customized' => $item['customized'],
+          'p1' => $item['p1'],
+          'p2' => $item['p2'],
+          'p3' => $item['p3'],
+          'p4' => $item['p4'],
+          'p5' => $item['p5'],
+          'p6' => $item['p6'],
+          'p7' => $item['p7'],
+          'p8' => $item['p8'],
+          'p9' => $item['p9'],
+        );
+        $changes = array_diff_assoc($new_item, $old_item);
+
+        if ($old_hash != $new_hash) {
+          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, p7 = %d, p8 = %d, p9 = %d,
+            module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d",
+            $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['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'],
+            $item['module'],  $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
+        }
+
+        // Check the has_children status of the parent.
+        if ($item['plid'] && !$item['hidden']) {
+          $parent_updates[$item['plid']][$item['mlid']] = $item['hidden'];
+        }
+      }
+    }
+
+    // Update item parents.
+    foreach ($parent_updates as $plid => $children) {
+      if (!empty($children)) {
+        if (!$items_cache['rows'][$plid]['has_children']) {
+          db_query('UPDATE {menu_links} SET has_children = %d WHERE mlid = %d', 1, $plid);
+          $items_cache['rows'][$plid]['has_children'] = 1;
+        }
       }
     }
   }
@@ -1900,10 +2051,42 @@ function _menu_delete_item($item, $force
  *   saved.
  */
 function menu_link_save(&$item) {
+  _menu_save_item($item);
 
-  // Get the router if it's already in memory. $menu will be NULL, unless this
-  // is during a menu rebuild
-  $menu = _menu_router_cache();
+  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, p7 = %d, p8 = %d, p9 = %d,
+    module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d",
+    $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['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'],
+    $item['module'],  $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
+  // Check the has_children status of the parent.
+  _menu_update_parental_status($item);
+  menu_cache_clear($menu_name);
+  if ($existing_item && $menu_name != $existing_item['menu_name']) {
+    menu_cache_clear($existing_item['menu_name']);
+  }
+
+  _menu_clear_page_cache();
+  return $item['mlid'];
+}
+
+/**
+ * Helper function to save a menu item.
+ *
+ * @param $item
+ *   The item to save.
+ * @param $existing_item
+ *   The existing item to save on lookups. Internal use only.
+ * @param $parent
+ *   An existing parent to save on lookups. Internal use only.
+ * @param $menu
+ *   The menu when doing a full rebuild. Internal use only.
+ */
+function _menu_save_item(&$item, $existing_item = FALSE, $parent = FALSE, $menu = NULL) {
   drupal_alter('menu_link', $item, $menu);
 
   // This is the easiest way to handle the unique internal path '<front>',
@@ -1922,15 +2105,18 @@ function menu_link_save(&$item) {
     'customized' => 0,
     'updated' => 0,
   );
-  $existing_item = FALSE;
-  if (isset($item['mlid'])) {
+
+  if (!$existing_item) {
     $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['mlid']));
   }
 
-  if (isset($item['plid'])) {
-    $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
+  if (!$parent && isset($item['plid'])) {
+    // No point in running query if plid is 0.
+    if ($item['plid'] != 0) {
+      $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
+    }
   }
-  else {
+  elseif (!$parent) {
     // Find the parent - it must be unique.
     $parent_path = $item['link_path'];
     $where = "WHERE link_path = '%s'";
@@ -1985,6 +2171,23 @@ function menu_link_save(&$item) {
       $item['module'],  $item['link_title'], serialize($item['options']),
       $item['customized'], $item['updated']);
     $item['mlid'] = db_last_insert_id('menu_links', 'mlid');
+
+    // Allow the caller to cache this item.
+    $item['_existing_item'] = array(
+      'menu_name' => $item['menu_name'],
+      'mlid' => $item['mlid'],
+      'plid' => $item['plid'],
+      'link_path' => $item['link_path'],
+      'link_title' => $item['link_title'],
+      'options' => serialize($item['options']),
+      'module' => $item['module'],
+      'hidden' => $item['hidden'],
+      'external' => $item['_external'],
+      'has_children' => $item['has_children'],
+      'expanded' => $item['expanded'],
+      'weight' => $item['weight'],
+      'customized' => $item['customized'],
+    );
   }
 
   if (!$item['plid']) {
@@ -2024,25 +2227,6 @@ function menu_link_save(&$item) {
       $item['router_path'] = _menu_find_router_path($item['link_path']);
     }
   }
-  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, p7 = %d, p8 = %d, p9 = %d,
-    module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d",
-    $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['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'],
-    $item['module'],  $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
-  // Check the has_children status of the parent.
-  _menu_update_parental_status($item);
-  menu_cache_clear($menu_name);
-  if ($existing_item && $menu_name != $existing_item['menu_name']) {
-    menu_cache_clear($existing_item['menu_name']);
-  }
-
-  _menu_clear_page_cache();
-  return $item['mlid'];
 }
 
 /**
