Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1199
diff -u -p -r1.1199 common.inc
--- includes/common.inc	3 Aug 2010 02:14:50 -0000	1.1199
+++ includes/common.inc	3 Aug 2010 13:39:01 -0000
@@ -2017,13 +2017,37 @@ function format_username($account) {
  *     this value to 'index.php'.
  *   - 'entity_type': The entity type of the object that called url(). Only set if
  *     url() is invoked by entity_uri().
+ *     @todo Document that this is usually handled internally.
  *   - 'entity': The entity object (such as a node) for which the URL is being
  *     generated. Only set if url() is invoked by entity_uri().
+ *     @todo Document that this is usually handled internally.
  *
  * @return
  *   A string containing a URL to the given path.
  */
 function url($path = NULL, array $options = array()) {
+  // Use the advanced drupal_static() pattern, since this is called very often.
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['altered_entity_root_paths'] = &drupal_static(__FUNCTION__);
+  }
+  $altered_entity_root_paths = &$drupal_static_fast['altered_entity_root_paths'];
+
+  // The first time this function is called, populate a list of all
+  // entity-related root paths we will need to search for, in order to properly
+  // link to entities.
+  if (!isset($altered_entity_root_paths)) {
+    $altered_entity_root_paths = array();
+    $entity_info = entity_get_info();
+    foreach ($entity_info as $entity_type => $info) {
+      if (!empty($info['altered root paths'])) {
+        foreach ($info['altered root paths'] as $root_path) {
+          $altered_entity_root_paths[$root_path] = $entity_type;
+        }
+      }
+    }
+  }
+
   // Merge in defaults.
   $options += array(
     'fragment' => '',
@@ -2043,6 +2067,36 @@ function url($path = NULL, array $option
     $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
   }
 
+  // If any modules altered entity URI callbacks, we need to check this path
+  // against that list and possibly rewrite it. We don't need to do that if the
+  // path was passed through entity_uri() already, though.
+  if (!empty($altered_entity_root_paths) && !isset($options['entity_type'])) {
+    // First do a quick check to see if the provided path is on our list; we
+    // only need to invoke the entity code if it is.
+    $slash_position = strrpos($path, '/');
+    if ($slash_position !== FALSE) {
+      $root_path = substr($path, 0, $slash_position);
+       if (isset($altered_entity_root_paths[$root_path])) {
+         // Load the entity by ID. Note that entity_load() is cached, and if we
+         // are linking to the entity it will generally already have been
+         // loaded anyway, but it would be nicer if we could find a way to do
+         // this with a stub entity instead...
+         $entity_type = $altered_entity_root_paths[$root_path];
+         $entity_id = substr($path, $slash_position + 1);
+         $entities = entity_load($entity_type, array($entity_id));
+         $entity = reset($entities);
+         // Generate an entity URI and use the results of that to construct the
+         // URL in the rest of this function.
+         $entity_uri = entity_uri($entity_type, $entity);
+         if (isset($entity_uri)) {
+           $path = $entity_uri['path'];
+           // @todo This may not handle fragments and query strings correctly.
+           $options = array_merge_recursive($entity_uri['options'], $options);
+         }
+       }
+    }
+  }
+
   // Preserve the original path before altering or aliasing.
   $original_path = $path;
 
@@ -6470,8 +6524,38 @@ function entity_get_info($entity_type = 
           $entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
         }
       }
-      // Let other modules alter the entity info.
+      // Let other modules alter the entity info. Make a copy of the original
+      // entity info beforehand so we can compare what changed below.
+      $original_entity_info = $entity_info;
       drupal_alter('entity_info', $entity_info);
+      // Search for any URI callbacks changed or added by the drupal_alter()
+      // call, and record the root path (the non-entity-specific part of the
+      // URI). For example, when the Forum module is enabled, we will use this
+      // information in url() to rewrite links to 'taxonomy/term/[tid]' as
+      // 'forum/[tid]'; in that case, 'taxonomy/term' will be recorded by the
+      // code below.
+      foreach ($entity_info as $name => $info) {
+        // Strip the original and altered entity info down to arrays that only
+        // contain 'uri callback' keys, and save the altered root paths if any
+        // of them changed. If there were no uri callbacks originally, then
+        // there is no need to record anything, because there is no way anyone
+        // could link to the original entity.
+        // @todo This code does not support the edge case of multiple levels
+        // of alteration; e.g., if a module implements hook_entity_info_alter()
+        // to further rewrite the Forum module's URLs, then calls to
+        // url('taxonomy/term/[tid]') will properly be translated to the final
+        // value, but direct calls to url('forum/[tid]') won't. To support
+        // that, we would need to replace the drupal_alter() call above with an
+        // explicit loop, and run the code here each time through the loop.
+        $entity_info[$name]['altered root paths'] = array();
+        _entity_info_extract_uri_callbacks($original_entity_info[$name]);
+        if (!empty($original_entity_info[$name])) {
+          _entity_info_extract_uri_callbacks($info);
+          if ($info != $original_entity_info[$name]) {
+            $entity_info[$name]['altered root paths'] = _entity_uri_collect_root_paths($name, $original_entity_info[$name]);
+          }
+        }
+      }
       cache_set("entity_info:$langcode", $entity_info);
     }
   }
@@ -6485,6 +6569,63 @@ function entity_get_info($entity_type = 
 }
 
 /**
+ * @todo Document
+ */
+function _entity_info_extract_uri_callbacks(&$info) {
+  foreach ($info as $key => &$data) {
+    if ($key != 'uri callback' && $key != 'bundles') {
+      unset($info[$key]);
+    }
+    elseif ($key == 'bundles') {
+      foreach ($data as $bundle_name => $bundle_data) {
+        if (isset($bundle_data['uri callback'])) {
+          $data[$bundle_name] = array('uri callback' => $bundle_data['uri callback']);
+        }
+        else {
+          unset($data[$bundle_name]);
+        }
+      }
+    }
+  }
+  if (empty($info['bundles'])) {
+    unset($info['bundles']);
+  }
+}
+
+/**
+ * @todo Document
+ */
+function _entity_uri_collect_root_paths($entity_type, $info) {
+  $root_paths = array();
+  foreach ($info as $value) {
+    if (is_array($value)) {
+      $root_paths = array_merge($root_paths, _entity_uri_collect_root_paths($entity_type, $value));
+    }
+    else {
+      $root_path = entity_uri_root_path($entity_type, $value);
+      if (isset($root_path)) {
+        $root_paths[] = $root_path;
+      }
+    }
+  }
+  return $root_paths;
+}
+
+/**
+ * @todo Document
+ */
+function entity_uri_root_path($entity_type, $uri_callback) {
+  if (function_exists($uri_callback)) {
+    $entity = entity_create_stub_entity($entity_type, array(1, NULL, NULL));
+    $uri = $uri_callback($entity);
+    $slash_position = strrpos($uri['path'], '/');
+    if ($slash_position !== FALSE) {
+      return substr($uri['path'], 0, $slash_position);
+    }
+  }
+}
+
+/**
  * Resets the cached information about entity types.
  */
 function entity_info_cache_clear() {
@@ -6649,6 +6790,9 @@ function entity_prepare_view($entity_typ
  *   An array containing the 'path' and 'options' keys used to build the uri of
  *   the entity, and matching the signature of url(). NULL if the entity has no
  *   uri of its own.
+ *
+ * @todo Document that this does not need to be called unless you are dealing
+ *   with generic entities and therefore do not know the specific URL pattern.
  */
 function entity_uri($entity_type, $entity) {
   // This check enables the URI of an entity to be easily overridden from what
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.887
diff -u -p -r1.887 comment.module
--- modules/comment/comment.module	30 Jul 2010 02:50:37 -0000	1.887
+++ modules/comment/comment.module	3 Aug 2010 13:39:02 -0000
@@ -2214,9 +2214,8 @@ function template_preprocess_comment(&$v
   $variables['picture']   = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', array('account' => $comment)) : '';
   $variables['signature'] = $comment->signature;
 
-  $uri = entity_uri('comment', $comment);
-  $variables['title']     = l($comment->subject, $uri['path'], $uri['options']);
-  $variables['permalink'] = l('#', $uri['path'], $uri['options']);
+  $variables['title']     = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid));
+  $variables['permalink'] = l('#', 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid));
 
   // Preprocess fields.
   field_attach_preprocess('comment', $comment, $variables['elements'], $variables);
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1283
diff -u -p -r1.1283 node.module
--- modules/node/node.module	2 Aug 2010 05:29:08 -0000	1.1283
+++ modules/node/node.module	3 Aug 2010 13:39:02 -0000
@@ -1348,8 +1348,7 @@ function template_preprocess_node(&$vari
   $variables['date']      = format_date($node->created);
   $variables['name']      = theme('username', array('account' => $node));
 
-  $uri = entity_uri('node', $node);
-  $variables['node_url']  = url($uri['path'], $uri['options']);
+  $variables['node_url']  = url('node/' . $node->nid);
   $variables['title']     = check_plain($node->title);
   $variables['page']      = node_is_page($node);
 
@@ -1575,9 +1574,8 @@ function node_search_execute($keys = NUL
 
     $extra = module_invoke_all('node_search_result', $node);
 
-    $uri = entity_uri('node', $node);
     $results[] = array(
-      'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
+      'link' => url('node/' . $node->nid, array('absolute' => TRUE)),
       'type' => check_plain(node_type_get_name($node)),
       'title' => $node->title,
       'user' => theme('username', array('account' => $node)),
@@ -2510,11 +2508,10 @@ function node_page_view($node) {
   // of the active trail, and the link name becomes the page title.
   // Thus, we must explicitly set the page title to be the node title.
   drupal_set_title($node->title);
-  $uri = entity_uri('node', $node);
   // Set the node path as the canonical URL to prevent duplicate content.
-  drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
+  drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url('node/' . $node->nid)), TRUE);
   // Set the non-aliased path as a default shortlink.
-  drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
+  drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url('node/' . $node->nid, array('alias' => TRUE))), TRUE);
   return node_show($node);
 }
 
Index: modules/rdf/rdf.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/rdf/rdf.module,v
retrieving revision 1.43
diff -u -p -r1.43 rdf.module
--- modules/rdf/rdf.module	23 Jul 2010 04:39:32 -0000	1.43
+++ modules/rdf/rdf.module	3 Aug 2010 13:39:02 -0000
@@ -565,30 +565,30 @@ function rdf_preprocess_field(&$variable
  */
 function rdf_preprocess_user_profile(&$variables) {
   $account = $variables['elements']['#account'];
-  $uri = entity_uri('user', $account);
+  $account_url = url('user/' . $account->uid);
 
   // Adds RDFa markup to the user profile page. Fields displayed in this page
   // will automatically describe the user.  
   if (!empty($account->rdf_mapping['rdftype'])) {
     $variables['attributes_array']['typeof'] = $account->rdf_mapping['rdftype'];
-    $variables['attributes_array']['about'] = url($uri['path'], $uri['options']);
+    $variables['attributes_array']['about'] = $account_url;
   }
   // Adds the relationship between the sioc:UserAccount and the foaf:Person who
   // holds the account.
   $account_holder_meta = array(
     '#tag' => 'meta',
     '#attributes' => array(
-      'about' => url($uri['path'], array_merge($uri['options'], array('fragment' => 'me'))),
+      'about' => url('user/' . $account->uid, array('fragment' => 'me')),
       'typeof' => array('foaf:Person'),
       'rel' => array('foaf:account'),
-      'resource' => url($uri['path'], $uri['options']),
+      'resource' => $account_url,
     ),
   );
   // Adds the markup for username.
   $username_meta = array(
     '#tag' => 'meta',
     '#attributes' => array(
-      'about' => url($uri['path'], $uri['options']),
+      'about' => $account_url,
       'property' => $account->rdf_mapping['name']['predicates'],
       'content' => $account->name,
     )
@@ -661,8 +661,7 @@ function rdf_preprocess_comment(&$variab
     // Adds RDFa markup to the comment container. The about attribute specifies
     // the URI of the resource described within the HTML element, while the
     // typeof attribute indicates its RDF type (e.g. sioc:Post, etc.).
-    $uri = entity_uri('comment', $comment);
-    $variables['attributes_array']['about'] = url($uri['path'], $uri['options']);
+    $variables['attributes_array']['about'] = url('comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid));
     $variables['attributes_array']['typeof'] = $comment->rdf_mapping['rdftype'];
   }
 
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.180
diff -u -p -r1.180 system.api.php
--- modules/system/system.api.php	26 Jul 2010 13:38:30 -0000	1.180
+++ modules/system/system.api.php	3 Aug 2010 13:39:03 -0000
@@ -87,6 +87,14 @@ function hook_hook_info_alter(&$hooks) {
  *   - uri callback: A function taking an entity as argument and returning the
  *     uri elements of the entity, e.g. 'path' and 'options'. The actual entity
  *     uri can be constructed by passing these elements to url().
+ *     @todo Document the restriction that entity URIs returned from this
+ *       callback must only use the entity ID (and no other entity property) to
+ *       construct the URI, and that it must be the last part of the path;
+ *       e.g., for nodes, it can be some/random/path/{$node->nid}, plus an
+ *       optional fragment and query string (although I'm not sure the code
+ *       written so far properly supports fragments and query strings). This
+ *       restriction is pretty reasonable and is already met by all core entity
+ *       URI callbacks.
  *   - fieldable: Set to TRUE if you want your entity type to be fieldable.
  *   - entity keys: An array describing how the Field API can extract the
  *     information it needs from the objects of the type. Elements:
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.600
diff -u -p -r1.600 taxonomy.module
--- modules/taxonomy/taxonomy.module	3 Aug 2010 02:17:18 -0000	1.600
+++ modules/taxonomy/taxonomy.module	3 Aug 2010 13:39:03 -0000
@@ -635,8 +635,7 @@ function template_preprocess_taxonomy_te
   $variables['term'] = $variables['elements']['#term'];
   $term = $variables['term'];
 
-  $uri = entity_uri('taxonomy_term', $term);
-  $variables['term_url']  = url($uri['path'], $uri['options']);
+  $variables['term_url']  = url('taxonomy/term/' . $term->tid);
   $variables['term_name'] = check_plain($term->name);
   $variables['page']      = taxonomy_term_is_page($term);
 
@@ -1247,12 +1246,10 @@ function taxonomy_field_formatter_view($
         }
         else {
           $term = $item['taxonomy_term'];
-          $uri = entity_uri('taxonomy_term', $term);
           $element[$delta] = array(
             '#type' => 'link',
             '#title' => $term->name,
-            '#href' => $uri['path'],
-            '#options' => $uri['options'],
+            '#href' => 'taxonomy/term/' . $term->tid,
           );
         }
       }
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1188
diff -u -p -r1.1188 user.module
--- modules/user/user.module	30 Jul 2010 02:47:28 -0000	1.1188
+++ modules/user/user.module	3 Aug 2010 13:39:03 -0000
@@ -3536,9 +3536,9 @@ function user_register_submit($form, &$f
   $account->password = $pass;
 
   // New administrative account without notification.
-  $uri = entity_uri('user', $account);
+  $account_url = url('user/' . $account->uid);
   if ($admin && !$notify) {
-    drupal_set_message(t('Created a new user account for <a href="@url">%name</a>. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
+    drupal_set_message(t('Created a new user account for <a href="@url">%name</a>. No e-mail has been sent.', array('@url' => $account_url, '%name' => $account->name)));
   }
   // No e-mail verification required; log in user immediately.
   elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
@@ -3553,7 +3553,7 @@ function user_register_submit($form, &$f
     $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
     _user_mail_notify($op, $account);
     if ($notify) {
-      drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user <a href="@url">%name</a>.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
+      drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user <a href="@url">%name</a>.', array('@url' => $account_url, '%name' => $account->name)));
     }
     else {
       drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.'));
