=== modified file 'includes/menu.inc'
--- includes/menu.inc	2009-03-14 20:56:06 +0000
+++ includes/menu.inc	2009-03-19 16:27:20 +0000
@@ -668,11 +668,44 @@ function _menu_link_map_translate(&$map,
   }
 }
 
+/**
+ * Collapse the rest of arguments into one part.
+ *
+ * For example, when searching for foo/bar, you want to pass in 'foo/bar' as
+ * one argument, instead of passing 'foo', 'bar'.
+ */
 function menu_tail_to_arg($arg, $map, $index) {
   return implode('/', array_slice($map, $index));
 }
 
 /**
+ * Extract an id from a menu argument using menu_get_object().
+ *
+ * Typically passed from a to_arg function, passing $arg and $index along.
+ *
+ * @param $type
+ *   Type of the object. These appear in hook_menu definitions as %type. Core
+ *   provides aggregator_feed, aggregator_category, contact, filter_format,
+ *   forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the
+ *   relevant {$type}_load function for more on each. Defaults to node.
+ * @param $key
+ *   The object property or array index in the object/array returned by
+ *   menu_get_object() like nid or fid.
+ * @param $arg
+ *   The argument itself.
+ * @param $ndex
+ *   The index of the argument in the path, where the first path segment is 0.
+ *   For node/%node, the position of %node is 1, but for comment/reply/%node,
+ *   it's 2.
+ */
+function menu_arg_extract_id($type, $key, $arg, $index) {
+  if ($arg == '%' && ($object = menu_get_object($type, $index))) {
+    $arg = is_object($object) ? $object->$key : $object[$key];
+  }
+  return $arg;
+}
+
+/**
  * This function is similar to _menu_translate() but does link-specific
  * preparation such as always calling to_arg functions
  *
@@ -688,6 +721,7 @@ function menu_tail_to_arg($arg, $map, $i
  *   to $item['localized_options'] by _menu_item_localize().
  */
 function _menu_link_translate(&$item) {
+  global $menu_admin;
   $item['options'] = unserialize($item['options']);
   if ($item['external']) {
     $item['access'] = 1;
@@ -703,9 +737,17 @@ function _menu_link_translate(&$item) {
 
     // Note - skip callbacks without real values for their arguments.
     if (strpos($item['href'], '%') !== FALSE) {
+      if ($menu_admin) {
+        $item['access'] = TRUE;
+        $item['title'] = $item['link_title'];
+        $item['_no_link'] = TRUE;
+        return $map;
+      }
+      else {
       $item['access'] = FALSE;
       return FALSE;
     }
+    }
     // menu_tree_check_access() may set this ahead of time for links to nodes.
     if (!isset($item['access'])) {
       if (!_menu_load_objects($item, $map)) {
@@ -755,7 +797,7 @@ function _menu_link_translate(&$item) {
  *   For node/%node, the position of %node is 1, but for comment/reply/%node,
  *   it's 2. Defaults to 1.
  * @param $path
- *   See menu_get_item() for more on this. Defaults to the current path.
+ *   @see menu_get_item() for more on this. Defaults to the current path.
  */
 function menu_get_object($type = 'node', $position = 1, $path = NULL) {
   $router_item = menu_get_item($path);
@@ -1740,7 +1782,8 @@ function menu_get_active_title() {
  *   A menu link, with $item['access'] filled and link translated for
  *   rendering.
  */
-function menu_link_load($mlid) {
+function menu_link_load($mlid, $is_menu_admin = FALSE) {
+  global $menu_admin;
   if (is_numeric($mlid)) {
     $query = db_select('menu_links', 'ml');
     $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
@@ -1748,7 +1791,9 @@ function menu_link_load($mlid) {
     $query->fields('m');
     $query->condition('ml.mlid', $mlid);
     if ($item = $query->execute()->fetchAssoc()) {
+      $menu_admin = $is_menu_admin;
       _menu_link_translate($item);
+      $menu_admin = FALSE;
       return $item;
     }
   }
@@ -2686,15 +2731,11 @@ function menu_valid_path($form_item) {
   if ($path == '<front>' || menu_path_is_external($path)) {
     $item = array('access' => TRUE);
   }
-  elseif (preg_match('/\/\%/', $path)) {
-    // Path is dynamic (ie 'user/%'), so check directly against menu_router table.
-    if ($item = db_query("SELECT * FROM {menu_router} where path = :path", array(':path' => $path))->fetchAssoc()) {
-      $item['link_path']  = $form_item['link_path'];
-      $item['link_title'] = $form_item['link_title'];
-      $item['external']   = FALSE;
-      $item['options'] = '';
-      _menu_link_translate($item);
-    }
+  // Path is dynamic (ie 'user/%'), so check directly against menu_router
+  // table. In the hope that on some page these will be accessible, we let
+  // them in.
+  elseif (preg_match('|/%|', $path) && ($item = db_fetch_array(db_query("SELECT * FROM {menu_router} where path = '%s' ", $path)))) {
+    $item['access'] = TRUE;
   }
   else {
     $item = menu_get_item($path);

=== modified file 'modules/aggregator/aggregator.module'
--- modules/aggregator/aggregator.module	2009-03-01 07:21:02 +0000
+++ modules/aggregator/aggregator.module	2009-03-19 16:27:20 +0000
@@ -554,6 +554,13 @@ function aggregator_refresh($feed) {
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function aggregator_feed_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('aggregator_feed', 'fid', $arg, $index);
+}
+
+/**
  * Load an aggregator feed.
  *
  * @param $fid
@@ -571,6 +578,19 @@ function aggregator_feed_load($fid) {
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function aggregator_category_to_arg($arg, $map, $index) {
+  // For dynamic links, we check whether we are on an aggregator feed page,
+  // and change the argument to the actual fid.
+  if ($arg == '%' && ($category = menu_get_object('aggregator_category', $index))) {
+    $arg = $category['cid'];
+  }
+  return $arg;
+}
+
+
+/**
  * Load an aggregator category.
  *
  * @param $cid

=== modified file 'modules/contact/contact.module'
--- modules/contact/contact.module	2009-03-08 05:08:22 +0000
+++ modules/contact/contact.module	2009-03-19 16:27:20 +0000
@@ -128,6 +128,13 @@ function _contact_user_tab_access($accou
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function contact_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('contact', 'cid', $arg, $index);
+}
+
+/**
  * Load the data for a single contact category.
  */
 function contact_load($cid) {

=== modified file 'modules/filter/filter.module'
--- modules/filter/filter.module	2009-03-15 01:53:16 +0000
+++ modules/filter/filter.module	2009-03-19 16:27:20 +0000
@@ -133,6 +133,13 @@ function filter_menu() {
   return $items;
 }
 
+/**
+ * Change arguments of dynamic menu links.
+ */
+function filter_format_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('filter_format', 'fid', $arg, $index);
+}
+
 function filter_format_load($arg) {
   return filter_formats($arg);
 }
@@ -485,15 +492,15 @@ function check_markup($text, $format = F
 function filter_form($value = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) {
   $value = filter_resolve_format($value);
   $formats = filter_formats();
-  
+
   $form = array(
    '#type' => 'fieldset',
    '#weight' => $weight,
    '#attributes' => array('class' => 'filter-wrapper'),
   );
-  
+
   $element_id = form_clean_id('edit-' . implode('-', $parents));
-   
+
   if (count($formats) > 1) {
     foreach ($formats as $format) {
       $options[$format->format] = $format->name;

=== modified file 'modules/forum/forum.module'
--- modules/forum/forum.module	2009-03-17 12:41:54 +0000
+++ modules/forum/forum.module	2009-03-19 16:27:20 +0000
@@ -65,6 +65,18 @@ function forum_theme() {
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function forum_term_to_arg($arg, $map, $index) {
+  // For dynamic links, we check if we are administering a forum term, change
+  // the argument to the current forum term id.
+  if ($arg == '%' && ($forum_term = menu_get_object('forum_term', $index))) {
+    $arg = $forum_term['tid'];
+  }
+  return $arg;
+}
+
+/**
  * Fetch a forum term.
  *
  * @param $tid

=== modified file 'modules/menu/menu.admin.inc'
--- modules/menu/menu.admin.inc	2009-03-16 20:31:11 +0000
+++ modules/menu/menu.admin.inc	2009-03-19 16:27:20 +0000
@@ -62,14 +62,17 @@ function menu_overview_form(&$form_state
 function _menu_overview_tree_form($tree) {
   static $form = array('#tree' => TRUE);
   foreach ($tree as $data) {
-    $title = '';
     $item = $data['link'];
     // Don't show callbacks; these have $item['hidden'] < 0.
     if ($item && $item['hidden'] >= 0) {
       $mlid = 'mlid:' . $item['mlid'];
       $form[$mlid]['#item'] = $item;
       $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => 'menu-disabled') : array('class' => 'menu-enabled');
-      $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['localized_options']) . ($item['hidden'] ? ' (' . t('disabled') . ')' : '');
+      $title = empty($item['_no_link']) ? l($item['title'], $item['href'], $item['options']) : t('@title (dynamic)', array('@title' => $item['title']));
+      $form[$mlid]['title']['#markup'] = $title;
+      if ($item['hidden']) {
+        $form[$mlid]['title']['#markup'] .= ' ('. t('disabled') .')';
+      }
       $form[$mlid]['hidden'] = array(
         '#type' => 'checkbox',
         '#default_value' => !$item['hidden'],
@@ -376,7 +379,10 @@ function menu_edit_item_submit($form, &$
 
   $item['options']['attributes']['title'] = $item['description'];
   list($item['menu_name'], $item['plid']) = explode(':', $item['parent']);
-  if (!menu_link_save($item)) {
+  if (menu_link_save($item)) {
+    drupal_set_message(t('Your item was saved'));
+  }
+  else {
     drupal_set_message(t('There was an error saving the menu link.'), 'error');
   }
   $form_state['redirect'] = 'admin/build/menu-customize/' . $item['menu_name'];

=== modified file 'modules/menu/menu.module'
--- modules/menu/menu.module	2009-03-08 04:25:03 +0000
+++ modules/menu/menu.module	2009-03-19 16:27:20 +0000
@@ -115,6 +115,7 @@ function menu_menu() {
     'title' => 'Edit menu item',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('menu_edit_item', 'edit', 4, NULL),
+    'load arguments' => array(TRUE),
     'access arguments' => array('administer menu'),
     'type' => MENU_CALLBACK,
   );
@@ -122,6 +123,7 @@ function menu_menu() {
     'title' => 'Reset menu item',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('menu_reset_item_confirm', 4),
+    'load arguments' => array(TRUE),
     'access arguments' => array('administer menu'),
     'type' => MENU_CALLBACK,
   );
@@ -129,6 +131,7 @@ function menu_menu() {
     'title' => 'Delete menu item',
     'page callback' => 'menu_item_delete_page',
     'page arguments' => array(4),
+    'load arguments' => array(TRUE),
     'access arguments' => array('administer menu'),
     'type' => MENU_CALLBACK,
   );
@@ -179,6 +182,13 @@ function menu_overview_title($menu) {
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function menu_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('menu', 'menu_name', $arg, $index);
+}
+
+/**
  * Load the data for a single custom menu.
  */
 function menu_load($menu_name) {

=== modified file 'modules/menu/menu.test'
--- modules/menu/menu.test	2009-03-16 20:31:11 +0000
+++ modules/menu/menu.test	2009-03-19 16:29:52 +0000
@@ -18,7 +18,7 @@ class MenuTestCase extends DrupalWebTest
   function setUp() {
     parent::setUp('menu');
     // Create users.
-    $this->big_user = $this->drupalCreateUser(array('access administration pages', 'administer blocks', 'administer menu', 'create article content'));
+    $this->big_user = $this->drupalCreateUser(array('access administration pages', 'administer blocks', 'administer menu', 'create article content', 'edit own article content'));
     $this->std_user = $this->drupalCreateUser(array());
   }
 
@@ -142,6 +142,17 @@ class MenuTestCase extends DrupalWebTest
     // Add menu links.
     $item1 = $this->addMenuLink(0, 'node/' . $node1->nid, $menu_name);
     $item2 = $this->addMenuLink($item1['mlid'], 'node/' . $node2->nid, $menu_name);
+    $dynamic_item = $this->addMenuLink(0, 'node/%/edit');
+    $title = $dynamic_link['link_title'];
+    $this->assertText(t('@title (dynamic)', array('@title' => $title)), t('Dynamic text found'));
+    $this->assertNoRaw('href="node/%/edit"', t('Dynamic link is not a link'));
+    $this->drupalGet("node/$node1->nid");
+    $urls = $this->xpath('//a[text()="' . $title . '"]');
+    $this->assertTrue($urls, t('link found'));
+    $this->assertTrue(strpos($urls[0]['href'], "node/$node1->nid/edit") !== FALSE, t('Dynamic link points to the correct url'));
+    $this->drupalGet("");
+    $urls = $this->xpath('//a[text()="' . $title . '"]');
+    $this->assertFalse($urls, t('link not found'));
 
     // Verify menu links.
     $this->verifyMenuLink($item1, $node1);
@@ -154,7 +165,7 @@ class MenuTestCase extends DrupalWebTest
     // Toggle menu links.
     $this->toggleMenuLink($item1);
     $this->toggleMenuLink($item2);
-    
+
     // Enable a link via the overview form.
     $this->disableMenuLink($item1);
     $edit = array();

=== modified file 'modules/node/node.module'
--- modules/node/node.module	2009-03-14 23:01:36 +0000
+++ modules/node/node.module	2009-03-19 16:27:20 +0000
@@ -1675,6 +1675,13 @@ function _node_add_access() {
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function node_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('node', 'nid', $arg, $index);
+}
+
+/**
  * Implementation of hook_menu().
  */
 function node_menu() {
@@ -1946,7 +1953,7 @@ function node_feed($nids = FALSE, $chann
         $item->body = $content;
         unset($item->teaser);
       }
-    
+
       // Allow modules to modify the fully-built node.
       node_invoke_node($item, 'alter', $teaser, FALSE);
     }

=== modified file 'modules/taxonomy/taxonomy.module'
--- modules/taxonomy/taxonomy.module	2009-03-08 04:25:03 +0000
+++ modules/taxonomy/taxonomy.module	2009-03-19 16:27:20 +0000
@@ -70,7 +70,7 @@ function taxonomy_node_view($node) {
       }
     }
   }
-  
+
   $node->content['links']['terms'] = array(
     '#type' => 'node_links',
     '#value' => $links,
@@ -320,7 +320,7 @@ function taxonomy_term_save($term) {
     $status = drupal_write_record('taxonomy_term_data', $term);
     module_invoke_all('taxonomy_term_update', $term);
   }
-  
+
   $or = db_or()->condition('tid1', $term->tid)->condition('tid2', $term->tid);
   db_delete('taxonomy_term_relation')->condition($or)->execute();
 
@@ -968,6 +968,13 @@ function taxonomy_get_term_by_name($name
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function taxonomy_vocabulary_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('taxonomy_vocabulary', 'vid', $arg, $index);
+}
+
+/**
  * Return the vocabulary object matching a vocabulary ID.
  *
  * @param $vid

=== modified file 'modules/user/user.module'
--- modules/user/user.module	2009-03-17 02:07:42 +0000
+++ modules/user/user.module	2009-03-19 16:27:20 +0000
@@ -1252,6 +1252,10 @@ function user_load_self($arg) {
   return $arg;
 }
 
+function user_category_to_arg($arg) {
+  return user_uid_optional_to_arg($arg);
+}
+
 /**
  * Implementation of hook_menu().
  */

