Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.341 diff -u -p -r1.341 menu.inc --- includes/menu.inc 24 Aug 2009 01:49:41 -0000 1.341 +++ includes/menu.inc 9 Sep 2009 15:29:59 -0000 @@ -1225,28 +1225,29 @@ function menu_tree_data(array $links, ar * the next menu link. */ function _menu_tree_data(&$links, $parents, $depth) { - $done = FALSE; $tree = array(); - while (!$done && $item = array_pop($links)) { + while ($item = array_pop($links)) { // We need to determine if we're on the path to root so we can later build // the correct active trail and breadcrumb. $item['in_active_trail'] = in_array($item['mlid'], $parents); - // Look ahead to the next link, but leave it on the array so it's available - // to other recursive function calls if we return or build a sub-tree. - $next = end($links); // Add the current link to the tree. $tree[$item['mlid']] = array( 'link' => $item, 'below' => array(), ); + // Look ahead to the next link, but leave it on the array so it's available + // to other recursive function calls if we return or build a sub-tree. + $next = end($links); // Check whether the next link is the first in a new sub-tree. if ($next && $next['depth'] > $depth) { // Recursively call _menu_tree_data to build the sub-tree. $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']); + // Fetch next link after filling the sub-tree. + $next = end($links); } - else { - // Determine if we should exit the loop and return. - $done = (!$next || $next['depth'] < $depth); + // Determine if we should exit the loop and return. + if (!$next || $next['depth'] < $depth) { + break; } } return $tree; @@ -2036,8 +2037,9 @@ function _menu_navigation_links_rebuild( $item['plid'] = $existing_item['plid']; } else { - // If it moved, put it at the top level in the new menu. - $item['plid'] = 0; + // It moved to a new menu, so let menu_link_save() try to find a + // valid parent based on path. + unset($item['plid']); } $item['has_children'] = $existing_item['has_children']; $item['updated'] = $existing_item['updated']; @@ -2164,7 +2166,6 @@ function _menu_delete_item($item, $force * saved. */ function menu_link_save(&$item) { - drupal_alter('menu_link', $item); // This is the easiest way to handle the unique internal path '', @@ -2190,14 +2191,11 @@ function menu_link_save(&$item) { } } - if (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; - } + // The re-parenting process always needs to be invoked, since there is no + // guarantee that {menu_links} does not contain stale data caused by a + // re-parenting process that went wrong in a previous rebuild. + if (!empty($item['plid'])) { + $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['plid']))->fetchAssoc(); } else { $query = db_select('menu_links'); @@ -2206,10 +2204,9 @@ function menu_link_save(&$item) { if ($item['module'] == 'system') { $query->condition('module', 'system'); } - else { - // If not derived from a router item, we respect the specified menu name. - $query->condition('menu_name', $item['menu_name']); - } + // Always respect the link's menu name. Inheritance for router items is + // ensured in _menu_router_build(). + $query->condition('menu_name', $item['menu_name']); // Find the parent - it must be unique. $parent_path = $item['link_path']; @@ -2224,6 +2221,7 @@ function menu_link_save(&$item) { } } while ($parent === FALSE && $parent_path); } + // If a parent link was found, derive its menu. if ($parent !== FALSE) { $item['menu_name'] = $parent['menu_name']; } @@ -2675,6 +2673,15 @@ function _menu_router_build($callbacks) $parent = $menu[$parent_path]; + // Try to inherit the menu name from parent router items. + if (!isset($item['menu_name'])) { + if (!isset($parent['menu_name'])) { + $parent['menu_name'] = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path", array(':router_path' => $parent_path))->fetchColumn(); + } + if (!empty($parent['menu_name'])) { + $item['menu_name'] = $parent['menu_name']; + } + } if (!isset($item['tab_parent'])) { // Parent stores the parent of the path. $item['tab_parent'] = $parent_path; Index: modules/simpletest/tests/menu.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu.test,v retrieving revision 1.13 diff -u -p -r1.13 menu.test --- modules/simpletest/tests/menu.test 14 Jul 2009 20:53:16 -0000 1.13 +++ modules/simpletest/tests/menu.test 9 Sep 2009 15:31:15 -0000 @@ -164,3 +164,59 @@ class MenuRebuildTestCase extends Drupal } } + +/** + * Menu tree data related tests. + */ +class MenuTreeDataTestCase extends DrupalUnitTestCase { + /** + * Dummy link structure acceptable for menu_tree_data(). + */ + var $links = array( + 1 => array('mlid' => 1, 'depth' => 1), + 2 => array('mlid' => 2, 'depth' => 1), + 3 => array('mlid' => 3, 'depth' => 2), + 4 => array('mlid' => 4, 'depth' => 3), + 5 => array('mlid' => 5, 'depth' => 1), + ); + + public static function getInfo() { + return array( + 'name' => 'Menu tree generation', + 'description' => 'Tests recursive menu tree generation functions.', + 'group' => 'Menu', + ); + } + + /** + * Validate the generation of a proper menu tree hierarchy. + */ + function testMenuTreeData() { + $tree = menu_tree_data($this->links); + + // Validate that parent items #1, #2, and #5 exist on the root level. + $this->assertSameLink($this->links[1], $tree[1]['link'], t('Parent item #1 exists.')); + $this->assertSameLink($this->links[2], $tree[2]['link'], t('Parent item #2 exists.')); + $this->assertSameLink($this->links[5], $tree[5]['link'], t('Parent item #5 exists.')); + + // Validate that child item #4 exists at the correct location in the hierarchy. + $this->assertSameLink($this->links[4], $tree[2]['below'][3]['below'][4]['link'], t('Child item #4 exists in the hierarchy.')); + } + + /** + * Check that two menu links are the same by comparing the mlid. + * + * @param $link1 + * A menu link item. + * @param $link2 + * A menu link item. + * @param $message + * The message to display along with the assertion. + * @return + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertSameLink($link1, $link2, $message = '') { + return $this->assert($link1['mlid'] == $link2['mlid'], $message ? $message : t('First link is identical to second link')); + } +} +