? 504448-after-with-perm.png
? 504448-after-without-perm.png
? 504448-before.png
? 514256-after1.png
? 514256-after2.png
? 514256-before.png
? 570142-after.png
? 570142-before.png
? 570142-flood-control.png
? 570142-limited.png
? 597540-after-form.png
? 597540-after-settings.png
? 597540-before-form1.png
? 597540-before-form2.png
? 597540-before-settings.png
? contact.fr.po
? docs
? modules/syndication
? modules/path/path.install
? modules/simpletest/tests/path_test.info
? modules/simpletest/tests/path_test.module
? modules/simpletest/tests/url_alter_test.info
? modules/simpletest/tests/url_alter_test.module
? modules/simpletest/tests/url_alter_test_2.info
? modules/simpletest/tests/url_alter_test_2.module
? modules/system/system.http.inc
? profiles/.fr-2utJIN
? profiles/.fr-bP1TqB
? profiles/D7UXBeginner
? profiles/d7uxtest.rar
? sites/all/modules/contact_actions
? sites/all/modules/pgp
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.309
diff -u -p -r1.309 bootstrap.inc
--- includes/bootstrap.inc	9 Oct 2009 16:33:13 -0000	1.309
+++ includes/bootstrap.inc	15 Oct 2009 21:42:01 -0000
@@ -1085,7 +1085,7 @@ function drupal_serve_page_from_cache(st
  * Define the critical hooks that force modules to always be loaded.
  */
 function bootstrap_hooks() {
-  return array('boot', 'exit', 'watchdog');
+  return array('boot', 'exit', 'watchdog', 'url_inbound_alter');
 }
 
 /**
@@ -1382,7 +1382,7 @@ function drupal_anonymous_user($session 
  *   function called from drupal_bootstrap (recursion).
  * @return
  *   The most recently completed phase.
- *   
+ *
  */
 function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
   $final_phase = &drupal_static(__FUNCTION__ . '_final_phase');
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1021
diff -u -p -r1.1021 common.inc
--- includes/common.inc	15 Oct 2009 21:19:30 -0000	1.1021
+++ includes/common.inc	15 Oct 2009 21:42:02 -0000
@@ -2357,6 +2357,7 @@ function url($path = NULL, array $option
     'https' => FALSE,
     'prefix' => ''
   );
+
   if (!isset($options['external'])) {
     // Return an external link if $path contains an allowed absolute URL.
     // Only call the slow filter_xss_bad_protocol if $path contains a ':' before
@@ -2365,10 +2366,12 @@ function url($path = NULL, array $option
     $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path));
   }
 
-  // May need language dependent rewriting if language.inc is present.
-  if (function_exists('language_url_rewrite')) {
-    language_url_rewrite($path, $options);
-  }
+  // Preserve the original path before altering or aliasing.
+  $original_path = $path;
+
+  // Allow other modules to alter the outbound URL and options.
+  drupal_alter('url_outbound', $path, $options);
+
   if ($options['fragment']) {
     $options['fragment'] = '#' . $options['fragment'];
   }
@@ -2417,21 +2420,13 @@ function url($path = NULL, array $option
     }
   }
 
-  // Preserve the original path before aliasing.
-  $original_path = $path;
-
   // The special path '<front>' links to the default front page.
   if ($path == '<front>') {
     $path = '';
   }
   elseif (!empty($path) && !$options['alias']) {
     $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
-    $path = drupal_get_path_alias($path, $language);
-  }
-
-  if (function_exists('custom_url_rewrite_outbound')) {
-    // Modules may alter outbound links by reference.
-    custom_url_rewrite_outbound($path, $options, $original_path);
+    $path = drupal_get_path_alias($original_path, $language);
   }
 
   $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
@@ -2595,7 +2590,7 @@ function l($text, $path, array $options 
  *
  * In order for page callbacks to be reusable in different delivery formats,
  * they should not issue any "print" or "echo" statements, but instead just
- * return content. 
+ * return content.
  *
  * @param $page_callback_result
  *   The result of a page callback. Can be one of:
@@ -2608,7 +2603,7 @@ function l($text, $path, array $options 
  *   to be appropriate for the page request as determined by the calling
  *   function (e.g., menu_execute_active_handler()). If not given, it is
  *   determined from the menu router information of the current page. In either
- *   case, modules have a final chance to alter which function is called. 
+ *   case, modules have a final chance to alter which function is called.
  *
  * @see menu_execute_active_handler()
  * @see hook_menu()
@@ -2655,7 +2650,7 @@ function drupal_deliver_html_page($page_
   // Menu status constants are integers; page content is a string or array.
   if (is_int($page_callback_result)) {
     // @todo: Break these up into separate functions?
-    switch ($page_callback_result) {   
+    switch ($page_callback_result) {
       case MENU_NOT_FOUND:
         // Print a 404 page.
         drupal_add_http_header('404 Not Found');
@@ -2719,7 +2714,7 @@ function drupal_deliver_html_page($page_
         drupal_add_http_header('503 Service unavailable');
         drupal_set_title(t('Site under maintenance'));
         print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',
-          t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));    
+          t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
         break;
     }
   }
@@ -6018,4 +6013,3 @@ function drupal_get_updaters() {
   }
   return $updaters;
 }
-
Index: includes/language.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/language.inc,v
retrieving revision 1.20
diff -u -p -r1.20 language.inc
--- includes/language.inc	9 Oct 2009 16:33:13 -0000	1.20
+++ includes/language.inc	15 Oct 2009 21:42:02 -0000
@@ -325,47 +325,6 @@ function language_from_default() {
 }
 
 /**
- * Rewrite URLs allowing modules to hook in.
- *
- * @param $path
- *   The path to rewrite.
- * @param $options
- *   An associative array of additional options as in url().
- */
-function language_url_rewrite(&$path, &$options) {
-  // Only modify relative (insite) URLs.
-  if (!$options['external']) {
-    static $callbacks;
-
-    if (!isset($callbacks)) {
-      $callbacks = array();
-
-      foreach (language_types_configurable() as $type) {
-        // Get url rewriter callbacks only from enabled language providers.
-        $negotiation = variable_get("language_negotiation_$type", array());
-
-        foreach ($negotiation as $id => $provider) {
-          if (isset($provider['file'])) {
-            require_once DRUPAL_ROOT . '/' . $provider['file'];
-          }
-
-          // Avoid duplicate callback entries.
-          if (isset($provider['callbacks']['url_rewrite'])) {
-            $callbacks[$provider['callbacks']['url_rewrite']] = NULL;
-          }
-        }
-      }
-
-      $callbacks = array_keys($callbacks);
-    }
-
-    foreach ($callbacks as $callback) {
-      $callback($path, $options);
-    }
-  }
-}
-
-/**
  * Split the given path into prefix and actual path.
  *
  * Parse the given path and return the language object identified by the
Index: includes/path.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/path.inc,v
retrieving revision 1.45
diff -u -p -r1.45 path.inc
--- includes/path.inc	15 Oct 2009 17:53:34 -0000	1.45
+++ includes/path.inc	15 Oct 2009 21:42:02 -0000
@@ -205,13 +205,25 @@ function drupal_get_path_alias($path = N
  */
 function drupal_get_normal_path($path, $path_language = '') {
   $result = $path;
+
+  // Lookup the path alias first.
   if ($source = drupal_lookup_path('source', $path, $path_language)) {
     $result = $source;
   }
-  if (function_exists('custom_url_rewrite_inbound')) {
-    // Modules may alter the inbound request path by reference.
-    custom_url_rewrite_inbound($result, $path, $path_language);
+
+  // Allow other modules to alter the outbound URL and options.
+  // We can't use drupal_alter() here because we want to run the hooks in
+  // reverse order from hook_url_outbound_alter() and since we also can't
+  // run module_implements(). BLEH!
+  $result_copy = $result;
+  foreach (array_reverse(module_list(TRUE, TRUE)) as $module) {
+    drupal_load('module', $module);
+    $function = $module . '_url_inbound_alter';
+    if (function_exists($function)) {
+      $function($result, $result_copy, $path_language);
+    }
   }
+
   return $result;
 }
 
Index: modules/forum/forum.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.module,v
retrieving revision 1.524
diff -u -p -r1.524 forum.module
--- modules/forum/forum.module	15 Oct 2009 12:44:36 -0000	1.524
+++ modules/forum/forum.module	15 Oct 2009 21:42:02 -0000
@@ -610,7 +610,7 @@ function forum_block_view_pre_render($el
  */
 function forum_form($node, $form_state) {
   $type = node_type_get_type($node);
-  
+
   if (!empty($node->nid)) {
     $forum_terms = $node->taxonomy_forums;
     // If editing, give option to leave shadows
@@ -626,10 +626,28 @@ function forum_form($node, $form_state) 
 }
 
 /**
- * Implement hook_term_path().
+ * Implement hook_url_alter_outbound().
+ */
+function forum_url_outbound_alter(&$path, &$options) {
+  if (preg_match('!taxonomy/term/(\d+)!', $path, $matches)) {
+    $vocabulary = taxonomy_vocabulary_load($matches[1]);
+    if ($vocabulary && $vocabulary->module == 'forum') {
+      $original_path = $path;
+      $path = 'forum/' . $matches[1];
+      //drupal_set_message('ALTERED OUTBOUND' . $original_path . ' to ' . $path);
+    }
+  }
+}
+
+/**
+ * Implement hook_url_alter_outbound().
  */
-function forum_term_path($term) {
-  return 'forum/' . $term->tid;
+function forum_url_inbound_alter(&$path, $original_path, $language) {
+  if (preg_match('!forum/(\d+)!', $path, $matches)) {
+    $original_path = $path;
+    $path = 'taxonomy/term/' . $matches[1];
+    //drupal_set_message('ALTERED INBOND ' . $original_path . ' to ' . $path);
+  }
 }
 
 /**
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.263
diff -u -p -r1.263 locale.module
--- modules/locale/locale.module	13 Oct 2009 21:34:14 -0000	1.263
+++ modules/locale/locale.module	15 Oct 2009 21:42:02 -0000
@@ -1015,3 +1015,40 @@ function locale_date_format_reset_form_s
   $form_state['redirect'] = 'admin/config/regional/date-time/locale';
 }
 
+/**
+ * Implement hook_url_outbound_alter().
+ *
+ * Rewrite outbound URLs with language based prefixes.
+ */
+function locale_url_outbound_alter(&$path, &$options) {
+  // Only modify relative (insite) URLs.
+  if (!$options['external']) {
+    static $callbacks;
+
+    if (!isset($callbacks)) {
+      $callbacks = array();
+
+      foreach (language_types_configurable() as $type) {
+        // Get url rewriter callbacks only from enabled language providers.
+        $negotiation = variable_get("language_negotiation_$type", array());
+
+        foreach ($negotiation as $id => $provider) {
+          if (isset($provider['file'])) {
+            require_once DRUPAL_ROOT . '/' . $provider['file'];
+          }
+
+          // Avoid duplicate callback entries.
+          if (isset($provider['callbacks']['url_rewrite'])) {
+            $callbacks[$provider['callbacks']['url_rewrite']] = NULL;
+          }
+        }
+      }
+
+      $callbacks = array_keys($callbacks);
+    }
+
+    foreach ($callbacks as $callback) {
+      $callback($path, $options);
+    }
+  }
+}
Index: modules/simpletest/tests/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/path.test,v
retrieving revision 1.1
diff -u -p -r1.1 path.test
--- modules/simpletest/tests/path.test	11 Sep 2009 02:19:02 -0000	1.1
+++ modules/simpletest/tests/path.test	15 Oct 2009 21:42:02 -0000
@@ -126,3 +126,92 @@ class DrupalMatchPathTestCase extends Dr
     );
   }
 }
+
+
+/**
+ * Tests hook_url_alter functions.
+ */
+class UrlAlterFunctionalTest extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => t('URL altering'),
+      'description' => t('Tests hook_url_inbound_alter() and hook_url_outbound_alter().'),
+      'group' => t('Path API'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('path', 'url_alter_test', 'url_alter_test_2');
+  }
+  
+  function testAgain() {
+    $clean_url = variable_get('clean_url', 0);
+    variable_set('clean_url', !$clean_url);
+    $this->testUrlAlter();
+    variable_set('clean_url', $clean_url);
+  }
+
+  /**
+   * Test that URL altering works and that it occurs in the correct order.
+   */
+  function testUrlAlter() {
+    // Set a series of test aliases, not all of which should work.
+    $account = $this->drupalCreateUser();
+    $uid = $account->uid;
+    $name = $account->name;
+    path_set_alias("user/$uid/test1", 'alias/test1');
+    path_set_alias("user/$uid/test2", 'user/test2');
+    path_set_alias("user/$uid/test3", "user/$uid/alias/test3");
+    path_set_alias("member/$name/test4", 'alias/test4');
+
+    $tests = array(
+      // "User" should be rewritten to "member" by the second implementation.
+      'user'              => 'member',
+      // Similarly, "user/register" should be rewritten to "member/register".
+      'user/register'     => 'member/register',
+      // "User/$uid" should be rewritten to "user/$name" first and then further
+      // rewritten to "member/$name".
+      "user/$uid"         => "member/$name",
+      // The same logic applies to "user/$uid/edit".
+      "user/$uid/edit"    => "member/$name/edit",
+      // A path alias was set, so that should be returned.
+      "user/$uid/test1"   => 'alias/test1',
+      // The path alias was set, but it will then be altered by the second hook
+      // implementation.
+      "user/$uid/test2"   => "member/test2",
+      // The path alias was set, and will be altered by both test modules.
+      "user/$uid/test3"   => "member/$name/alias/test3",
+      // This path alias should not work, as it was set for the final path.
+      "user/$uid/test4"   => "member/$name/test4",
+    );
+    foreach ($tests as $original => $final) {
+      $this->assertUrlAlter($original, $final);
+    }
+  }
+
+  /**
+   * Assert that a path is altered to an expected value and back again.
+   *
+   * @param $original
+   *   A string with the original path that is run through url(). This should
+   *   also be the expected result after drupal_get_normal_path().
+   * @param $final
+   *   A string with the path that is run through drupal_get_normal_path().
+   *   This should also be the expected result after url().
+   * @return
+   *   TRUE if $original was correctly altered to $final and then back, FALSE
+   *   otherwise.
+   */
+  protected function assertUrlAlter($original, $final) {
+    // Test outbound altering.
+    $final_result = url($original, array('absolute' => TRUE));
+    $to_remove = $GLOBALS['base_url'] . '/' . (variable_get('clean_url', '0') ? '' : '?q=');
+    $final_result = substr($final_result, strlen($to_remove));
+    //$final_result = str_replace(url() . (variable_get('clean_url', '0') ? '' : '?q='), '', $final_result);
+    $this->assertIdentical($final, $final_result, t('Altered outbound URL %original, expected %final, and got %result.', array('%original' => $original, '%final' => $final, '%result' => $final_result)));
+
+    // Test inbound altering.
+    $original_result = drupal_get_normal_path($final);
+    $this->assertIdentical($original, $original_result, t('Altered inbound URL %final, expected %original, and got %result.', array('%final' => $final, '%original' => $original, '%result' => $original_result)));
+  }
+}
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.90
diff -u -p -r1.90 system.api.php
--- modules/system/system.api.php	15 Oct 2009 17:55:55 -0000	1.90
+++ modules/system/system.api.php	15 Oct 2009 21:42:03 -0000
@@ -2284,7 +2284,7 @@ function hook_drupal_goto_alter(&$path, 
  *   the Drupal installation process that occurs after the installation profile
  *   is selected.
  * @param $install_state
- *   An array of information about the current installation state. 
+ *   An array of information about the current installation state.
  */
 function hook_install_tasks_alter(&$tasks, $install_state) {
   // Replace the "Choose language" installation task provided by Drupal core
@@ -2459,7 +2459,7 @@ function hook_date_format_types() {
  * module can define additional types that can be used when displaying dates. A
  * date type is a key which can be passed to format_date() to return a date in
  * the configured displayed format. A date format is a string defining the date
- * and time elements to use. For example, a date type could be 
+ * and time elements to use. For example, a date type could be
  * 'mymodule_extra_long', while a date format is like 'Y-m-d'.
  *
  * New date types must first be declared using hook_date_format_types(). It is
@@ -2487,7 +2487,7 @@ function hook_date_format_types() {
  *     'short', 'mymodule_extra_long'. It must first be declared in
  *     hook_date_format_types() unless extending a type provided by another
  *     module.
- *   - 'format': a string defining the date and time elements to use. It 
+ *   - 'format': a string defining the date and time elements to use. It
  *     can contain any of the formatting options described at
  *     http://php.net/manual/en/function.date.php
  *   - 'locales': (optional) an array of 2 and 5 character language codes, for
@@ -2605,5 +2605,77 @@ function hook_page_delivery_callback_alt
 }
 
 /**
+ * Alters inbound URL requests.
+ *
+ * @param $path
+ *   The path being constructed, which, if a path alias, has been resolved to a
+ *   Drupal path by the database, and which also may have been altered by other
+ *   modules before this one.
+ * @param $original_path
+ *   The original path, before being checked for path aliases or altered by the
+ *   modules.
+ * @param $path_language
+ *   The language of the path.
+ *
+ * @see drupal_get_normal_path()
+ */
+function hook_url_inbound_alter(&$path, $original_path, $path_language) {
+  // Change all node/[title]/edit paths to node/[nid]/edit.
+  if (preg_match('|^node(/.*)/edit|', $path, $matches)) {
+    $nodes = node_load_multiple(array(), array('title' => $matches[1]));
+    if (count($nodes) === 1) {
+      $node = array_shift($nodes);
+      $path = "node/$node->nid/edit";
+    }
+  }
+
+  // Create the path user/me/edit, which allows a user to edit their account.
+  if (preg_match('|^user/me/edit(/.*)?|', $path, $matches)) {
+    global $user;
+    $path = 'user/' . $user->uid . '/edit' . $matches[1];
+  }
+}
+
+/**
+ * Alters outbound URLs.
+ *
+ * @param $path
+ *   The outbound path to alter, not adjusted for path aliases yet. It won't be
+ *   adjusted for path aliases until all modules are finished altering it, thus
+ *   being consistent with hook_url_alter_inbound(), which adjusts for all path
+ *   aliases before allowing modules to alter it. This may have been altered by
+ *   other modules before this one.
+ * @param $options
+ *   A set of URL options for the URL so elements such as a fragment or a query
+ *   string can be added to the URL.
+ *
+ * @see url()
+ */
+function hook_url_outbound_alter(&$path, &$options) {
+  // Use an external RSS feed rather than the Drupal one.
+  if ($path == 'rss.xml') {
+    $path = 'http://example.com/rss.xml';
+    $options['external'] = TRUE;
+  }
+
+  // Instead of pointing to user/[uid]/edit, point to user/me/edit.
+  if (preg_match('|^user/([0-9]*)/edit(/.*)?|', $path, $matches)) {
+    global $user;
+    if ($user->uid == $matches[1]) {
+      $path = 'user/me/edit' . $matches[2];
+    }
+  }
+
+  // Change node/[nid]/edit paths to node/[title]/edit.
+  if (preg_match('|^node([0-9]*)/edit|', $path, $matches)) {
+    $node = node_load($matches[1]);
+    $nodes = node_load_multiple(array(), array('title' => $node->title));
+    if (count($nodes) === 1) {
+      $path = "node/$node->title/edit";
+    }
+  }
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.522
diff -u -p -r1.522 taxonomy.module
--- modules/taxonomy/taxonomy.module	15 Oct 2009 14:34:07 -0000	1.522
+++ modules/taxonomy/taxonomy.module	15 Oct 2009 21:42:03 -0000
@@ -158,23 +158,6 @@ function taxonomy_theme() {
 }
 
 /**
- * For vocabularies not maintained by taxonomy.module, give the maintaining
- * module a chance to provide a path for terms in that vocabulary.
- *
- * @param $term
- *   A term object.
- * @return
- *   An internal Drupal path.
- */
-function taxonomy_term_path($term) {
-  $vocabulary = taxonomy_vocabulary_load($term->vid);
-  if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
-    return $path;
-  }
-  return 'taxonomy/term/' . $term->tid;
-}
-
-/**
  * Implement hook_menu().
  */
 function taxonomy_menu() {
@@ -1093,7 +1076,7 @@ function taxonomy_field_formatter_info()
  */
 function theme_field_formatter_taxonomy_term_link($variables) {
   $term = $variables['element']['#item']['taxonomy_term'];
-  return l($term->name, taxonomy_term_path($term));
+  return l($term->name, 'taxonomy/term/' . $term->tid, array('term' => $term));
 }
 
 /**
@@ -1494,4 +1477,3 @@ function taxonomy_taxonomy_term_delete($
 /**
  * @} End of "defgroup taxonomy indexing"
  */
-
Index: modules/taxonomy/taxonomy.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.pages.inc,v
retrieving revision 1.39
diff -u -p -r1.39 taxonomy.pages.inc
--- modules/taxonomy/taxonomy.pages.inc	15 Oct 2009 12:13:45 -0000	1.39
+++ modules/taxonomy/taxonomy.pages.inc	15 Oct 2009 21:42:03 -0000
@@ -22,7 +22,7 @@ function taxonomy_term_page($term) {
   $breadcrumb = array();
   while ($parents = taxonomy_get_parents($current->tid)) {
     $current = array_shift($parents);
-    $breadcrumb[] = l($current->name, taxonomy_term_path($current));
+    $breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid);
   }
   $breadcrumb[] = l(t('Home'), NULL);
   $breadcrumb = array_reverse($breadcrumb);
Index: modules/taxonomy/taxonomy.tokens.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.tokens.inc,v
retrieving revision 1.2
diff -u -p -r1.2 taxonomy.tokens.inc
--- modules/taxonomy/taxonomy.tokens.inc	18 Sep 2009 00:04:23 -0000	1.2
+++ modules/taxonomy/taxonomy.tokens.inc	15 Oct 2009 21:42:03 -0000
@@ -119,7 +119,7 @@ function taxonomy_tokens($type, $tokens,
           break;
 
         case 'url':
-          $replacements[$original] = url(taxonomy_term_path($term), array('absolute' => TRUE));
+          $replacements[$original] = url('taxonomy/term/' . $term, array('absolute' => TRUE));
           break;
 
         case 'node-count':
