Index: pathauto.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto.inc,v
retrieving revision 1.56.2.25
diff -u -p -r1.56.2.25 pathauto.inc
--- pathauto.inc	31 Jul 2010 18:54:01 -0000	1.56.2.25
+++ pathauto.inc	9 Aug 2010 18:12:33 -0000
@@ -169,18 +169,28 @@ function _pathauto_existing_alias_data($
 }
 
 /**
- * Clean up a string value provided by a module.
+ * Clean up a string segment to be used in an URL alias.
  *
- * Resulting string contains only alphanumerics and separators.
+ * Peforms the following possible alterations:
+ * - Process the string through the transliteration module.
+ * - Replace or remove punctuation with the separator character.
+ * - Remove back-slashes.
+ * - Replace non-ascii and non-numeric characters with the separator.
+ * - Remove common words.
+ * - Replace whitespace with the separator character.
+ * - Trim duplicate, leading, and trailing separators.
+ * - Convert to lower-case.
+ * - Shorten to a desired length and logical position based on word boundries.
+ *
+ * This function should *not* be called on URL alias or path strings because it
+ * is assumed that they are already clean.
  *
  * @param $string
  *   A string to clean.
- * @param $clean_slash
- *   Whether to clean slashes from the given string.
  * @return
  *   The cleaned string.
  */
-function pathauto_cleanstring($string, $clean_slash = TRUE) {
+function pathauto_cleanstring($string) {
   $output = $string;
 
   // Optionally transliterate (by running through the Transliteration module)
@@ -204,14 +214,13 @@ function pathauto_cleanstring($string, $
     }
   }
 
-  // If something is already urlsafe then don't remove slashes
-  if ($clean_slash) {
-    $output = str_replace('/', '', $output);
-  }
+  // Remove slashes.
+  // @todo Merge into punctuation replacement.
+  $output = str_replace('/', '', $output);
 
   // Reduce strings to letters and numbers
   if (variable_get('pathauto_reduce_ascii', FALSE)) {
-    $pattern = '/[^a-zA-Z0-9\/]+/ ';
+    $pattern = '/[^a-zA-Z0-9\/]+/';
     $output = preg_replace($pattern, $separator, $output);
   }
 
@@ -242,9 +251,14 @@ function pathauto_cleanstring($string, $
   $output = preg_replace('/\s+/', $separator, $output);
 
   // Trim duplicates and remove trailing and leading separators.
-  $output = _pathauto_clean_separators($output, $separator);
+  $output = _pathauto_clean_separators($output);
 
-  // Enforce the maximum component length
+  // Optionally convert to lower case.
+  if (variable_get('pathauto_case', PATHAUTO_CASE_LOWER)) {
+    $output = drupal_strtolower($output);
+  }
+
+  // Shorten to a logical place based on word boundries.
   $maxlength = min(variable_get('pathauto_max_component_length', 100), _pathauto_get_schema_alias_maxlength());
   $output = pathauto_truncate_utf8($output, $maxlength, TRUE);
 
@@ -252,9 +266,7 @@ function pathauto_cleanstring($string, $
 }
 
 /**
- * Clean path separators from a given string.
- *
- * Trims duplicates and strips leading and trailing separators.
+ * Trims duplicate, leading, and trailing separators from a string.
  *
  * @param $string
  *   The string to clean path separators from.
@@ -269,8 +281,12 @@ function pathauto_cleanstring($string, $
 function _pathauto_clean_separators($string, $separator = NULL) {
   $output = $string;
 
+  if (!isset($separator)) {
+    $separator = variable_get('pathauto_separator', '-');
+  }
+
   // Clean duplicate or trailing separators.
-  if (isset($separator) && strlen($separator)) {
+  if (strlen($separator)) {
     // Escape the separator.
     $seppattern = preg_quote($separator, '/');
 
@@ -278,45 +294,42 @@ function _pathauto_clean_separators($str
     $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output);
 
     // Replace trailing separators around slashes.
-    $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output);
+    if ($separator !== '/') {
+      $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output);
+    }
 
     // Replace multiple separators with a single one.
     $output = preg_replace("/$seppattern+/", $separator, $output);
   }
 
-  // Optionally convert to lower case.
-  if (variable_get('pathauto_case', PATHAUTO_CASE_LOWER)) {
-    $output = drupal_strtolower($output);
-  }
-
   return $output;
 }
 
 /**
- * Clean up an alias.
+ * Clean up an URL alias.
  *
- * This strips leading and trailing separators from the alias, and invokes
- * hook_pathauto_clean_alias() for other modules to have their chance.
+ * Peforms the following alterations:
+ * - Trim duplicate, leading, and trailing back-slashes.
+ * - Trim duplicate, leading, and trailing separators.
+ * - Shorten to a desired length and logical position based on word boundries.
  *
  * @param $alias
- *   A path alias to clean.
- * @param $clean_slash
- *   Whether to clean slashes from the alias..
+ *   A string with the URL alias to clean up.
  * @return
- *   The cleaned alias.
+ *   The cleaned URL alias.
  */
 function pathauto_clean_alias($alias) {
   $output = $alias;
-  $separator = variable_get('pathauto_separator', '-');
 
-  // Trim duplicates and remove trailing and leading separators.
-  $output = _pathauto_clean_separators($output, $separator);
+  // Trim duplicate, leading, and trailing back-slashes.
+  $output = _pathauto_clean_separators($output, '/');
 
-  // Give other modules a chance to clean this alias.
-  foreach (module_implements('pathauto_clean_alias') as $module) {
-    $function = $module .'_pathauto_clean_alias';
-    $output = $function($output);
-  }
+  // Trim duplicate, leading, and trailing separators.
+  $output = _pathauto_clean_separators($output);
+
+  // Shorten to a logical place based on word boundries.
+  $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength());
+  $output = pathauto_truncate_utf8($output, $maxlength, TRUE);
 
   return $output;
 }
@@ -402,20 +415,20 @@ function pathauto_create_alias($module, 
     return '';
   }
 
-  // Two or more slashes should be collapsed into one
-  $alias = preg_replace('/\/+/', '/', $alias);
-
-  // Trim any leading or trailing slashes
-  $alias = preg_replace('/^\/|\/+$/', '', $alias);
-
-  // Clean the alias.
   $alias = pathauto_clean_alias($alias);
 
-  // Shorten to a logical place based on the last separator.
-  // Make sure we're not ending the alias in the middle of a word.
-  $separator = variable_get('pathauto_separator', '-');
-  $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength());
-  $alias = pathauto_truncate_utf8($alias, $maxlength, TRUE);
+  // Allow other modules to alter the alias.
+  $context = array(
+    'module' => $module,
+    'op' => $op,
+    'source' => $source,
+    'data' => $data,
+    'entity_id' => $entity_id,
+    'type' => $type,
+    'language' => $language,
+    'pattern' => $pattern,
+  );
+  drupal_alter('pathauto_alias', $alias, $context);
 
   // If we have arrived at an empty string, discontinue.
   if (!drupal_strlen($alias)) {
@@ -424,12 +437,18 @@ function pathauto_create_alias($module, 
 
   // If the alias already exists, generate a new, hopefully unique, variant
   if (_pathauto_alias_exists($alias, $source, $language)) {
+    $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength());
+    $separator = variable_get('pathauto_separator', '-');
     $original_alias = $alias;
-    for ($i = 0; _pathauto_alias_exists(drupal_substr($alias, 0, $maxlength - strlen($i)) . $separator . $i, $source, $language); $i++) {
-    }
-    // Make room for the sequence number
-    $alias = drupal_substr($alias, 0, $maxlength - drupal_strlen($i));
-    $alias = $alias . $separator . $i;
+
+    $i = 0;
+    do {
+      // Append an incrementing numeric suffix until we find a unique alias.
+      $unique_suffix = $separator . $i;
+      $alias = truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix, TRUE)) . $unique_suffix;
+      $i++;
+    } while (_pathauto_alias_exists($alias, $source, $language));
+
     // If verbose is on, alert the user why this happened
     if ($verbose) {
       drupal_set_message(t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.',
@@ -590,29 +609,27 @@ function _pathauto_set_alias($path, $exi
 function pathauto_get_placeholders($type, $object) {
   $full = token_get_values($type, $object, TRUE);
   $tokens = token_prepare_tokens($full->tokens);
-  $values = pathauto_clean_token_values($full);
-  return array('tokens' => $tokens, 'values' => $values);
+  $replacements = array();
+  foreach ($tokens as $index => $token) {
+    $replacements[$token] = $full->values[$index];
+  }
+  pathauto_clean_token_values($replacements);
+  return array('tokens' => array_keys($replacements), 'values' => array_values($replacements));
 }
 
 /**
  * Clean tokens so they are URL friendly.
  *
- * @param $full
- *   An array of token values that need to be "cleaned" for use in the URL.
- * @return
- *   An array of the cleaned tokens.
+ * @param $replacements
+ *   An array of token replacements that need to be "cleaned" for use in the URL.
  */
-function pathauto_clean_token_values($full) {
-  foreach ($full->values as $key => $value) {
-    // If it's a "path" or "url friendly" token don't remove the "/" character
-    if (drupal_substr($full->tokens[$key], -4, 4) === 'path' || drupal_substr($full->tokens[$key], -8, 8) === 'path-raw' || drupal_substr($full->tokens[$key], -5, 5) === 'alias') {
-      $full->values[$key] = pathauto_cleanstring($value, FALSE);
-    }
-    else {
-      $full->values[$key] = pathauto_cleanstring($value);
+function pathauto_clean_token_values(&$replacements) {
+  foreach ($replacements as $token => $value) {
+    // Only clean non-path tokens.
+    if (!preg_match('/(path|alias|url|url-brief)(-raw)?\]$/', $token)) {
+      $replacements[$token] = pathauto_cleanstring($value);
     }
   }
-  return $full->values;
 }
 
 /**
Index: pathauto.pathauto.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto.pathauto.inc,v
retrieving revision 1.1.2.5
diff -u -p -r1.1.2.5 pathauto.pathauto.inc
--- pathauto.pathauto.inc	29 Jul 2010 21:44:28 -0000	1.1.2.5
+++ pathauto.pathauto.inc	9 Aug 2010 18:12:33 -0000
@@ -103,15 +103,14 @@ function _taxonomy_pathauto($op) {
       $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
 
       $vocabularies = taxonomy_get_vocabularies();
-      if (sizeof($vocabularies) > 0) {
+      if (count($vocabularies)) {
         $settings['patternitems'] = array();
-        $forum_vid = variable_get('forum_nav_vocabulary', '');
-        foreach ($vocabularies as $vocab) {
-          if ($vocab->vid != $forum_vid) {
-            $vocabname = $vocab->name;
-            $fieldlabel = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabname));
-            $settings['patternitems'][$vocab->vid] = $fieldlabel;
+        foreach ($vocabularies as $vid => $vocabulary) {
+          if ($vid == variable_get('forum_nav_vocabulary', '')) {
+            // Skip the forum vocabulary.
+            continue;
           }
+          $settings['patternitems'][$vid] = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabulary->name));
         }
       }
       return (object) $settings;
@@ -162,7 +161,7 @@ function taxonomy_pathauto_bulk_update_b
 }
 
 /**
- * Implementation of hook_pathauto().
+ * Implements hook_pathauto() for forum module.
  */
 function _forum_pathauto($op) {
   switch ($op) {
Index: pathauto.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto.test,v
retrieving revision 1.1.4.26
diff -u -p -r1.1.4.26 pathauto.test
--- pathauto.test	31 Jul 2010 18:54:01 -0000	1.1.4.26
+++ pathauto.test	9 Aug 2010 18:12:34 -0000
@@ -30,7 +30,13 @@ class PathautoTestHelper extends DrupalW
     if (isset($args[0]) && is_array($args[0])) {
       $modules = array_merge($modules, $args[0]);
     }
-    call_user_func_array(array('parent', 'setUp'), $modules);
+
+    if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+      call_user_func_array(array($this, 'parent::setUp'), $modules);
+    }
+    else {
+      call_user_func_array('parent::setUp', $modules);
+    }
 
     // Clear some static caches.
     pathauto_pattern_load_by_entity('', '', '', TRUE);
@@ -78,9 +84,9 @@ class PathautoTestHelper extends DrupalW
   }
 
   function assertAliasExists($conditions) {
-    $alias = $this->path_load($conditions);
-    $this->assertTrue($alias, t('Alias with conditions @conditions found.', array('@conditions' => var_export($conditions, TRUE))));
-    return $alias;
+    $path = $this->path_load($conditions);
+    $this->assertTrue($path, t('Alias with conditions @conditions found.', array('@conditions' => var_export($conditions, TRUE))));
+    return $path;
   }
 
   function assertNoAliasExists($conditions) {
@@ -157,6 +163,24 @@ class PathautoTestHelper extends DrupalW
     db_query("DELETE FROM {url_alias}");
     drupal_clear_path_cache();
   }
+
+  function addVocabulary(array $vocabulary = array()) {
+    $vocabulary += array(
+      'name' => drupal_strtolower($this->randomName(5)),
+      'nodes' => array('story' => 'story'),
+    );
+    taxonomy_save_vocabulary($vocabulary);
+    return (object) $vocabulary;
+  }
+
+  function addTerm(stdClass $vocabulary, array $term = array()) {
+    $term += array(
+      'name' => drupal_strtolower($this->randomName(5)),
+      'vid' => $vocabulary->vid,
+    );
+    taxonomy_save_term($term);
+    return (object) $term;
+  }
 }
 
 /**
@@ -341,6 +365,14 @@ class PathautoUnitTestCase extends Patha
     pathauto_nodeapi($node, 'update');
     $this->assertEntityAlias('node', $node, 'content/hello');
   }
+
+  /**
+   * Test the handling of path vs non-path tokens in pathauto_clean_token_values().
+   *
+   * @see PathautoBookTokenTestCase::testBookPathAlias()
+   */
+  //function testPathTokens() {
+  //}
 }
 
 /**
@@ -444,7 +476,7 @@ class PathautoFunctionalTestCase extends
     $node2 = $this->drupalCreateNode(array('title' => 'node2'));
 
     // Delete all current URL aliases.
-    db_query("DELETE FROM {url_alias}");
+    $this->deleteAllAliases();
 
     $edit = array(
       'operation' => 'pathauto_update_alias',
@@ -464,7 +496,7 @@ class PathautoFunctionalTestCase extends
     $account = $this->drupalCreateUser();
 
     // Delete all current URL aliases.
-    db_query("DELETE FROM {url_alias}");
+    $this->deleteAllAliases();
 
     $edit = array(
       'operation' => 'pathauto_update_alias',
@@ -535,7 +567,7 @@ class PathautoLocaleTestCase extends Pat
   function setUp() {
     parent::setUp(array('locale', 'translation'), array('administer languages'));
 
-    // Add predefined language and reset the locale cache.
+    // Add predefined French language and reset the locale cache.
     require_once './includes/locale.inc';
     locale_add_language('fr', NULL, NULL, LANGUAGE_LTR, '', 'fr');
     language_list('language', TRUE);
@@ -664,6 +696,11 @@ class PathautoBookTokenTestCase extends 
     // Add a child page on an existing child page.
     $sub_child_node1 = $this->drupalCreateNode(array('type' => 'book', 'title' => 'Sub-sub Page1', 'book' => array('bid' => $parent_node->book['bid'], 'plid' => $child_node1->book['mlid'])));
     $this->assertToken('node', $sub_child_node1, 'bookpathalias', 'root/sub-page1');
+
+    // Test that path tokens should not be altered.
+    $this->saveEntityAlias('node', $child_node1, 'My Crazy/Alias/');
+    pathauto_nodeapi($sub_child_node1, 'update');
+    $this->assertEntityAlias('node', $sub_child_node1, 'My Crazy/Alias/sub-sub-page1');
   }
 }
 
@@ -734,22 +771,4 @@ class PathautoTaxonomyTokenTestCase exte
     $this->assertToken('node', $non_term_node, 'termpath', '');
     $this->assertToken('node', $non_term_node, 'termalias', '');
   }
-
-  function addVocabulary(array $vocabulary = array()) {
-    $vocabulary += array(
-      'name' => drupal_strtolower($this->randomName(5)),
-      'nodes' => array('story' => 'story'),
-    );
-    taxonomy_save_vocabulary($vocabulary);
-    return (object) $vocabulary;
-  }
-
-  function addTerm(stdClass $vocabulary, array $term = array()) {
-    $term += array(
-      'name' => drupal_strtolower($this->randomName(5)),
-      'vid' => $vocabulary->vid,
-    );
-    taxonomy_save_term($term);
-    return (object) $term;
-  }
 }
