Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.380
diff -u -9 -p -r1.380 menu.inc
--- includes/menu.inc	17 Feb 2010 22:44:51 -0000	1.380
+++ includes/menu.inc	21 Feb 2010 14:25:47 -0000
@@ -2924,53 +2924,42 @@ function menu_link_children_relative_dep
   return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0;
 }
 
 /**
  * Update the children of a menu link that's being moved.
  *
  * The menu name, parents (p1 - p6), and depth are updated for all children of
  * the link, and the has_children status of the previous parent is updated.
  */
-function _menu_link_move_children($item, $existing_item) {
-  $query = db_update('menu_links');
+function _menu_link_move_children($item, $existing_item = NULL) {
+  $child_mlids = db_query('SELECT mlid FROM {menu_links} WHERE plid = :plid', array(':plid' => $item['mlid']))->fetchCol();
+  foreach ($child_mlids as $mlid) {
+    $query = db_update('menu_links');
+    $child_item = array('mlid' => $mlid, 'depth' => $item['depth'] + 1);
+    _menu_link_parents_set($child_item, $item);
 
-  $query->fields(array('menu_name' => $item['menu_name']));
+    $query->expression('depth', $child_item['depth']);
+    for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
+      $query->expression('p' . $i, $child_item['p' . $i]);
+    }
+    $query->condition('mlid', $mlid);
 
-  $p = 'p1';
-  for ($i = 1; $i <= $item['depth']; $p = 'p' . ++$i) {
-    $query->fields(array($p => $item[$p]));
-  }
-  $j = $existing_item['depth'] + 1;
-  while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
-    $query->expression('p' . $i++, 'p' . $j++);
-  }
-  while ($i <= MENU_MAX_DEPTH) {
-    $query->fields(array('p' . $i++ => 0));
-  }
+    $query->execute();
 
-  $shift = $item['depth'] - $existing_item['depth'];
-  if ($shift < 0) {
-    $query->expression('depth', 'depth - :depth', array(':depth' => -$shift));
-  }
-  elseif ($shift > 0) {
-    $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
+    if (!empty($item['has_children'])) {
+      _menu_link_move_children($child_item);
+    }
   }
 
-  $query->condition('menu_name', $existing_item['menu_name']);
-  $p = 'p1';
-  for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p' . ++$i) {
-    $query->condition($p, $existing_item[$p]);
+  if ($existing_item) {
+    // Check the has_children status of the parent, while excluding this item.
+    _menu_update_parental_status($existing_item, TRUE);
   }
-
-  $query->execute();
-
-  // Check the has_children status of the parent, while excluding this item.
-  _menu_update_parental_status($existing_item, TRUE);
 }
 
 /**
  * Check and update the has_children status for the parent of a link.
  */
 function _menu_update_parental_status($item, $exclude = FALSE) {
   // If plid == 0, there is nothing to update.
   if ($item['plid']) {
     // Check if at least one visible child exists in the table.
Index: modules/menu/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.test,v
retrieving revision 1.34
diff -u -9 -p -r1.34 menu.test
--- modules/menu/menu.test	17 Feb 2010 05:46:16 -0000	1.34
+++ modules/menu/menu.test	21 Feb 2010 14:25:47 -0000
@@ -186,47 +186,68 @@ class MenuTestCase extends DrupalWebTest
 
   /**
    * Test menu functionality using navigation menu.
    *
    */
   function doMenuTests($menu_name = 'navigation') {
     // Add nodes to use as links for menu links.
     $node1 = $this->drupalCreateNode(array('type' => 'article'));
     $node2 = $this->drupalCreateNode(array('type' => 'article'));
+    $node3 = $this->drupalCreateNode(array('type' => 'article'));
+    $node4 = $this->drupalCreateNode(array('type' => 'article'));
+    $node5 = $this->drupalCreateNode(array('type' => 'article'));
 
     // Add menu links.
     $item1 = $this->addMenuLink(0, 'node/' . $node1->nid, $menu_name);
     $item2 = $this->addMenuLink($item1['mlid'], 'node/' . $node2->nid, $menu_name);
+    $item3 = $this->addMenuLink($item2['mlid'], 'node/' . $node3->nid, $menu_name);
+    $this->assertMenuLink($item1['mlid'], array('depth' => 1, 'has_children' => 1, 'p1' => $item1['mlid'], 'p2' => 0));
+    $this->assertMenuLink($item2['mlid'], array('depth' => 2, 'has_children' => 1, 'p1' => $item1['mlid'], 'p2' => $item2['mlid'], 'p3' => 0));
+    $this->assertMenuLink($item3['mlid'], array('depth' => 3, 'has_children' => 0, 'p1' => $item1['mlid'], 'p2' => $item2['mlid'], 'p3' => $item3['mlid'], 'p4' => 0));
 
     // Verify menu links.
     $this->verifyMenuLink($item1, $node1);
     $this->verifyMenuLink($item2, $node2, $item1, $node1);
+    $this->verifyMenuLink($item3, $node3, $item2, $node2);
+
+    // Add more menu links.
+    $item4 = $this->addMenuLink(0, 'node/' . $node4->nid, $menu_name);
+    $item5 = $this->addMenuLink($item4['mlid'], 'node/' . $node5->nid, $menu_name);
+    $this->assertMenuLink($item4['mlid'], array('depth' => 1, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => 0));
+    $this->assertMenuLink($item5['mlid'], array('depth' => 2, 'has_children' => 0, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => 0));
 
     // Modify menu links.
     $this->modifyMenuLink($item1);
     $this->modifyMenuLink($item2);
 
     // Toggle menu links.
     $this->toggleMenuLink($item1);
     $this->toggleMenuLink($item2);
 
+    // Move link and verify that descendants are updated.
+    $this->moveMenuLink($item2, $item5['mlid'], $menu_name);
+    $this->assertMenuLink($item1['mlid'], array('depth' => 1, 'has_children' => 0, 'p1' => $item1['mlid'], 'p2' => 0));
+    $this->assertMenuLink($item4['mlid'], array('depth' => 1, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => 0));
+    $this->assertMenuLink($item5['mlid'], array('depth' => 2, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => 0));
+    $this->assertMenuLink($item2['mlid'], array('depth' => 3, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => $item2['mlid'], 'p4' => 0));
+    $this->assertMenuLink($item3['mlid'], array('depth' => 4, 'has_children' => 0, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => $item2['mlid'], 'p4' => $item3['mlid'], 'p5' => 0));
+
     // Enable a link via the overview form.
     $this->disableMenuLink($item1);
     $edit = array();
 
     // Note in the UI the 'mlid:x[hidden]' form element maps to enabled, or
     // NOT hidden.
     $edit['mlid:' . $item1['mlid'] . '[hidden]'] = TRUE;
     $this->drupalPost('admin/structure/menu/manage/' . $item1['menu_name'], $edit, t('Save configuration'));
 
     // Verify in the database.
-    $hidden = db_query("SELECT hidden FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item1['mlid']))->fetchField();
-    $this->assertEqual($hidden, 0, t('Link is not hidden in the database table when enabled via the overview form'));
+    $this->assertMenuLink($item1['mlid'], array('hidden' => 0));
 
     // Save menu links for later tests.
     $this->items[] = $item1;
     $this->items[] = $item2;
   }
 
   /**
    * Add and remove a menu link with a query string and fragment.
    */
@@ -271,44 +292,21 @@ class MenuTestCase extends DrupalWebTest
       'weight' => '0',
     );
 
     // Add menu link.
     $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertResponse(200);
     // Unlike most other modules, there is no confirmation message displayed.
     $this->assertText($title, 'Menu link was added');
 
-    // Retrieve menu link.
-    $item = db_query("SELECT * FROM {menu_links} WHERE link_title = :title", array(':title' => $title))->fetchAssoc();
-
-    // Check the structure in the DB of the two menu links.
-    // In general, if $n = $item['depth'] then $item['p'. $n] == $item['mlid'] and $item['p' . ($n - 1)] == $item['plid'] (unless depth == 0).
-    // All $item['p' . $n] for $n > depth must be 0.
-    // We know link1 is at the top level, so $item1['deptj'] == 1 and $item1['plid'] == 0.
-    // We know that the parent of link2 is link1, so $item2['plid'] == $item1['mlid'].
-    // Both menu links were created in the navigation menu.
-    $this->assertEqual($item['menu_name'], $menu_name);
-    $this->assertEqual($item['plid'], $plid);
-    $options = unserialize($item['options']);
-    if (!empty($options['query'])) {
-      $item['link_path'] .= '?' . drupal_http_build_query($options['query']);
-    }
-    if (!empty($options['fragment'])) {
-      $item['link_path'] .= '#' . $options['fragment'];
-    }
-    $this->assertEqual($item['link_path'], $link);
-    $this->assertEqual($item['link_title'], $title);
-    if ($plid == 0) {
-      $this->assertTrue($item['depth'] == 1 && !$item['has_children'] && $item['p1'] == $item['mlid'] && $item['p2'] == 0, 'Menu link has correct data');
-    }
-    else {
-      $this->assertTrue($item['depth'] == 2 && !$item['has_children'] && $item['p1'] == $plid && $item['p2'] == $item['mlid'], 'Menu link has correct data');
-    }
+    $item = db_query('SELECT * FROM {menu_links} WHERE link_title = :title', array(':title' => $title))->fetchAssoc();
+    $this->assertTrue(t('Menu link was found in database.'));
+    $this->assertMenuLink($item['mlid'], array('menu_name' => $menu_name, 'link_path' => $link, 'has_children' => 0, 'plid' => $plid));
 
     return $item;
   }
 
   /**
    * Attempt to add menu link with invalid path or no access permission.
    *
    * @param string $menu_name Menu name.
    */
@@ -353,18 +351,31 @@ class MenuTestCase extends DrupalWebTest
     $this->assertText($title, 'Menu link was displayed');
 
     // Verify menu link link.
     $this->clickLink($title);
     $title = $item_node->title;
     $this->assertTitle(t("@title | Drupal", array('@title' => $title)), t('Menu link link target was correct'));
   }
 
   /**
+   * Change the parent of a menu link using the menu module UI.
+   */
+  function moveMenuLink($item, $plid, $menu_name) {
+    $mlid = $item['mlid'];
+
+    $edit = array(
+      'parent' => $menu_name . ':' . $plid,
+    );
+    $this->drupalPost("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
+    $this->assertResponse(200);
+  }
+
+  /**
    * Modify a menu link using the menu module UI.
    *
    * @param array &$item Menu link passed by reference.
    */
   function modifyMenuLink(&$item) {
     $item['link_title'] = $this->randomName(16);
 
     $mlid = $item['mlid'];
     $title = $item['link_title'];
@@ -447,36 +458,58 @@ class MenuTestCase extends DrupalWebTest
    *   Menu link.
    */
   function disableMenuLink($item) {
     $mlid = $item['mlid'];
     $edit['enabled'] = FALSE;
     $this->drupalPost("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
 
     // Unlike most other modules, there is no confirmation message displayed.
     // Verify in the database.
-    $hidden = db_query("SELECT hidden FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchField();
-    $this->assertEqual($hidden, 1, t('Link is hidden in the database table'));
+    $this->assertMenuLink($mlid, array('hidden' => 1));
   }
 
   /**
    * Enable a menu link.
    *
    * @param $item
    *   Menu link.
    */
   function enableMenuLink($item) {
     $mlid = $item['mlid'];
     $edit['enabled'] = TRUE;
     $this->drupalPost("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
 
     // Verify in the database.
-    $hidden = db_query("SELECT hidden FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchField();
-    $this->assertEqual($hidden, 0, t('Link is not hidden in the database table'));
+    $this->assertMenuLink($mlid, array('hidden' => 0));
+  }
+
+  /**
+   * Fetch the menu item from the database and compare it to the specified
+   * array.
+   *
+   * @param $mlid
+   *   Menu item id.
+   * @param $item
+   *   Array containing properties to verify.
+   */
+  function assertMenuLink($mlid, array $expected_item) {
+    // Retrieve menu link.
+    $item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array(':mlid' => $mlid))->fetchAssoc();
+    $options = unserialize($item['options']);
+    if (!empty($options['query'])) {
+      $item['link_path'] .= '?' . drupal_http_build_query($options['query']);
+    }
+    if (!empty($options['fragment'])) {
+      $item['link_path'] .= '#' . $options['fragment'];
+    }
+    foreach ($expected_item as $key => $value) {
+      $this->assertEqual($item[$key], $value, t('Parameter %key had expected value.', array('%key' => $key)));
+    }
   }
 
   /**
    * Get standard menu link.
    */
   private function getStandardMenuLink() {
     // Retrieve menu link id of the Log out menu link, which will always be on the front page.
     $mlid = db_query("SELECT mlid FROM {menu_links} WHERE module = 'system' AND router_path = 'user/logout'")->fetchField();
     $this->assertTrue($mlid > 0, 'Standard menu link id was found');
