Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.348
diff -u -p -r1.348 menu.inc
--- includes/menu.inc	1 Oct 2009 19:07:12 -0000	1.348
+++ includes/menu.inc	6 Oct 2009 21:33:52 -0000
@@ -694,11 +694,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 $index
+ *   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
  *
@@ -714,6 +747,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;
@@ -729,8 +763,16 @@ function _menu_link_translate(&$item) {
 
     // Note - skip callbacks without real values for their arguments.
     if (strpos($item['href'], '%') !== FALSE) {
-      $item['access'] = FALSE;
-      return 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'])) {
@@ -1932,7 +1974,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');
@@ -1940,7 +1983,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;
     }
   }
@@ -2981,15 +3026,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_query('SELECT * FROM {menu_router} WHERE path = :path', array(':path' => $path))->fetchAssoc())) {
+    $item['access'] = TRUE;
   }
   else {
     $item = menu_get_item($path);
Index: modules/aggregator/aggregator.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.module,v
retrieving revision 1.424
diff -u -p -r1.424 aggregator.module
--- modules/aggregator/aggregator.module	25 Sep 2009 15:20:12 -0000	1.424
+++ modules/aggregator/aggregator.module	6 Oct 2009 21:33:52 -0000
@@ -606,6 +606,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
@@ -623,6 +630,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 category id.
+  if ($arg === '%' && ($category = menu_get_object('aggregator_category', $index))) {
+    $arg = $category['cid'];
+  }
+  return $arg;
+}
+
+
+/**
  * Load an aggregator category.
  *
  * @param $cid
Index: modules/contact/contact.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/contact/contact.module,v
retrieving revision 1.128
diff -u -p -r1.128 contact.module
--- modules/contact/contact.module	26 Sep 2009 00:13:19 -0000	1.128
+++ modules/contact/contact.module	6 Oct 2009 21:33:52 -0000
@@ -121,6 +121,13 @@ function _contact_personal_tab_access($a
 }
 
 /**
+ * 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) {
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.293
diff -u -p -r1.293 filter.module
--- modules/filter/filter.module	5 Oct 2009 01:18:25 -0000	1.293
+++ modules/filter/filter.module	6 Oct 2009 21:33:52 -0000
@@ -148,6 +148,13 @@ function _filter_delete_format_access($f
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function filter_format_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('filter_format', 'fid', $arg, $index);
+}
+
+/**
  * Load a text format object from the database.
  *
  * @param $format
Index: modules/forum/forum.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.module,v
retrieving revision 1.519
diff -u -p -r1.519 forum.module
--- modules/forum/forum.module	18 Sep 2009 00:04:22 -0000	1.519
+++ modules/forum/forum.module	6 Oct 2009 21:33:52 -0000
@@ -61,6 +61,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 actual 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
Index: modules/menu/menu.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v
retrieving revision 1.61
diff -u -p -r1.61 menu.admin.inc
--- modules/menu/menu.admin.inc	3 Oct 2009 01:16:02 -0000	1.61
+++ modules/menu/menu.admin.inc	6 Oct 2009 21:33:52 -0000
@@ -80,14 +80,16 @@ function menu_overview_form($form, &$for
 function _menu_overview_tree_form($tree) {
   $form = &drupal_static(__FUNCTION__, 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' => array('menu-disabled')) : array('class' => array('menu-enabled'));
-      $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['localized_options']) . ($item['hidden'] ? ' (' . t('disabled') . ')' : '');
+      $form[$mlid]['title']['#markup'] = empty($item['_no_link']) ? l($item['title'], $item['href'], $item['localized_options']) : t('@title (dynamic)', array('@title' => $item['title']));
+      if ($item['hidden']) {
+        $form[$mlid]['title']['#markup'] .= ' ('. t('disabled') .')';
+      }
       $form[$mlid]['hidden'] = array(
         '#type' => 'checkbox',
         '#default_value' => !$item['hidden'],
@@ -395,7 +397,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('The menu link has been saved.'));
+  }
+  else {
     drupal_set_message(t('There was an error saving the menu link.'), 'error');
   }
   $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
Index: modules/menu/menu.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v
retrieving revision 1.206
diff -u -p -r1.206 menu.module
--- modules/menu/menu.module	3 Oct 2009 01:16:02 -0000	1.206
+++ modules/menu/menu.module	6 Oct 2009 21:33:52 -0000
@@ -121,6 +121,7 @@ function menu_menu() {
     'title' => 'Edit menu link',
     '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,
     'file' => 'menu.admin.inc',
@@ -129,6 +130,7 @@ function menu_menu() {
     'title' => 'Reset menu link',
     '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,
     'file' => 'menu.admin.inc',
@@ -137,6 +139,7 @@ function menu_menu() {
     'title' => 'Delete menu link',
     'page callback' => 'menu_item_delete_page',
     'page arguments' => array(4),
+    'load arguments' => array(TRUE),
     'access arguments' => array('administer menu'),
     'type' => MENU_CALLBACK,
     'file' => 'menu.admin.inc',
@@ -197,6 +200,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) {
Index: modules/menu/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.test,v
retrieving revision 1.22
diff -u -p -r1.22 menu.test
--- modules/menu/menu.test	28 Sep 2009 22:14:30 -0000	1.22
+++ modules/menu/menu.test	6 Oct 2009 21:50:31 -0000
@@ -23,7 +23,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());
   }
 
@@ -156,7 +156,6 @@ class MenuTestCase extends DrupalWebTest
 
   /**
    * Test menu functionality using navigation menu.
-   *
    */
   function doMenuTests($menu_name = 'navigation') {
     // Add nodes to use as links for menu links.
@@ -166,6 +165,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_link = $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);
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1139
diff -u -p -r1.1139 node.module
--- modules/node/node.module	5 Oct 2009 01:18:25 -0000	1.1139
+++ modules/node/node.module	6 Oct 2009 21:33:52 -0000
@@ -1765,6 +1765,13 @@ function node_menu() {
 }
 
 /**
+ * Change arguments of dynamic menu links.
+ */
+function node_to_arg($arg, $map, $index) {
+  return menu_arg_extract_id('node', 'nid', $arg, $index);
+}
+
+/**
  * Title callback.
  */
 function node_page_title($node) {
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.513
diff -u -p -r1.513 taxonomy.module
--- modules/taxonomy/taxonomy.module	26 Sep 2009 15:57:39 -0000	1.513
+++ modules/taxonomy/taxonomy.module	6 Oct 2009 21:33:52 -0000
@@ -1362,6 +1362,13 @@ function taxonomy_vocabulary_load_multip
 }
 
 /**
+ * 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
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1055
diff -u -p -r1.1055 user.module
--- modules/user/user.module	2 Oct 2009 14:49:10 -0000	1.1055
+++ modules/user/user.module	6 Oct 2009 21:33:52 -0000
@@ -1204,6 +1204,10 @@ function user_load_self($arg) {
   return $arg;
 }
 
+function user_category_to_arg($arg) {
+  return user_uid_optional_to_arg($arg);
+}
+
 /**
  * Implement hook_menu().
  */
