Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.341
diff -u -9 -p -r1.341 menu.inc
--- includes/menu.inc	24 Aug 2009 01:49:41 -0000	1.341
+++ includes/menu.inc	24 Aug 2009 17:22:02 -0000
@@ -1999,29 +1999,37 @@ function _menu_link_build($item) {
   return $item;
 }
 
 /**
  * Helper function to build menu links for the items in the menu router.
  */
 function _menu_navigation_links_rebuild($menu) {
   // Add normal and suggested items as links.
   $menu_links = array();
+  $menu_links_again = array();
   foreach ($menu as $path => $item) {
     if ($item['_visible']) {
-      $menu_links[$path] = $item;
+      $menu_links[] = $item;
       $sort[$path] = $item['_number_parts'];
+      // An item that explicitly refer to its parent must be processed after
+      // the parent, but it must also be processed before its own children, so
+      // process it in the normal order as determined by $item['_number_parts']
+      // and then again when all other items has been processed.
+      if (isset($item['parent path'])) {
+        $menu_links_again[] = $item;
+      }
     }
   }
   if ($menu_links) {
     // Make sure no child comes before its parent.
     array_multisort($sort, SORT_NUMERIC, $menu_links);
 
-    foreach ($menu_links as $item) {
+    foreach (array_merge($menu_links, $menu_links_again) as $item) {
       $existing_item = db_select('menu_links')
         ->fields('menu_links', array(
           'mlid',
           'menu_name',
           'plid',
           'customized',
           'has_children',
           'updated',
         ))
@@ -2184,19 +2192,22 @@ function menu_link_save(&$item) {
     'updated' => 0,
   );
   $existing_item = FALSE;
   if (isset($item['mlid'])) {
     if ($existing_item = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['mlid']))->fetchAssoc()) {
       $existing_item['options'] = unserialize($existing_item['options']);
     }
   }
 
-  if (isset($item['plid'])) {
+  if (isset($item['parent path'])) {
+    $parent = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $item['parent path']))->fetchAssoc();
+  }
+  elseif (isset($item['plid'])) {
     if ($item['plid']) {
       $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['plid']))->fetchAssoc();
     }
     else {
       // Don't bother with the query - mlid can never equal zero..
       $parent = FALSE;
     }
   }
   else {
@@ -2222,25 +2233,23 @@ function menu_link_save(&$item) {
       if ($new_query->countQuery()->execute()->fetchField() == 1) {
         $parent = $new_query->fields('menu_links')->execute()->fetchAssoc();
       }
     } while ($parent === FALSE && $parent_path);
   }
   if ($parent !== FALSE) {
     $item['menu_name'] = $parent['menu_name'];
   }
   $menu_name = $item['menu_name'];
-  // Menu callbacks need to be in the links table for breadcrumbs, but can't
-  // be parents if they are generated directly from a router item.
-  if (empty($parent['mlid']) || $parent['hidden'] < 0) {
-    $item['plid'] =  0;
+  if ($parent) {
+    $item['plid'] = $parent['mlid'];
   }
   else {
-    $item['plid'] = $parent['mlid'];
+    $item['plid'] =  0;
   }
 
   if (!$existing_item) {
     $item['mlid'] = db_insert('menu_links')
       ->fields(array(
         'menu_name' => $item['menu_name'],
         'plid' => $item['plid'],
         'link_path' => $item['link_path'],
         'hidden' => $item['hidden'],
@@ -2663,18 +2672,24 @@ function _menu_router_build($callbacks) 
   array_multisort($sort, SORT_NUMERIC, $menu);
   // Apply inheritance rules.
   foreach ($menu as $path => $v) {
     $item = &$menu[$path];
     if (!$item['_tab']) {
       // Non-tab items.
       $item['tab_parent'] = '';
       $item['tab_root'] = $path;
     }
+    // If a menu_name is specified, the item becomes a top-level item in that
+    // menu, and the relation to the item that would otherwise be this item's
+    // parent is ignored.
+    if (isset($item['menu_name'])) {
+      $item['parent 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;
Index: modules/menu/menu.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.api.php,v
retrieving revision 1.9
diff -u -9 -p -r1.9 menu.api.php
--- modules/menu/menu.api.php	11 Jul 2009 13:56:21 -0000	1.9
+++ modules/menu/menu.api.php	24 Aug 2009 17:22:02 -0000
@@ -29,19 +29,19 @@
  *   - "title callback": Function to generate the title, defaults to t().
  *     If you require only the raw string to be output, set this to FALSE.
  *   - "title arguments": Arguments to send to t() or your custom callback.
  *   - "description": The untranslated description of the menu item.
  *   - "page callback": The function to call to display a web page when the user
  *     visits the path. If omitted, the parent menu item's callback will be used
  *     instead.
  *   - "page arguments": An array of arguments to pass to the page callback
  *     function. Integer values pass the corresponding URL component (see arg()).
- *   - "access callback": A  function returning a boolean value that determines
+ *   - "access callback": A function returning a boolean value that determines
  *     whether the user has access rights to this menu item. Defaults to
  *     user_access() unless a value is inherited from a parent menu item.
  *   - "access arguments": An array of arguments to pass to the access callback
  *     function. Integer values pass the corresponding URL component.
  *   - "file": A file that will be included before the callbacks are accessed;
  *     this allows callback functions to be in separate files. The file should
  *     be relative to the implementing module's directory unless otherwise
  *     specified by the "file path" option.
  *   - "file path": The path to the folder containing the file specified in
@@ -53,19 +53,24 @@
  *     as with other arguments, integers are automatically cast to URL
  *     arguments. There are also two "magic" values: "%index" will correspond
  *     to the URL index where the object's load function is specified; "%map"
  *     will correspond to the full menu map, passed in by reference to the
  *     load function.
  *   - "weight": An integer that determines relative position of items in the
  *     menu; higher-weighted items sink. Defaults to 0. When in doubt, leave
  *     this alone; the default alphabetical order is usually best.
  *   - "menu_name": Optional. Set this to a custom menu if you don't want your
- *     item to be placed in Navigation.
+ *     item to be placed in Navigation. The menu item becomes a top-level item
+ *     in the specified menu.
+ *   - "parent path": Optional. The path of the menu item's parent. This
+ *     overrides the default parent and allows a menu item to use a path that
+ *     begins with a different string than the parent path. The menu item
+ *     does not inherit page or access callbacks from the specified parent.
  *   - "type": A bitmask of flags describing properties of the menu item.
  *     Many shortcut bitmasks are provided as constants in menu.inc:
  *     - MENU_NORMAL_ITEM: Normal menu items show up in the menu tree and can be
  *       moved/hidden by the administrator.
  *     - MENU_CALLBACK: Callbacks simply register a path so that the correct
  *       function is fired when the URL is accessed.
  *     - MENU_SUGGESTED_ITEM: Modules may "suggest" menu items that the
  *       administrator may enable.
  *     - MENU_LOCAL_TASK: Local tasks are rendered as tabs by default.
Index: modules/menu/menu.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v
retrieving revision 1.200
diff -u -9 -p -r1.200 menu.module
--- modules/menu/menu.module	24 Aug 2009 00:14:21 -0000	1.200
+++ modules/menu/menu.module	24 Aug 2009 17:22:02 -0000
@@ -259,19 +259,20 @@ function _menu_parents_recurse($tree, $m
       }
     }
   }
 }
 
 /**
  * Reset a system-defined menu item.
  */
 function menu_reset_item($item) {
-  $new_item = _menu_link_build(menu_get_item($item['router_path']));
+  $menu = menu_get_router();
+  $new_item = _menu_link_build($menu[$item['path']]);
   foreach (array('mlid', 'has_children') as $key) {
     $new_item[$key] = $item[$key];
   }
   menu_link_save($new_item);
   return $new_item;
 }
 
 /**
  * Implement hook_block_list().
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1110
diff -u -9 -p -r1.1110 node.module
--- modules/node/node.module	24 Aug 2009 00:14:21 -0000	1.1110
+++ modules/node/node.module	24 Aug 2009 17:22:03 -0000
@@ -1766,24 +1766,28 @@ function node_menu() {
       'title' => $type->name,
       'title callback' => 'check_plain',
       'page callback' => 'node_add',
       'page arguments' => array(2),
       'access callback' => 'node_access',
       'access arguments' => array('create', $type->type),
       'description' => $type->description,
       'file' => 'node.pages.inc',
     );
+    // Do not use the admin/structure/types prefix in order to avoid conflicts
+    // e.g. with admin/structure/types/add for a node type whose machine name
+    // is "add".
     $items['admin/structure/node-type/' . $type_url_str] = array(
       'title' => $type->name,
       'page callback' => 'drupal_get_form',
       'page arguments' => array('node_type_form', $type),
       'access arguments' => array('administer content types'),
       'type' => MENU_CALLBACK,
+      'parent path' => 'admin/structure/types',
       'file' => 'content_types.inc',
     );
     $items['admin/structure/node-type/' . $type_url_str . '/edit'] = array(
       'title' => 'Edit',
       'type' => MENU_DEFAULT_LOCAL_TASK,
     );
     $items['admin/structure/node-type/' . $type_url_str . '/delete'] = array(
       'title' => 'Delete',
       'page arguments' => array('node_type_delete_confirm', $type),
Index: modules/simpletest/tests/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu.test,v
retrieving revision 1.13
diff -u -9 -p -r1.13 menu.test
--- modules/simpletest/tests/menu.test	14 Jul 2009 20:53:16 -0000	1.13
+++ modules/simpletest/tests/menu.test	24 Aug 2009 17:22:03 -0000
@@ -97,21 +97,25 @@ class MenuIncTestCase extends DrupalWebT
   }
 
   /**
    * Tests for menu hiearchy.
    */
   function testMenuHiearchy() {
     $parent_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent'))->fetchAssoc();
     $child_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent/child'))->fetchAssoc();
     $unattached_child_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent/child2/child'))->fetchAssoc();
+    $stepchild_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent2/stepchild'))->fetchAssoc();
+    $stepchild_child_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent2/stepchild/child'))->fetchAssoc();
 
     $this->assertEqual($child_link['plid'], $parent_link['mlid'], t('The parent of a directly attached child is correct.'));
     $this->assertEqual($unattached_child_link['plid'], $parent_link['mlid'], t('The parent of a non-directly attached child is correct.'));
+    $this->assertEqual($stepchild_link['plid'], $parent_link['mlid'], t('The parent of a stepchild is correct.'));
+    $this->assertEqual($stepchild_child_link['plid'], $stepchild_link['mlid'], t("The parent of a stepchild's child is correct."));
   }
 
   /**
    * Test menu_set_item().
    */
   function testMenuSetItem() {
     $item = menu_get_item('node');
 
     $this->assertEqual($item['path'], 'node', t("Path from menu_get_item('node') is equal to 'node'"), 'menu');
Index: modules/simpletest/tests/menu_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu_test.module,v
retrieving revision 1.6
diff -u -9 -p -r1.6 menu_test.module
--- modules/simpletest/tests/menu_test.module	14 Jul 2009 20:53:16 -0000	1.6
+++ modules/simpletest/tests/menu_test.module	24 Aug 2009 17:22:03 -0000
@@ -38,18 +38,27 @@ function menu_test_menu() {
   );
   $items['menu-test/hierarchy/parent/child'] = array(
     'title' => 'Child menu router',
     'page callback' => 'node_page_default',
   );
   $items['menu-test/hierarchy/parent/child2/child'] = array(
     'title' => 'Unattached subchild router',
     'page callback' => 'node_page_default',
   );
+  $items['menu-test/hierarchy/parent2/stepchild'] = array(
+    'title' => 'Stepchild menu router',
+    'page callback' => 'node_page_default',
+    'parent path' => 'menu-test/hierarchy/parent',
+  );
+  $items['menu-test/hierarchy/parent2/stepchild/child'] = array(
+    'title' => 'Child of stepchild menu router',
+    'page callback' => 'node_page_default',
+  );
   return $items;
 }
 
 /**
  * Dummy callback for hook_menu() to point to.
  *
  * @return
  *  A random string.
  */
