Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.166
diff -u -p -r1.166 menu.inc
--- includes/menu.inc	16 May 2007 13:45:16 -0000	1.166
+++ includes/menu.inc	19 May 2007 02:43:06 -0000
@@ -149,11 +149,6 @@ define('MENU_SITE_OFFLINE', 4);
  * @} End of "Menu status codes".
  */
 
-
-/**
- * @} End of "Menu operations."
- */
-
 /**
  * @Name Menu tree parameters
  * @{
@@ -898,6 +893,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;
 
@@ -1004,32 +1018,11 @@ function menu_get_active_title() {
 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))) {
     _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
@@ -1074,15 +1067,17 @@ function _menu_navigation_links_rebuild(
     if ($item['type'] == MENU_CALLBACK || $item['type'] == MENU_SUGGESTED_ITEM) {
       $item['hidden'] = $item['type'];
     }
+    // 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',
       'hidden' => 0,
     );
     // 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 href = '%s'", $item['menu_name'], $item['href']))) {
       $menu_links[$path] = $item;
       $sort[$path] = $item['_number_parts'];
     }
@@ -1106,7 +1101,7 @@ function _menu_navigation_links_rebuild(
  * @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
+ *     menu_name   default is navigation
  *     weight      default is 0
  *     expanded    whether the item is expanded.
  *     options     An array of options, @see l for more.
@@ -1130,7 +1125,7 @@ function menu_link_save(&$item, $_menu =
   $item['_external'] = menu_path_is_external($item['href']);
   // Load defaults.
   $item += array(
-    'menu name' => 'navigation',
+    'menu_name' => 'navigation',
     'weight' => 0,
     'link_title' => '',
     'hidden' => 0,
@@ -1138,26 +1133,26 @@ function menu_link_save(&$item, $_menu =
     'expanded' => 0,
     'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
   );
-  $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 href = '%s'", $menu_name, $item['href']));
   }
 
-  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.
   if (isset($item['plid'])) {
     $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
   }
-  else { //
+  else {
     $parent_path = $item['href'];
     do {
       $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
@@ -1180,18 +1175,21 @@ 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_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) {
@@ -1213,13 +1211,13 @@ function menu_link_save(&$item, $_menu =
       }
     }
   }
-  if (!empty($existing_item)) {
+  if ($existing_item) {
     db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, href = '%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['href'],
       $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'],
@@ -1237,18 +1235,19 @@ 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['href'],
       $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['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']);
+  }
+  // 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']);
   }
 
-  // Keep track of which menus have expanded items
+  // 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)) {
@@ -1258,13 +1257,94 @@ 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;
+  while ($i <= MENU_MAX_DEPTH && $existing_item[$p]) {
+    $p = 'p'. $i++;
+    $match .= " AND $p = %d";
+    $args[] = $existing_item[$p];
+  }
+  
+  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) {
