Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1090 diff -u -p -r1.1090 common.inc --- includes/common.inc 14 Jan 2010 18:45:17 -0000 1.1090 +++ includes/common.inc 14 Jan 2010 19:03:17 -0000 @@ -245,7 +245,7 @@ function drupal_get_profile() { function drupal_set_breadcrumb($breadcrumb = NULL) { $stored_breadcrumb = &drupal_static(__FUNCTION__); - if (!is_null($breadcrumb)) { + if (isset($breadcrumb)) { $stored_breadcrumb = $breadcrumb; } return $stored_breadcrumb; @@ -257,7 +257,7 @@ function drupal_set_breadcrumb($breadcru function drupal_get_breadcrumb() { $breadcrumb = drupal_set_breadcrumb(); - if (is_null($breadcrumb)) { + if (!isset($breadcrumb)) { $breadcrumb = menu_get_active_breadcrumb(); } Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.375 diff -u -p -r1.375 menu.inc --- includes/menu.inc 14 Jan 2010 13:45:33 -0000 1.375 +++ includes/menu.inc 14 Jan 2010 19:03:17 -0000 @@ -780,11 +780,9 @@ function _menu_link_translate(&$item) { $item['localized_options'] = $item['options']; } else { - $map = explode('/', $item['link_path']); - if (!empty($item['to_arg_functions'])) { - _menu_link_map_translate($map, $item['to_arg_functions']); - } - $item['href'] = implode('/', $map); + // Complete the path of the menu link with elements from the current path. + $item['href'] = _menu_fill_wildcards($item['link_path'], arg()); + $map = explode('/', $item['href']); // Note - skip callbacks without real values for their arguments. if (strpos($item['href'], '%') !== FALSE) { @@ -1098,49 +1096,27 @@ function menu_tree_page_data($menu_name, if (!isset($data)) { // Build and run the query, and build the tree. if ($item['access']) { - // Check whether a menu link exists that corresponds to the current path. - $args[] = $item['href']; - if (drupal_is_front_page()) { - $args[] = ''; - } - $parents = db_select('menu_links') - ->fields('menu_links', array( - 'p1', - 'p2', - 'p3', - 'p4', - 'p5', - 'p6', - 'p7', - 'p8', - )) - ->condition('menu_name', $menu_name) - ->condition('link_path', $args, 'IN') - ->execute()->fetchAssoc(); - - if (empty($parents)) { - // If no link exists, we may be on a local task that's not in the links. - // TODO: Handle the case like a local task on a specific node in the menu. - $parents = db_select('menu_links') - ->fields('menu_links', array( - 'p1', - 'p2', - 'p3', - 'p4', - 'p5', - 'p6', - 'p7', - 'p8', - )) - ->condition('menu_name', $menu_name) - ->condition('link_path', $item['tab_root']) - ->execute()->fetchAssoc(); + // Parent mlids. + $parents = array(); + + // Find a menu link corresponding to the current path. + $top_link = menu_link_get_prefered(); + + if ($top_link) { + // Use all the coordinates, except the last one because there + // can be no child beyond the last column. + for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { + if ($top_link['p' . $i]) { + $parents[] = $top_link['p' . $i]; + } + } } + // We always want all the top-level links with plid == 0. $parents[] = '0'; // Use array_values() so that the indices are numeric for array_merge(). - $args = $parents = array_unique(array_values($parents)); + $args = $parents = array_values(array_unique($parents)); $expanded = variable_get('menu_expanded', array()); // Check whether the current menu has any links set to be expanded. if (in_array($menu_name, $expanded)) { @@ -2044,52 +2020,25 @@ function menu_set_active_trail($new_trai elseif (!isset($trail)) { $trail = array(); $trail[] = array('title' => t('Home'), 'href' => '', 'localized_options' => array(), 'type' => 0); - $item = menu_get_item(); - // Check whether the current item is a local task (displayed as a tab). - if ($item['tab_parent']) { - // The title of a local task is used for the tab, never the page title. - // Thus, replace it with the item corresponding to the root path to get - // the relevant href and title. For example, the menu item corresponding - // to 'admin' is used when on the 'By module' tab at 'admin/by-module'. - $parts = explode('/', $item['tab_root']); - $args = arg(); - // Replace wildcards in the root path using the current path. - foreach ($parts as $index => $part) { - if ($part == '%') { - $parts[$index] = $args[$index]; - } - } - // Retrieve the menu item using the root path after wildcard replacement. - $root_item = menu_get_item(implode('/', $parts)); - if ($root_item && $root_item['access']) { - $item = $root_item; - } - } - $menu_names = menu_get_active_menu_names(); - $curr = FALSE; - // Determine if the current page is a link in any of the active menus. - if ($menu_names) { - $query = db_select('menu_links', 'ml'); - $query->fields('ml', array('menu_name')); - $query->condition('ml.link_path', $item['href']); - $query->condition('ml.menu_name', $menu_names, 'IN'); - $result = $query->execute(); - $found = array(); - foreach ($result as $menu) { - $found[] = $menu->menu_name; - } - // The $menu_names array is ordered, so take the first one that matches. - $name = current(array_intersect($menu_names, $found)); - if ($name !== FALSE) { - $tree = menu_tree_page_data($name); + $prefered_link = menu_link_get_prefered(); + + if ($prefered_link) { + $tree = menu_tree_page_data($prefered_link['menu_name']); + if ($router = menu_get_item(_menu_fill_wildcards($prefered_link['link_path'], arg()))) { + $prefered_link += $router; list($key, $curr) = each($tree); } } + else { + $prefered_link = menu_get_item(); + $key = NULL; + $curr = NULL; + } while ($curr) { // Terminate the loop when we find the current path in the active trail. - if ($curr['link']['href'] == $item['href']) { + if ($curr['link']['href'] == $prefered_link['href']) { $trail[] = $curr['link']; $curr = FALSE; } @@ -2102,17 +2051,115 @@ function menu_set_active_trail($new_trai list($key, $curr) = each($tree); } } + // Make sure the current page is in the trail (needed for the page title), // but exclude tabs and the front page. $last = count($trail) - 1; - if ($trail[$last]['href'] != $item['href'] && !(bool)($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) { - $trail[] = $item; + if ($trail[$last]['href'] != $prefered_link['href'] && !(bool)($prefered_link['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) { + $trail[] = $prefered_link; } } return $trail; } /** + * Load the prefered menu link for a given path. + * + * @param $path + * The path, for example node/5. The function will find the corresponding + * menu link (node/5 if it exists, or fallback to node/%). + * @return + * A fully translated menu link, or NULL if not matching menu link was + * found. The most specific menu link (node/5 prefered over node/%) in the + * most prefered menu (as defined by menu_get_active_menu_names()) is returned. + */ +function menu_link_get_prefered($href = NULL, $check_access = TRUE) { + $prefered_links = &drupal_static(__FUNCTION__); + + if (!isset($href)) { + $href = $_GET['q']; + } + + if (!isset($prefered_links[$href])) { + $item = menu_get_item($href); + + // We look for the correct menu link by building a list of candidate + // paths. We pick the most relevant path in the prefered menu. + $path_candidates = array(); + // 1. The current item href. + $path_candidates[] = $item['href']; + // 2. The current item path (with wildcards). + $path_candidates[] = $item['path']; + if ($item['tab_parent']) { + // 3. The href of the current item tab root (if its exists). + $path_candidates[] = _menu_fill_wildcards($item['tab_root'], $item['original_map']); + // 4. The path of the current item tab root (if its exists). + $path_candidates[] = $item['tab_root']; + } + + // The list of prefered menu names, in preference order. + $menu_names = menu_get_active_menu_names(); + + // Build the query. + $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); + $query->addTag('translatable'); + $query->fields('ml'); + $query->condition('ml.link_path', $path_candidates, 'IN'); + $query->condition('ml.menu_name', $menu_names, 'IN'); + + // Sort candidates by link path and menu name. + $candidates = array(); + foreach ($query->execute() as $candidate) { + $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; + } + + // Pick the most specific link, in the prefered menu. + $prefered_links[$href] = NULL; + foreach ($path_candidates as $path) { + if (!isset($candidates[$path])) { + continue; + } + foreach ($menu_names as $menu_name) { + if (!isset($candidates[$path][$menu_name])) { + continue; + } + + $prefered_links[$href] = $candidates[$path][$menu_name]; + break 2; + } + } + } + + return $prefered_links[$href]; +} + +/** + * Replace a path wildcard with elements from a map. + * + * @param $path + * A menu path (ie. 'node/%/edit'). + * @param $map + * A menu map (ie. array('node', '12')). + * @return + * A completed menu path (ie. 'node/12/edit'). + */ +function _menu_fill_wildcards($path, array $map) { + $parts = arg(NULL, $path); + foreach ($parts as $index => $part) { + if (isset($map[$index])) { + if ($part == '%') { + $parts[$index] = $map[$index]; + } + else if ($part != $map[$index]) { + // This path doesn't match from this point forward, abort. + break; + } + } + } + return implode('/', $parts); +} + +/** * Gets the active trail (path to root menu root) of the current page. * * See menu_set_active_trail() for details of return value. Index: modules/field/tests/field.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/tests/field.test,v retrieving revision 1.12 diff -u -p -r1.12 field.test --- modules/field/tests/field.test 13 Jan 2010 05:08:29 -0000 1.12 +++ modules/field/tests/field.test 14 Jan 2010 19:06:39 -0000 @@ -1431,14 +1431,14 @@ class FieldFormTestCase extends FieldTes $value = mt_rand(1, 127); $edit = array("{$this->field_name}[$langcode][0][value]" => $value); $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); $entity = field_test_entity_test_load($id); $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved'); // Display edit form. - $this->drupalGet('test-entity/' . $id . '/edit'); + $this->drupalGet('test-entity/manage/' . $id . '/edit'); $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", $value, 'Widget is displayed with the correct default value'); $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); @@ -1453,7 +1453,7 @@ class FieldFormTestCase extends FieldTes // Empty the field. $value = ''; $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); + $this->drupalPost('test-entity/manage/' . $id . '/edit', $edit, t('Save')); $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); $entity = field_test_entity_test_load($id); $this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied'); @@ -1478,7 +1478,7 @@ class FieldFormTestCase extends FieldTes $value = mt_rand(1, 127); $edit = array("{$this->field_name}[$langcode][0][value]" => $value); $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); $entity = field_test_entity_test_load($id); @@ -1487,7 +1487,7 @@ class FieldFormTestCase extends FieldTes // Edit with missing required value. $value = ''; $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); + $this->drupalPost('test-entity/manage/' . $id . '/edit', $edit, t('Save')); $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation'); } @@ -1558,7 +1558,7 @@ class FieldFormTestCase extends FieldTes // Submit the form and create the entity. $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); $entity = field_test_entity_test_load($id); @@ -1650,7 +1650,7 @@ class FieldFormTestCase extends FieldTes // Create entity with three values. $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3'); $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; // Check that the values were saved. @@ -1658,7 +1658,7 @@ class FieldFormTestCase extends FieldTes $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); // Display the form, check that the values are correctly filled in. - $this->drupalGet('test-entity/' . $id . '/edit'); + $this->drupalGet('test-entity/manage/' . $id . '/edit'); $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', t('Widget is displayed.')); // Submit the form with more values than the field accepts. @@ -1705,7 +1705,7 @@ class FieldFormTestCase extends FieldTes // Create entity. $edit = array("{$field_name}[$langcode][0][value]" => 1); $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; // Check that the default value was saved. @@ -1715,7 +1715,7 @@ class FieldFormTestCase extends FieldTes // Create a new revision. $edit = array("{$field_name}[$langcode][0][value]" => 2, 'revision' => TRUE); - $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); + $this->drupalPost('test-entity/manage/' . $id . '/edit', $edit, t('Save')); // Check that the new revision has the expected values. $entity = field_test_entity_test_load($id); Index: modules/field/tests/field_test.entity.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/tests/field_test.entity.inc,v retrieving revision 1.2 diff -u -p -r1.2 field_test.entity.inc --- modules/field/tests/field_test.entity.inc 26 Dec 2009 16:50:08 -0000 1.2 +++ modules/field/tests/field_test.entity.inc 14 Jan 2010 19:06:48 -0000 @@ -296,7 +296,7 @@ function field_test_entity_form_submit($ if ($entity->ftid) { unset($form_state['rebuild']); - $form_state['redirect'] = 'test-entity/' . $entity->ftid . '/edit'; + $form_state['redirect'] = 'test-entity/manage/' . $entity->ftid . '/edit'; } else { // Error on save. Index: modules/field/tests/field_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/tests/field_test.module,v retrieving revision 1.3 diff -u -p -r1.3 field_test.module --- modules/field/tests/field_test.module 4 Dec 2009 16:49:46 -0000 1.3 +++ modules/field/tests/field_test.module 14 Jan 2010 20:01:58 -0000 @@ -39,7 +39,6 @@ function field_test_permission() { * Implements hook_menu(). */ function field_test_menu() { - $items = array(); $bundles = field_info_bundles('test_entity'); foreach ($bundles as $bundle_name => $bundle_info) { @@ -52,10 +51,10 @@ function field_test_menu() { 'type' => MENU_NORMAL_ITEM, ); } - $items['test-entity/%field_test_entity_test/edit'] = array( + $items['test-entity/manage/%field_test_entity_test/edit'] = array( 'title' => 'Edit test entity', 'page callback' => 'field_test_entity_edit', - 'page arguments' => array(1), + 'page arguments' => array(2), 'access arguments' => array('administer field_test content'), 'type' => MENU_NORMAL_ITEM, ); Index: modules/menu/menu.module =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v retrieving revision 1.227 diff -u -p -r1.227 menu.module --- modules/menu/menu.module 14 Jan 2010 13:45:33 -0000 1.227 +++ modules/menu/menu.module 14 Jan 2010 19:03:17 -0000 @@ -249,8 +249,6 @@ function menu_load_all() { * - menu_name: The unique name of the custom menu. * - title: The human readable menu title. * - description: The custom menu description. - * - old_name: For existing menus, the current 'menu_name', otherwise empty. - * Decides whether hook_menu_insert() or hook_menu_update() will be invoked. * * Modules should always pass a fully populated $menu when saving a custom * menu, so other modules are able to output proper status or watchdog messages. @@ -258,7 +256,7 @@ function menu_load_all() { * @see menu_load() */ function menu_save($menu) { - db_merge('menu_custom') + $status = db_merge('menu_custom') ->key(array('menu_name' => $menu['menu_name'])) ->fields(array( 'title' => $menu['title'], @@ -267,17 +265,14 @@ function menu_save($menu) { ->execute(); menu_cache_clear_all(); - // Since custom menus are keyed by name and their machine-name cannot be - // changed, there is no real differentiation between inserting and updating a - // menu. To overcome this, we define the existing menu_name as 'old_name' in - // menu_edit_menu(). - // @todo Replace this condition when db_merge() returns the proper query - // result (insert/update). - if (!empty($menu['old_name'])) { - module_invoke_all('menu_update', $menu); - } - else { - module_invoke_all('menu_insert', $menu); + switch ($status) { + case SAVED_NEW: + module_invoke_all('menu_insert', $menu); + break; + + case SAVED_UPDATED: + module_invoke_all('menu_update', $menu); + break; } } Index: modules/menu/menu.test =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.test,v retrieving revision 1.31 diff -u -p -r1.31 menu.test --- modules/menu/menu.test 9 Jan 2010 21:54:00 -0000 1.31 +++ modules/menu/menu.test 14 Jan 2010 19:03:17 -0000 @@ -112,14 +112,14 @@ class MenuTestCase extends DrupalWebTest // Assert the new menu. $this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit'); - $this->assertText($title, t('Custom menu was added.')); + $this->assertRaw($title, t('Custom menu was added.')); // Edit the menu. $new_title = $this->randomName(16); $menu['title'] = $new_title; menu_save($menu); $this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit'); - $this->assertText($new_title, t('Custom menu was edited.')); + $this->assertRaw($new_title, t('Custom menu was edited.')); } /** Index: modules/openid/openid.test =================================================================== RCS file: /cvs/drupal/drupal/modules/openid/openid.test,v retrieving revision 1.9 diff -u -p -r1.9 openid.test --- modules/openid/openid.test 9 Jan 2010 23:03:21 -0000 1.9 +++ modules/openid/openid.test 14 Jan 2010 19:08:12 -0000 @@ -163,7 +163,7 @@ class OpenIDFunctionalTest extends Drupa // Check we are on the OpenID redirect form. $this->assertTitle(t('OpenID redirect'), t('OpenID redirect page was displayed.')); - + // Submit form to the OpenID Provider Endpoint. $this->drupalPost(NULL, array(), t('Send')); $this->assertText('john', t('User was logged in.'));