Browse Source

updated pathauto token

Bachir Soussi Chiadmi 5 years ago
parent
commit
4e0d4316d6

+ 0 - 149
sites/all/modules/pathauto/API.txt

@@ -1,149 +0,0 @@
-This document explains how to provide "Pathauto integration" in a
-module. You need this if you would like to provide additional tokens
-or if your module has paths and you wish to have them automatically
-aliased.  The simplest integration is just to provide tokens so we
-cover that first.  More advanced integration requires an
-implementation of hook_pathauto to provide a settings form.
-
-It may be helpful to review some examples of integration from the
-pathauto_node.inc, pathauto_taxonomy.inc, and pathauto_user.inc files.
-
-
-==================
-1 - Providing additional tokens
-==================
-
-If all you want is to enable tokens for your module you will simply
-need to implement two functions:
-
-  hook_token_values
-  hook_token_list
-
-See the token.module and it's API.txt for more information about this
-process.
-
-If the token is intended to generate a path expected to contain slashes,
-the token name must end in 'path', 'path-raw' or 'alias'. This indicates to
-Pathauto that the slashes should not be removed from the replacement value.
-
-When an object is created (whether it is a node or a user or a
-taxonomy term) the data that Pathauto hands to the token_values in the
-$object is in a specific format. This is the format that most people
-write code to handle. However, during edits and bulk updates the data
-may be in a totally different format. So, if you are writing a
-hook_token_values implementation to add special tokens, be sure to
-test creation, edit, and bulk update cases to make sure your code will
-handle it.
-
-==================
-2 - Settings hook - To create aliases for your module
-==================
-You must implement hook_pathauto($op), where $op is always (at this
-time) 'settings'. Return an object (NOT an array) containing the
-following members, which will be used by pathauto to build a group
-of settings for your module and define the variables for saving your
-settings:
-
-module - The name of your module (e.g., 'node')
-groupheader - The translated label for the settings group (e.g.,
-  t('Content path settings')
-patterndescr - The translated label for the default pattern (e.g.,
-  t('Default path pattern (applies to all content types with blank patterns below)')
-patterndefault - A translated default pattern (e.g., t('[cat]/[title].html'))
-token_type - The token type (e.g. 'node', 'user') that can be used.
-patternitems - For modules which need to express multiple patterns
-  (for example, the node module supports a separate pattern for each
-  content type), an array whose keys consist of identifiers for each
-  pattern (e.g., the content type name) and values consist of the
-  translated label for the pattern
-bulkname - For modules which support a bulk update operation, the
-  translated label for the action (e.g., t('Bulk update content paths'))
-bulkdescr - For modules which support a bulk update operation, a
-  translated, more thorough description of what the operation will do
-  (e.g., t('Generate aliases for all existing content items which do not already have aliases.'))
-
-
-==================
-2 - $alias = pathauto_create_alias($module, $op, $placeholders, $src, $type=NULL)
-==================
-
-At the appropriate time (usually when a new item is being created for
-which a generated alias is desired), call pathauto_create_alias() to
-generate and create the alias.  See the user, taxonomy, and nodeapi hook
-implementations in pathauto.module for examples.
-
-$module - The name of your module (e.g., 'node')
-$op - Operation being performed on the item ('insert', 'update', or
-  'bulkupdate')
-$placeholders - An array whose keys consist of the translated placeholders
-  which appear in patterns and values are the "clean" values to be
-  substituted into the pattern. Call pathauto_cleanstring() on any
-  values which you do not know to be purely alphanumeric, to substitute
-  any non-alphanumerics with the user's designated separator. Note that
-  if the pattern has multiple slash-separated components (e.g., [term:path]),
-  pathauto_cleanstring() should be called for each component, not the
-  complete string.
-  Example: $placeholders[t('[title]')] = pathauto_cleanstring($node->title);
-$src - The "real" URI of the content to be aliased (e.g., "node/$node->nid")
-$type - For modules which provided patternitems in hook_autopath(),
-  the relevant identifier for the specific item to be aliased (e.g.,
-  $node->type)
-
-pathauto_create_alias() returns the alias that was created.
-
-
-==================
-3 - Bulk update function
-==================
-
-If a module supports bulk updating of aliases, it must provide a
-function of this form, to be called by pathauto when the corresponding
-checkbox is selected and the settings page submitted:
-
-function <module>_pathauto_bulkupdate()
-
-The function should iterate over the content items controlled by the
-module, calling pathauto_create_alias() for each one. It is
-recommended that the function report on its success (e.g., with a
-count of created aliases) via drupal_set_message().
-
-
-==================
-4 - Bulk delete hook_path_alias_types()
-==================
-
-For modules that create new types of pages that can be aliased with pathauto, a
-hook implementation is needed to allow the user to delete them all at once.
-
-function hook_path_alias_types()
-
-This hook returns an array whose keys match the beginning of the source paths
-(e.g.: "node/", "user/", etc.) and whose values describe the type of page (e.g.:
-"content", "users"). Like all displayed strings, these descriptionsshould be
-localized with t(). Use % to match interior pieces of a path; "user/%/track". This
-is a database wildcard, so be careful.
-
-
-==================
-Modules that extend node and/or taxonomy
-==================
-
-NOTE: this is basically not true any more.  If you feel you need this file an issue.
-
-Many contributed Drupal modules extend the core node and taxonomy
-modules. To extend pathauto patterns to support their extensions, they
-may implement the pathauto_node and pathauto_taxonomy hooks.
-
-To do so, implement the function <modulename>_pathauto_node (or _taxonomy),
-accepting the arguments $op and $node (or $term). Two operations are
-supported:
-
-$op = 'placeholders' - return an array keyed on placeholder strings
-(e.g., t('[eventyyyy]')) valued with descriptions (e.g. t('The year the
-event starts.')).
-$op = 'values' - return an array keyed on placeholder strings, valued
-with the "clean" actual value for the passed node or category (e.g.,
-pathauto_cleanstring(date('M', $eventstart)));
-
-See contrib/pathauto_node_event.inc for an example of extending node
-patterns.

+ 32 - 45
sites/all/modules/pathauto/README.txt

@@ -1,48 +1,49 @@
-Please read this file and also the INSTALL.txt.  
+Please read this file and also the INSTALL.txt.
 They contain answers to many common questions.
 If you are developing for this module, the API.txt may be interesting.
 If you are upgrading, check the CHANGELOG.txt for major changes.
 
-**Description:
-The Pathauto module provides support functions for other modules to 
-automatically generate aliases based on appropriate criteria, with a 
+** Description:
+The Pathauto module provides support functions for other modules to
+automatically generate aliases based on appropriate criteria, with a
 central settings path for site administrators.
 
 Implementations are provided for core entity types: content, taxonomy terms,
-and users (including blogs and tracker pages).
+and users (including blogs and forum pages).
 
-Pathauto also provides a way to delete large numbers of aliases.  This feature 
-is available at  Administer > Site building > URL aliases > Delete aliases
+Pathauto also provides a way to delete large numbers of aliases.  This feature
+is available at  Administer > Configuration > Search and metadata > URL aliases
+> Delete aliases.
 
-**Benefits:
+** Benefits:
 Besides making the page address more reflective of its content than
-"node/138", it's important to know that modern search engines give 
-heavy weight to search terms which appear in a page's URL. By 
-automatically using keywords based directly on the page content in the URL, 
+"node/138", it's important to know that modern search engines give
+heavy weight to search terms which appear in a page's URL. By
+automatically using keywords based directly on the page content in the URL,
 relevant search engine hits for your page can be significantly
 enhanced.
 
-**Installation AND Upgrades:
+** Installation AND Upgrades:
 See the INSTALL.txt file.
 
-**Notices:
+** Notices:
 Pathauto just adds URL aliases to content, users, and taxonomy terms.
-Because it's an alias, the standard Drupal URL (for example node/123 or 
-taxonomy/term/1) will still function as normal.  If you have external links 
-to your site pointing to standard Drupal URLs, or hardcoded links in a module, 
+Because it's an alias, the standard Drupal URL (for example node/123 or
+taxonomy/term/1) will still function as normal.  If you have external links
+to your site pointing to standard Drupal URLs, or hardcoded links in a module,
 template, content or menu which point to standard Drupal URLs it will bypass
 the alias set by Pathauto.
 
-There are reasons you might not want two URLs for the same content on your 
-site. If this applies to you, please note that you will need to update any 
-hard coded links in your content or blocks. 
+There are reasons you might not want two URLs for the same content on your
+site. If this applies to you, please note that you will need to update any
+hard coded links in your content or blocks.
 
 If you use the "system path" (i.e. node/10) for menu items and settings like
 that, Drupal will replace it with the url_alias.
 
-For external links, you might want to consider the Path Redirect or 
-Global Redirect modules, which allow you to set forwarding either per item or 
-across the site to your aliased URLs. 
+For external links, you might want to consider the Path Redirect or
+Global Redirect modules, which allow you to set forwarding either per item or
+across the site to your aliased URLs.
 
 URLs (not) Getting Replaced With Aliases:
 Please bear in mind that only URLs passed through Drupal's l() or url()
@@ -54,41 +55,27 @@ Drupal API instead:
 * 'href="'. url("node/$node->nid") .'"' or
 * l("Your link title", "node/$node->nid")
 
-See http://api.drupal.org/api/HEAD/function/url and 
+See http://api.drupal.org/api/HEAD/function/url and
 http://api.drupal.org/api/HEAD/function/l for more information.
 
 ** Disabling Pathauto for a specific content type (or taxonomy)
-When the pattern for a content type is left blank, the default pattern will be 
-used. But if the default pattern is also blank, Pathauto will be disabled 
+When the pattern for a content type is left blank, the default pattern will be
+used. But if the default pattern is also blank, Pathauto will be disabled
 for that content type.
 
-** Bulk Updates Must be Run Multiple Times:
-As of 5.x-2.x Pathauto now performs bulk updates in a manner which is more 
-likely to succeed on large sites.  The drawback is that it needs to be run 
-multiple times.  If you want to reduce the number of times that you need to 
-run Pathauto you can increase the "Maximum number of objects to alias in a 
-bulk update:" setting under General Settings.
-
-**WYSIWYG Conflicts - FCKEditor, TinyMCE, etc.
-If you use a WYSIWYG editor, please disable it for the Pathauto admin page.  
-Failure to do so may cause errors about "preg_replace" problems due to the <p>
-tag being added to the "strings to replace".  See http://drupal.org/node/175772
-
-**Credits:
+** Credits:
 The original module combined the functionality of Mike Ryan's autopath with
 Tommy Sundstrom's path_automatic.
 
 Significant enhancements were contributed by jdmquin @ www.bcdems.net.
 
-Matt England added the tracker support.
+Matt England added the tracker support (tracker support has been removed in
+recent changes).
 
 Other suggestions and patches contributed by the Drupal community.
 
-Current maintainers: 
-  Greg Knaddison - http://growingventuresolutions.com
+Current maintainers:
+  Dave Reid - http://www.davereid.net
+  Greg Knaddison - http://www.knaddison.com
   Mike Ryan - http://mikeryan.name
   Frederik 'Freso' S. Olesen - http://freso.dk
-
-**Changes:
-See the CHANGELOG.txt file.
-

+ 4 - 10
sites/all/modules/pathauto/pathauto.admin.inc

@@ -64,17 +64,11 @@ function pathauto_patterns_form($form, $form_state) {
       }
     }
 
-    // Display the user documentation of placeholders supported by
-    // this module, as a description on the last pattern
+    // Show the token help relevant to this pattern type.
     $form[$module]['token_help'] = array(
-      '#title' => t('Replacement patterns'),
-      '#type' => 'fieldset',
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-    $form[$module]['token_help']['help'] = array(
       '#theme' => 'token_tree',
       '#token_types' => array($settings->token_type),
+      '#dialog' => TRUE,
     );
   }
 
@@ -144,7 +138,7 @@ function pathauto_settings_form($form) {
 
   $description = t('What should Pathauto do when updating an existing content item which already has an alias?');
   if (module_exists('redirect')) {
-    $description .= ' ' . t('The <a href="!url">Redirect module settings</a> affect whether a redirect is created when an alias is deleted.', array('!url' => url('admin/config/search/redirect')));
+    $description .= ' ' . t('The <a href="!url">Redirect module settings</a> affect whether a redirect is created when an alias is deleted.', array('!url' => url('admin/config/search/redirect/settings')));
   }
   else {
     $description .= ' ' . t('Considering installing the <a href="!url">Redirect module</a> to get redirects when your aliases change.', array('!url' => 'http://drupal.org/project/redirect'));
@@ -165,7 +159,7 @@ function pathauto_settings_form($form) {
     '#type' => 'checkbox',
     '#title' => t('Transliterate prior to creating alias'),
     '#default_value' => variable_get('pathauto_transliterate', FALSE) && module_exists('transliteration'),
-    '#description' => t('When a pattern includes certain characters (such as those with accents) should Pathauto attempt to transliterate them into the ASCII-96 alphabet? Transliteration is handled by the Transliteration module.'),
+    '#description' => t('When a pattern includes certain characters (such as those with accents) should Pathauto attempt to transliterate them into the US-ASCII alphabet? Transliteration is handled by the Transliteration module.'),
     '#access' => module_exists('transliteration'),
   );
 

+ 157 - 3
sites/all/modules/pathauto/pathauto.api.php

@@ -1,17 +1,171 @@
 <?php
-
 /**
  * @file
  * Documentation for pathauto API.
  *
- * @see hook_token_info
- * @see hook_tokens
+ * It may be helpful to review some examples of integration from
+ * pathauto.pathauto.inc.
+ *
+ * Pathauto works by using tokens in path patterns.  Thus the simplest
+ * integration is just to provide tokens.  Token support is provided by Drupal
+ * core. To provide additional token from your module, implement the following
+ * hooks:
+ *
+ * hook_tokens() - http://api.drupal.org/api/function/hook_tokens
+ * hook_token_info() - http://api.drupal.org/api/function/hook_token_info
+ *
+ * If you wish to provide pathauto integration for custom paths provided by your
+ * module, there are a few steps involved.
+ *
+ * 1. hook_pathauto()
+ *    Provide information required by pathauto for the settings form as well as
+ *    bulk generation.  See the documentation for hook_pathauto() for more
+ *    details.
+ *
+ * 2. pathauto_create_alias()
+ *    At the appropriate time (usually when a new item is being created for
+ *    which a generated alias is desired), call pathauto_create_alias() with the
+ *    appropriate parameters to generate and create the alias. See the user,
+ *    taxonomy, and node hook implementations in pathauto.module for examples.
+ *    Also see the documentation for pathauto_create_alias().
+ *
+ * 3. pathauto_path_delete_all()
+ *    At the appropriate time (usually when an item is being deleted), call
+ *    pathauto_path_delete_all() to remove any aliases that were created for the
+ *    content being removed.  See the documentation for
+ *    pathauto_path_delete_all() for more details.
+ *
+ * 4. hook_path_alias_types()
+ *    For modules that create new types of content that can be aliased with
+ *    pathauto, a hook implementation is needed to allow the user to delete them
+ *    all at once.  See the documentation for hook_path_alias_types() below for
+ *    more information.
+ *
+ * There are other integration points with pathauto, namely alter hooks that
+ * allow you to change the data used by pathauto at various points in the
+ * process.  See the below hook documentation for details.
  */
 
+/**
+ * Used primarily by the bulk delete form.  This hooks provides pathauto the
+ * information needed to bulk delete aliases created by your module.  The keys
+ * of the return array are used by pathauto as the system path prefix to delete
+ * from the url_aliases table.  The corresponding value is simply used as the
+ * label for each type of path on the bulk delete form.
+ *
+ * @return
+ *   An array whose keys match the beginning of the source paths
+ *   (e.g.: "node/", "user/", etc.) and whose values describe the type of page
+ *   (e.g.: "Content", "Users"). Like all displayed strings, these descriptions
+ *   should be localized with t(). Use % to match interior pieces of a path,
+ *   like "user/%/track". This is a database wildcard (meaning "user/%/track"
+ *   matches "user/1/track" as well as "user/1/view/track").
+ */
 function hook_path_alias_types() {
+  $objects['user/'] = t('Users');
+  $objects['node/'] = t('Content');
+  return $objects;
 }
 
+/**
+ * Provide information about the way your module's aliases will be built.
+ *
+ * The information you provide here is used to build the form
+ * on search/path/patterns. File pathauto.pathauto.inc provides example
+ * implementations for system modules.
+ *
+ * @see node_pathauto
+ *
+ * @param $op
+ *   At the moment this will always be 'settings'.
+ *
+ * @return object|null
+ *   An object, or array of objects (if providing multiple groups of path
+ *   patterns).  Each object should have the following members:
+ *   - 'module': The module or entity type.
+ *   - 'token_type': Which token type should be allowed in the patterns form.
+ *   - 'groupheader': Translated label for the settings group
+ *   - 'patterndescr': The translated label for the default pattern (e.g.,
+ *      t('Default path pattern (applies to all content types with blank
+ *      patterns below)')
+ *   - 'patterndefault': Default pattern  (e.g. 'content/[node:title]'
+ *   - 'batch_update_callback': The name of function that should be ran for
+ *      bulk update. @see node_pathauto_bulk_update_batch_process for example
+ *   - 'batch_file': The name of the file with the bulk update function.
+ *   - 'patternitems': Optional. An array of descritpions keyed by bundles.
+ */
 function hook_pathauto($op) {
+  switch ($op) {
+    case 'settings':
+      $settings = array();
+      $settings['module'] = 'file';
+      $settings['token_type'] = 'file';
+      $settings['groupheader'] = t('File paths');
+      $settings['patterndescr'] = t('Default path pattern (applies to all file types with blank patterns below)');
+      $settings['patterndefault'] = 'files/[file:name]';
+      $settings['batch_update_callback'] = 'file_entity_pathauto_bulk_update_batch_process';
+      $settings['batch_file'] = drupal_get_path('module', 'file_entity') . '/file_entity.pathauto.inc';
+
+      foreach (file_type_get_enabled_types() as $file_type => $type) {
+        $settings['patternitems'][$file_type] = t('Pattern for all @file_type paths.', array('@file_type' => $type->label));
+      }
+      return (object) $settings;
+
+    default:
+      break;
+  }
+}
+
+/**
+ * Determine if a possible URL alias would conflict with any existing paths.
+ *
+ * Returning TRUE from this function will trigger pathauto_alias_uniquify() to
+ * generate a similar URL alias with a suffix to avoid conflicts.
+ *
+ * @param string $alias
+ *   The potential URL alias.
+ * @param string $source
+ *   The source path for the alias (e.g. 'node/1').
+ * @param string $langcode
+ *   The language code for the alias (e.g. 'en').
+ *
+ * @return bool
+ *   TRUE if $alias conflicts with an existing, reserved path, or FALSE/NULL if
+ *   it does not match any reserved paths.
+ *
+ * @see pathauto_alias_uniquify()
+ */
+function hook_pathauto_is_alias_reserved($alias, $source, $langcode) {
+  // Check our module's list of paths and return TRUE if $alias matches any of
+  // them.
+  return (bool) db_query("SELECT 1 FROM {mytable} WHERE path = :path", array(':path' => $alias))->fetchField();
+}
+
+/**
+ * Alter the pattern to be used before an alias is generated by Pathauto.
+ *
+ * This hook will only be called if a default pattern is configured (on
+ * admin/config/search/path/patterns).
+ *
+ * @param string $pattern
+ *   The alias pattern for Pathauto to pass to token_replace() to generate the
+ *   URL alias.
+ * @param array $context
+ *   An associative array of additional options, with the following elements:
+ *   - 'module': The module or entity type being aliased.
+ *   - 'op': A string with the operation being performed on the object being
+ *     aliased. Can be either 'insert', 'update', 'return', or 'bulkupdate'.
+ *   - 'source': A string of the source path for the alias (e.g. 'node/1').
+ *   - 'data': An array of keyed objects to pass to token_replace().
+ *   - 'type': The sub-type or bundle of the object being aliased.
+ *   - 'language': A string of the language code for the alias (e.g. 'en').
+ *     This can be altered by reference.
+ */
+function hook_pathauto_pattern_alter(&$pattern, array $context) {
+  // Switch out any [node:created:*] tokens with [node:updated:*] on update.
+  if ($context['module'] == 'node' && ($context['op'] == 'update')) {
+    $pattern = preg_replace('/\[node:created(\:[^]]*)?\]/', '[node:updated$1]', $pattern);
+  }
 }
 
 /**

+ 88 - 70
sites/all/modules/pathauto/pathauto.inc

@@ -59,30 +59,27 @@ define('PATHAUTO_PUNCTUATION_DO_NOTHING', 2);
  *   A string alias.
  * @param $source
  *   A string that is the internal path.
- * @param $language
+ * @param $langcode
  *   A string indicating the path's language.
- * @return
+ *
+ * @return bool
  *   TRUE if an alias exists, FALSE if not.
+ *
+ * @deprecated Use path_pathauto_is_alias_reserved() instead.
  */
-function _pathauto_alias_exists($alias, $source, $language = LANGUAGE_NONE) {
-  $pid = db_query_range("SELECT pid FROM {url_alias} WHERE source <> :source AND alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", 0, 1, array(
-    ':source' => $source,
-    ':alias' => $alias,
-    ':language' => $language,
-    ':language_none' => LANGUAGE_NONE,
-  ))->fetchField();
-
-  return !empty($pid);
+function _pathauto_alias_exists($alias, $source, $langcode = LANGUAGE_NONE) {
+  return path_pathauto_is_alias_reserved($alias, $source, $langcode);
 }
 
 /**
  * Fetches an existing URL alias given a path and optional language.
  *
- * @param $source
+ * @param string $source
  *   An internal Drupal path.
- * @param $language
+ * @param string $language
  *   An optional language code to look up the path in.
- * @return
+ *
+ * @return bool|array
  *   FALSE if no alias was found or an associative array containing the
  *   following keys:
  *   - pid: Unique path alias identifier.
@@ -111,12 +108,17 @@ function _pathauto_existing_alias_data($source, $language = LANGUAGE_NONE) {
  * This function should *not* be called on URL alias or path strings because it
  * is assumed that they are already clean.
  *
- * @param $string
+ * @param string $string
  *   A string to clean.
- * @return
+ * @param array $options
+ *   (optional) A keyed array of settings and flags to control the Pathauto
+ *   clean string replacement process. Supported options are:
+ *   - langcode: A language code to be used when translating strings.
+ *
+ * @return string
  *   The cleaned string.
  */
-function pathauto_cleanstring($string) {
+function pathauto_cleanstring($string, array $options = array()) {
   // Use the advanced drupal_static() pattern, since this is called very often.
   static $drupal_static_fast;
   if (!isset($drupal_static_fast)) {
@@ -161,6 +163,7 @@ function pathauto_cleanstring($string) {
     if ($ignore_words_regex) {
       $cache['ignore_words_regex'] = '\b' . $ignore_words_regex . '\b';
       if (function_exists('mb_eregi_replace')) {
+        mb_regex_encoding('UTF-8');
         $cache['ignore_words_callback'] = 'mb_eregi_replace';
       }
       else {
@@ -170,15 +173,23 @@ function pathauto_cleanstring($string) {
     }
   }
 
-  // Empty strings do not need any proccessing.
+  // Empty strings do not need any processing.
   if ($string === '' || $string === NULL) {
     return '';
   }
 
+  $langcode = NULL;
+  if (!empty($options['language']->language)) {
+    $langcode = $options['language']->language;
+  }
+  elseif (!empty($options['langcode'])) {
+    $langcode = $options['langcode'];
+  }
+
   // Check if the string has already been processed, and if so return the
   // cached result.
-  if (isset($cache['strings'][$string])) {
-    return $cache['strings'][$string];
+  if (isset($cache['strings'][$langcode][$string])) {
+    return $cache['strings'][$langcode][$string];
   }
 
   // Remove all HTML tags from the string.
@@ -186,7 +197,10 @@ function pathauto_cleanstring($string) {
 
   // Optionally transliterate (by running through the Transliteration module)
   if ($cache['transliterate']) {
-    $output = transliteration_get($output);
+    // If the reduce strings to letters and numbers is enabled, don't bother
+    // replacing unknown characters with a question mark. Use an empty string
+    // instead.
+    $output = transliteration_get($output, $cache['reduce_ascii'] ? '' : '?', $langcode);
   }
 
   // Replace or drop punctuation based on user settings
@@ -220,7 +234,7 @@ function pathauto_cleanstring($string) {
   $output = truncate_utf8($output, $cache['maxlength'], TRUE);
 
   // Cache this result in the static array.
-  $cache['strings'][$string] = $output;
+  $cache['strings'][$langcode][$string] = $output;
 
   return $output;
 }
@@ -228,11 +242,12 @@ function pathauto_cleanstring($string) {
 /**
  * Trims duplicate, leading, and trailing separators from a string.
  *
- * @param $string
+ * @param string $string
  *   The string to clean path separators from.
- * @param $separator
+ * @param string $separator
  *   The path separator to use when cleaning.
- * @return
+ *
+ * @return string
  *   The cleaned version of the string.
  *
  * @see pathauto_cleanstring()
@@ -250,21 +265,20 @@ function _pathauto_clean_separators($string, $separator = NULL) {
 
   $output = $string;
 
-  // Clean duplicate or trailing separators.
   if (strlen($separator)) {
-    // Escape the separator.
+    // Trim any leading or trailing separators.
+    $output = trim($output, $separator);
+
+    // Escape the separator for use in regular expressions.
     $seppattern = preg_quote($separator, '/');
 
-    // Trim any leading or trailing separators.
-    $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output);
+    // Replace multiple separators with a single one.
+    $output = preg_replace("/$seppattern+/", $separator, $output);
 
     // Replace trailing separators around slashes.
     if ($separator !== '/') {
-      $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output);
+      $output = preg_replace("/\/+$seppattern\/+|$seppattern\/+|\/+$seppattern/", "/", $output);
     }
-
-    // Replace multiple separators with a single one.
-    $output = preg_replace("/$seppattern+/", $separator, $output);
   }
 
   return $output;
@@ -278,9 +292,10 @@ function _pathauto_clean_separators($string, $separator = NULL) {
  * - Trim duplicate, leading, and trailing separators.
  * - Shorten to a desired length and logical position based on word boundaries.
  *
- * @param $alias
+ * @param string $alias
  *   A string with the URL alias to clean up.
- * @return
+ *
+ * @return string
  *   The cleaned URL alias.
  */
 function pathauto_clean_alias($alias) {
@@ -294,12 +309,15 @@ function pathauto_clean_alias($alias) {
 
   $output = $alias;
 
-  // Trim duplicate, leading, and trailing back-slashes.
-  $output = _pathauto_clean_separators($output, '/');
-
-  // Trim duplicate, leading, and trailing separators.
+  // Trim duplicate, leading, and trailing separators. Do this before cleaning
+  // backslashes since a pattern like "[token1]/[token2]-[token3]/[token4]"
+  // could end up like "value1/-/value2" and if backslashes were cleaned first
+  // this would result in a duplicate blackslash.
   $output = _pathauto_clean_separators($output);
 
+  // Trim duplicate, leading, and trailing backslashes.
+  $output = _pathauto_clean_separators($output, '/');
+
   // Shorten to a logical place based on word boundaries.
   $output = truncate_utf8($output, $cache['maxlength'], TRUE);
 
@@ -328,8 +346,10 @@ function pathauto_clean_alias($alias) {
  *   (e.g., $node->type).
  * @param $language
  *   A string specify the path's language.
- * @return
- *   The alias that was created.
+ *
+ * @return array|null|false
+ *   The alias array that was created, NULL if an empty alias was generated, or
+ *   FALSE if the alias generation was not possible.
  *
  * @see _pathauto_set_alias()
  * @see token_replace()
@@ -337,20 +357,32 @@ function pathauto_clean_alias($alias) {
 function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $language = LANGUAGE_NONE) {
   // Retrieve and apply the pattern for this content type.
   $pattern = pathauto_pattern_load_by_entity($module, $type, $language);
+
+  // Allow other modules to alter the pattern.
+  $context = array(
+    'module' => $module,
+    'op' => $op,
+    'source' => $source,
+    'data' => $data,
+    'type' => $type,
+    'language' => &$language,
+  );
+  drupal_alter('pathauto_pattern', $pattern, $context);
+
   if (empty($pattern)) {
     // No pattern? Do nothing (otherwise we may blow away existing aliases...)
-    return '';
+    return FALSE;
   }
 
   // Special handling when updating an item which is already aliased.
   $existing_alias = NULL;
-  if ($op == 'update' || $op == 'bulkupdate') {
+  if ($op != 'insert') {
     if ($existing_alias = _pathauto_existing_alias_data($source, $language)) {
       switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
         case PATHAUTO_UPDATE_ACTION_NO_NEW:
           // If an alias already exists, and the update action is set to do nothing,
           // then gosh-darn it, do nothing.
-          return '';
+          return FALSE;
       }
     }
   }
@@ -369,26 +401,19 @@ function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $lang
   // @see token_scan()
   $pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern);
   if ($alias === $pattern_tokens_removed) {
-    return '';
+    return;
   }
 
   $alias = pathauto_clean_alias($alias);
 
   // Allow other modules to alter the alias.
-  $context = array(
-    'module' => $module,
-    'op' => $op,
-    'source' => &$source,
-    'data' => $data,
-    'type' => $type,
-    'language' => &$language,
-    'pattern' => $pattern,
-  );
+  $context['source'] = &$source;
+  $context['pattern'] = $pattern;
   drupal_alter('pathauto_alias', $alias, $context);
 
   // If we have arrived at an empty string, discontinue.
   if (!drupal_strlen($alias)) {
-    return '';
+    return;
   }
 
   // If the alias already exists, generate a new, hopefully unique, variant.
@@ -432,7 +457,7 @@ function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $lang
  *   A string with a language code.
  */
 function pathauto_alias_uniquify(&$alias, $source, $langcode) {
-  if (!_pathauto_alias_exists($alias, $source, $langcode)) {
+  if (!pathauto_is_alias_reserved($alias, $source, $langcode)) {
     return;
   }
 
@@ -445,9 +470,9 @@ function pathauto_alias_uniquify(&$alias, $source, $langcode) {
   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;
+    $alias = truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix), TRUE) . $unique_suffix;
     $i++;
-  } while (_pathauto_alias_exists($alias, $source, $langcode));
+  } while (pathauto_is_alias_reserved($alias, $source, $langcode));
 }
 
 /**
@@ -463,7 +488,7 @@ function pathauto_alias_uniquify(&$alias, $source, $langcode) {
 function _pathauto_path_is_callback($path) {
   // We need to use a try/catch here because of a core bug which will throw an
   // exception if $path is something like 'node/foo/bar'.
-  // @todo Remove when http://drupal.org/node/1302158 is fixed in core.
+  // @todo Remove when http://drupal.org/node/1003788 is fixed in core.
   try {
     $menu = menu_get_item($path);
   }
@@ -498,26 +523,19 @@ function _pathauto_path_is_callback($path) {
  *   An optional string with the operation being performed.
  *
  * @return
- *   The saved path from path_save() or NULL if the path was not saved.
+ *   The saved path from path_save() or FALSE if the path was not saved.
  *
  * @see path_save()
  */
 function _pathauto_set_alias(array $path, $existing_alias = NULL, $op = NULL) {
   $verbose = _pathauto_verbose(NULL, $op);
 
-  // Alert users that an existing callback cannot be overridden automatically
-  if (_pathauto_path_is_callback($path['alias'])) {
-    if ($verbose) {
-      _pathauto_verbose(t('Ignoring alias %alias due to existing path conflict.', array('%alias' => $path['alias'])));
-    }
-    return;
-  }
   // Alert users if they are trying to create an alias that is the same as the internal path
   if ($path['source'] == $path['alias']) {
     if ($verbose) {
       _pathauto_verbose(t('Ignoring alias %alias because it is the same as the internal path.', array('%alias' => $path['alias'])));
     }
-    return;
+    return FALSE;
   }
 
   // Skip replacing the current alias with an identical alias
@@ -529,7 +547,7 @@ function _pathauto_set_alias(array $path, $existing_alias = NULL, $op = NULL) {
       switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
         case PATHAUTO_UPDATE_ACTION_NO_NEW:
           // Do not create the alias.
-          return;
+          return FALSE;
         case PATHAUTO_UPDATE_ACTION_LEAVE:
           // Create a new alias instead of overwriting the existing by leaving
           // $path['pid'] empty.
@@ -605,7 +623,7 @@ function pathauto_clean_token_values(&$replacements, $data = array(), $options =
   foreach ($replacements as $token => $value) {
     // Only clean non-path tokens.
     if (!preg_match('/(path|alias|url|url-brief)\]$/', $token)) {
-      $replacements[$token] = pathauto_cleanstring($value);
+      $replacements[$token] = pathauto_cleanstring($value, $options);
     }
   }
 }

+ 4 - 3
sites/all/modules/pathauto/pathauto.info

@@ -3,13 +3,14 @@ description = Provides a mechanism for modules to automatically generate aliases
 dependencies[] = path
 dependencies[] = token
 core = 7.x
+files[] = pathauto.migrate.inc
 files[] = pathauto.test
 configure = admin/config/search/path/patterns
 recommends[] = redirect
 
-; Information added by drupal.org packaging script on 2012-08-09
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2015-10-07
+version = "7.x-1.3"
 core = "7.x"
 project = "pathauto"
-datestamp = "1344525185"
+datestamp = "1444232655"
 

+ 114 - 8
sites/all/modules/pathauto/pathauto.install

@@ -7,19 +7,59 @@
  * @ingroup pathauto
  */
 
+/**
+ * Implements hook_schema().
+ */
+function pathauto_schema() {
+  $schema['pathauto_state'] = array(
+    'description' => 'The status of each entity alias (whether it was automatically generated or not).',
+    'fields' => array(
+      'entity_type' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'description' => 'An entity type.',
+      ),
+      'entity_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'An entity ID.',
+      ),
+      'pathauto' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'The automatic alias status of the entity.',
+      ),
+    ),
+    'primary key' => array('entity_type', 'entity_id'),
+  );
+
+  return $schema;
+}
+
+
 /**
  * Implements hook_install().
  */
 function pathauto_install() {
   // Set some default variables necessary for the module to perform.
-  variable_set('pathauto_node_pattern', 'content/[node:title]');
-  variable_set('pathauto_taxonomy_term_pattern', '[term:vocabulary]/[term:name]');
-  variable_set('pathauto_forum_pattern', '[term:vocabulary]/[term:name]');
-  variable_set('pathauto_user_pattern', 'users/[user:name]');
-  variable_set('pathauto_blog_pattern', 'blogs/[user:name]');
-
-  // Set the default separator character to replace instead of remove (default).
-  variable_set('pathauto_punctuation_hyphen', 1);
+  $defaults = array(
+    'pathauto_node_pattern' => 'content/[node:title]',
+    'pathauto_taxonomy_term_pattern' => '[term:vocabulary]/[term:name]',
+    'pathauto_forum_pattern' => '[term:vocabulary]/[term:name]',
+    'pathauto_user_pattern' => 'users/[user:name]',
+    'pathauto_blog_pattern' => 'blogs/[user:name]',
+    // Set hyphen character to replace instead of remove.
+    'pathauto_punctuation_hyphen' => 1,
+  );
+  foreach ($defaults as $variable => $default) {
+    if (variable_get($variable) === NULL) {
+      variable_set($variable, $default);
+    }
+  }
 
   // Set the weight to 1
   db_update('system')
@@ -38,6 +78,23 @@ function pathauto_uninstall() {
   cache_clear_all('variables', 'cache');
 }
 
+/**
+ * Implements hook_requirements().
+ */
+function pathauto_requirements($phase) {
+  $requirements = array();
+  $t = get_t();
+  if ($phase == 'runtime' && module_exists('pathauto_persist')) {
+    $requirements['pathauto'] = array(
+      'title' => $t('Pathauto Persist'),
+      'value' => $t('Enabled'),
+      'description' => $t('Pathauto Persist is installed and enabled. As Pathauto Persist has been merged into Pathauto, the Pathauto Persist module can be safely disabled and removed. All Pathauto Persist settings have been migrated to the Pathauto implementation.'),
+      'severity' => REQUIREMENT_INFO,
+    );
+  }
+  return $requirements;
+}
+
 /**
  * Remove the unsupported user/%/contact and user/%/tracker pattern variables.
  */
@@ -168,6 +225,55 @@ function pathauto_update_7005() {
   return 'Your Pathauto taxonomy and forum patterns have been corrected. You may wish to regenerate your taxonomy and forum term URL aliases.';
 }
 
+/**
+ * Create pathauto_state table, using data from pathauto_persist if it exists.
+ */
+function pathauto_update_7006() {
+  if (!db_table_exists('pathauto_state')) {
+
+    $schema['pathauto_state'] = array(
+      'description' => 'The status of each entity alias (whether it was automatically generated or not).',
+      'fields' => array(
+        'entity_type' => array(
+          'type' => 'varchar',
+          'length' => 32,
+          'not null' => TRUE,
+          'description' => 'The entity type.',
+        ),
+        'entity_id' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'description' => 'The entity ID.',
+        ),
+        'pathauto' => array(
+          'type' => 'int',
+          'size' => 'tiny',
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'The automatic alias status of the entity.',
+        ),
+      ),
+      'primary key' => array('entity_type', 'entity_id'),
+    );
+
+    if (db_table_exists('pathauto_persist')) {
+      // Rename pathauto_persist's table, then create a new empty one just so
+      // that we can cleanly disable that module.
+      db_rename_table('pathauto_persist', 'pathauto_state');
+      db_create_table('pathauto_persist', $schema['pathauto_state']);
+      // Disable the module and inform the user.
+      if (module_exists('pathauto_persist')) {
+        module_disable(array('pathauto_persist'));
+      }
+      return t('The Pathauto Persist module and all of its data has been merged into Pathauto. The Pathauto Persist module has been disabled and can be safely uninstalled.');
+    }
+    else {
+      db_create_table('pathauto_state', $schema['pathauto_state']);
+    }
+  }
+}
+
 /**
  * Build a list of Drupal 6 tokens and their Drupal 7 token names.
  */

+ 3 - 3
sites/all/modules/pathauto/pathauto.js

@@ -3,13 +3,13 @@
 Drupal.behaviors.pathFieldsetSummaries = {
   attach: function (context) {
     $('fieldset.path-form', context).drupalSetSummary(function (context) {
-      var path = $('.form-item-path-alias input').val();
-      var automatic = $('.form-item-path-pathauto input').attr('checked');
+      var path = $('.form-item-path-alias input', context).val();
+      var automatic = $('.form-item-path-pathauto input', context).attr('checked');
 
       if (automatic) {
         return Drupal.t('Automatic alias');
       }
-      if (path) {
+      else if (path) {
         return Drupal.t('Alias: @alias', { '@alias': path });
       }
       else {

+ 56 - 0
sites/all/modules/pathauto/pathauto.migrate.inc

@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Support for the Pathauto module.
+ */
+
+/**
+ * Field handler.
+ */
+class PathautoMigrationHandler extends MigrateDestinationHandler {
+  public function __construct() {
+    $this->registerTypes(array('entity'));
+  }
+
+  /**
+   * Make the destination field visible.
+   */
+  public function fields() {
+    return array(
+      'pathauto' => t('Pathauto: Perform aliasing (set to 0 to prevent alias generation during migration'),
+    );
+  }
+
+  public function prepare($entity, stdClass $row) {
+    if (isset($entity->pathauto)) {
+      if (!isset($entity->path)) {
+        $entity->path = array();
+      }
+      elseif (is_string($entity->path)) {
+        // If MigratePathEntityHandler->prepare() hasn't run yet, support
+        // the alias (set as $entity->path as a string) being formatted properly
+        // in the path alias array.
+        $path = $entity->path;
+        $entity->path = array();
+        $entity->path['alias'] = $path;
+      }
+      $entity->path['pathauto'] = $entity->pathauto;
+      if (!isset($entity->path['alias'])) {
+        $entity->path['alias'] = '';
+      }
+      unset($entity->pathauto);
+    }
+  }
+}
+
+/*
+ * Implementation of hook_migrate_api().
+ */
+function pathauto_migrate_api() {
+  $api = array(
+    'api' => 2,
+    'destination handlers' => array('PathautoMigrationHandler'),
+  );
+  return $api;
+}

File diff suppressed because it is too large
+ 8 - 24
sites/all/modules/pathauto/pathauto.module


+ 0 - 165
sites/all/modules/pathauto/pathauto.pathauto.inc

@@ -7,79 +7,6 @@
  * @ingroup pathauto
  */
 
-/**
- * Implements hook_path_alias_types().
- *
- * Used primarily by the bulk delete form.
- */
-function pathauto_path_alias_types() {
-  $objects['user/'] = t('Users');
-  $objects['node/'] = t('Content');
-  if (module_exists('blog')) {
-    $objects['blog/'] = t('User blogs');
-  }
-  if (module_exists('taxonomy')) {
-    $objects['taxonomy/term/'] = t('Taxonomy terms');
-  }
-  if (module_exists('forum')) {
-    $objects['forum/'] = t('Forums');
-  }
-  return $objects;
-}
-
-/**
- * Implements hook_pathauto().
- *
- * This function is empty so that the other core module implementations can be
- * defined in this file. This is because in pathauto_module_implements_alter()
- * we add pathauto to be included first. The module system then peforms a
- * check on any subsequent run if this function still exists. If this does not
- * exist, than this file will not get included and the core implementations
- * will never get run.
- *
- * @see pathauto_module_implements_alter().
- */
-function pathauto_pathauto() {
-  // Empty hook; see the above comment.
-}
-
-/**
- * Implements hook_pathauto().
- */
-function node_pathauto($op) {
-  switch ($op) {
-    case 'settings':
-      $settings = array();
-      $settings['module'] = 'node';
-      $settings['token_type'] = 'node';
-      $settings['groupheader'] = t('Content paths');
-      $settings['patterndescr'] = t('Default path pattern (applies to all content types with blank patterns below)');
-      $settings['patterndefault'] = 'content/[node:title]';
-      $settings['batch_update_callback'] = 'node_pathauto_bulk_update_batch_process';
-      $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
-
-      $languages = array();
-      if (module_exists('locale')) {
-        $languages = array(LANGUAGE_NONE => t('language neutral')) + locale_language_list('name');
-      }
-
-      foreach (node_type_get_names() as $node_type => $node_name) {
-        if (count($languages) && variable_get('language_content_type_' . $node_type, 0)) {
-          $settings['patternitems'][$node_type] = t('Default path pattern for @node_type (applies to all @node_type content types with blank patterns below)', array('@node_type' => $node_name));
-          foreach ($languages as $lang_code => $lang_name) {
-            $settings['patternitems'][$node_type . '_' . $lang_code] = t('Pattern for all @language @node_type paths', array('@node_type' => $node_name, '@language' => $lang_name));
-          }
-        }
-        else {
-          $settings['patternitems'][$node_type] = t('Pattern for all @node_type paths', array('@node_type' => $node_name));
-        }
-      }
-      return (object) $settings;
-    default:
-      break;
-  }
-}
-
 /**
  * Batch processing callback; Generate aliases for nodes.
  */
@@ -122,38 +49,6 @@ function node_pathauto_bulk_update_batch_process(&$context) {
   }
 }
 
-/**
- * Implements hook_pathauto().
- */
-function taxonomy_pathauto($op) {
-  switch ($op) {
-    case 'settings':
-      $settings = array();
-      $settings['module'] = 'taxonomy_term';
-      $settings['token_type'] = 'term';
-      $settings['groupheader'] = t('Taxonomy term paths');
-      $settings['patterndescr'] = t('Default path pattern (applies to all vocabularies with blank patterns below)');
-      $settings['patterndefault'] = '[term:vocabulary]/[term:name]';
-      $settings['batch_update_callback'] = 'taxonomy_pathauto_bulk_update_batch_process';
-      $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
-
-      $vocabularies = taxonomy_get_vocabularies();
-      if (count($vocabularies)) {
-        $settings['patternitems'] = array();
-        foreach ($vocabularies as $vid => $vocabulary) {
-          if ($vid == variable_get('forum_nav_vocabulary', '')) {
-            // Skip the forum vocabulary.
-            continue;
-          }
-          $settings['patternitems'][$vocabulary->machine_name] = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabulary->name));
-        }
-      }
-      return (object) $settings;
-    default:
-      break;
-  }
-}
-
 /**
  * Batch processing callback; Generate aliases for taxonomy terms.
  */
@@ -200,26 +95,6 @@ function taxonomy_pathauto_bulk_update_batch_process(&$context) {
   }
 }
 
-/**
- * Implements hook_pathauto() for forum module.
- */
-function forum_pathauto($op) {
-  switch ($op) {
-    case 'settings':
-      $settings = array();
-      $settings['module'] = 'forum';
-      $settings['token_type'] = 'term';
-      $settings['groupheader'] = t('Forum paths');
-      $settings['patterndescr'] = t('Pattern for forums and forum containers');
-      $settings['patterndefault'] = '[term:vocabulary]/[term:name]';
-      $settings['batch_update_callback'] = 'forum_pathauto_bulk_update_batch_process';
-      $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
-      return (object) $settings;
-    default:
-      break;
-  }
-}
-
 /**
  * Batch processing callback; Generate aliases for forums.
  */
@@ -263,26 +138,6 @@ function forum_pathauto_bulk_update_batch_process(&$context) {
   }
 }
 
-/**
- * Implements hook_pathauto().
- */
-function user_pathauto($op) {
-  switch ($op) {
-    case 'settings':
-      $settings = array();
-      $settings['module'] = 'user';
-      $settings['token_type'] = 'user';
-      $settings['groupheader'] = t('User paths');
-      $settings['patterndescr'] = t('Pattern for user account page paths');
-      $settings['patterndefault'] = 'users/[user:name]';
-      $settings['batch_update_callback'] = 'user_pathauto_bulk_update_batch_process';
-      $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
-      return (object) $settings;
-    default:
-      break;
-  }
-}
-
 /**
  * Batch processing callback; Generate aliases for users.
  */
@@ -325,26 +180,6 @@ function user_pathauto_bulk_update_batch_process(&$context) {
   }
 }
 
-/**
- * Implements hook_pathauto().
- */
-function blog_pathauto($op) {
-  switch ($op) {
-    case 'settings':
-      $settings = array();
-      $settings['module'] = 'blog';
-      $settings['token_type'] = 'user';
-      $settings['groupheader'] = t('Blog paths');
-      $settings['patterndescr'] = t('Pattern for blog page paths');
-      $settings['patterndefault'] = 'blogs/[user:name]';
-      $settings['batch_update_callback'] = 'blog_pathauto_bulk_update_batch_process';
-      $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
-      return (object) $settings;
-    default:
-      break;
-  }
-}
-
 /**
  * Batch processing callback; Generate aliases for blogs.
  */

+ 192 - 10
sites/all/modules/pathauto/pathauto.test

@@ -55,9 +55,13 @@ class PathautoTestHelper extends DrupalWebTestCase {
     $this->assertEntityAlias($entity_type, $entity, $uri['path'], $language);
   }
 
-  function assertNoEntityAliasExists($entity_type, $entity) {
+  function assertNoEntityAliasExists($entity_type, $entity, $alias = NULL) {
     $uri = entity_uri($entity_type, $entity);
-    $this->assertNoAliasExists(array('source' => $uri['path']));
+    $path = array('source' => $uri['path']);
+    if (!empty($alias)) {
+      $path['alias'] = $alias;
+    }
+    $this->assertNoAliasExists($path);
   }
 
   function assertAlias($source, $expected_alias, $language = LANGUAGE_NONE) {
@@ -192,6 +196,23 @@ class PathautoUnitTestCase extends PathautoTestHelper {
     }
   }
 
+  /**
+   * Test pathauto_clean_alias().
+   */
+  function testCleanAlias() {
+    $tests = array();
+    $tests['one/two/three'] = 'one/two/three';
+    $tests['/one/two/three/'] = 'one/two/three';
+    $tests['one//two///three'] = 'one/two/three';
+    $tests['one/two--three/-/--/-/--/four---five'] = 'one/two-three/four-five';
+    $tests['one/-//three--/four'] = 'one/three/four';
+    foreach ($tests as $input => $expected) {
+      $output = pathauto_clean_alias($input);
+      $this->assertEqual($output, $expected, t("pathauto_clean_alias('@input') expected '@expected', actual '@output'", array('@input' => $input, '@expected' => $expected, '@output' => $output)));
+    }
+
+  }
+
   /**
    * Test pathauto_path_delete_multiple().
    */
@@ -244,7 +265,7 @@ class PathautoUnitTestCase extends PathautoTestHelper {
     $node->title = 'Fifth title';
     pathauto_node_update($node);
     $this->assertEntityAlias('node', $node, 'content/fourth-title');
-    $this->assertNoAliasExists(array('alias' => 'content/fith-title'));
+    $this->assertNoAliasExists(array('alias' => 'content/fifth-title'));
 
     // Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'update'.
     $this->deleteAllAliases();
@@ -289,6 +310,40 @@ class PathautoUnitTestCase extends PathautoTestHelper {
     $this->assertEntityAlias('taxonomy_term', $term2, 'My Crazy/Alias/child-term');
   }
 
+  /**
+   * Test using fields for path structures.
+   */
+  function testParentChildPathTokens() {
+    // First create a field which will be used to create the path. It must
+    // begin with a letter.
+    $fieldname = 'a' . drupal_strtolower($this->randomName());
+    field_create_field(array('field_name' => $fieldname, 'type' => 'text'));
+    field_create_instance(array('field_name' => $fieldname, 'entity_type' => 'taxonomy_term', 'bundle' => 'tags'));
+
+    // Make the path pattern of a field use the value of this field appended
+    // to the parent taxonomy term's pattern if there is one.
+    variable_set('pathauto_taxonomy_term_tags_pattern', '[term:parents:join-path]/[term:' . $fieldname . ']');
+
+    // Start by creating a parent term.
+    $parent = new stdClass();
+    $parent->$fieldname = array(LANGUAGE_NONE => array(array('value' => $parent->name = $this->randomName())));
+    $parent->vid = 1;
+    taxonomy_term_save($parent);
+
+    // Create the child term.
+    $child = new stdClass();
+    $child->name = $this->randomName();
+    $child->$fieldname = array(LANGUAGE_NONE => array(array('value' => $child->name = $this->randomName())));
+    $child->vid = 1;
+    $child->parent = $parent->tid;
+    taxonomy_term_save($child);
+    $this->assertEntityAlias('taxonomy_term', $child, drupal_strtolower($parent->name . '/' . $child->name));
+
+    // Re-saving the parent term should not modify the child term's alias.
+    taxonomy_term_save($parent);
+    $this->assertEntityAlias('taxonomy_term', $child, drupal_strtolower($parent->name . '/' . $child->name));
+  }
+
   function testEntityBundleRenamingDeleting() {
     // Create a vocabulary and test that it's pattern variable works.
     $vocab = $this->addVocabulary(array('machine_name' => 'old_name'));
@@ -315,17 +370,17 @@ class PathautoUnitTestCase extends PathautoTestHelper {
 
     // Check that Pathauto does not create an alias of '/admin'.
     $node = $this->drupalCreateNode(array('title' => 'Admin', 'type' => 'page'));
-    $this->assertNoEntityAlias('node', $node);
+    $this->assertEntityAlias('node', $node, 'admin-0');
 
     // Check that Pathauto does not create an alias of '/modules'.
     $node->title = 'Modules';
     node_save($node);
-    $this->assertNoEntityAlias('node', $node);
+    $this->assertEntityAlias('node', $node, 'modules-0');
 
     // Check that Pathauto does not create an alias of '/index.php'.
     $node->title = 'index.php';
     node_save($node);
-    $this->assertNoEntityAlias('node', $node);
+    $this->assertEntityAlias('node', $node, 'index.php-0');
 
     // Check that a safe value gets an automatic alias. This is also a control
     // to ensure the above tests work properly.
@@ -333,6 +388,18 @@ class PathautoUnitTestCase extends PathautoTestHelper {
     node_save($node);
     $this->assertEntityAlias('node', $node, 'safe-value');
   }
+
+  function testPathAliasUniquifyWordsafe() {
+    variable_set('pathauto_max_length', 25);
+
+    $node_1 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
+    $node_2 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
+
+    // Check that pathauto_alias_uniquify is calling truncate_utf8 with $wordsafe param set to TRUE.
+    // If it doesn't path alias result would be content/thequick-brownf-0
+    $this->assertEntityAlias('node', $node_1, 'content/thequick-brownfox');
+    $this->assertEntityAlias('node', $node_2, 'content/thequick-0');
+  }
 }
 
 /**
@@ -408,13 +475,23 @@ class PathautoFunctionalTestCase extends PathautoFunctionalTestHelper {
     $this->drupalGet($automatic_alias);
     $this->assertText($title, 'Node accessible through automatic alias.');
 
+    // Disable the update action. The checkbox should not be visible.
+    variable_set('pathauto_update_action', 0);
+    $this->drupalGet("node/{$node->nid}/edit");
+    $this->assertNoFieldById('edit-path-pathauto');
+
+    // Reset the update action back to default. The checkbox should be visible.
+    variable_del('pathauto_update_action');
+    $this->drupalGet("node/{$node->nid}/edit");
+    $this->assertFieldChecked('edit-path-pathauto');
+
     // Manually set the node's alias.
     $manual_alias = 'content/' . $node->nid;
     $edit = array(
       'path[pathauto]' => FALSE,
       'path[alias]' => $manual_alias,
     );
-    $this->drupalPost("node/{$node->nid}/edit", $edit, t('Save'));
+    $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertText("Basic page $title has been updated.");
 
     // Check that the automatic alias checkbox is now unchecked by default.
@@ -449,6 +526,26 @@ class PathautoFunctionalTestCase extends PathautoFunctionalTestHelper {
     $this->assertNoFieldById('edit-path-pathauto');
     $this->assertFieldByName('path[alias]', '');
     $this->assertNoEntityAlias('node', $node);
+
+    // Set the page pattern to use only tokens so we can test the checkbox
+    // behavior if none of the tokens have a value currently.
+    variable_set('pathauto_node_page_pattern', '[node:title]');
+
+    // Create a node with an empty title. The Pathauto checkbox should still be
+    // visible but unchecked.
+    $node = $this->drupalCreateNode(array('type' => 'page', 'title' => ''));
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertNoFieldChecked('edit-path-pathauto');
+    $this->assertFieldByName('path[alias]', '');
+    $this->assertNoEntityAlias('node', $node);
+
+    $edit = array();
+    $edit['title'] = 'Valid title';
+    $edit['path[pathauto]'] = TRUE;
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertFieldChecked('edit-path-pathauto');
+    $this->assertFieldByName('path[alias]', 'valid-title');
   }
 
   /**
@@ -472,6 +569,83 @@ class PathautoFunctionalTestCase extends PathautoFunctionalTestHelper {
     $this->assertEntityAlias('node', $node2, 'node/' . $node2->nid);
   }
 
+  /**
+   * @todo Merge this with existing node test methods?
+   */
+  public function testNodeState() {
+    $nodeNoAliasUser = $this->drupalCreateUser(array('bypass node access'));
+    $nodeAliasUser = $this->drupalCreateUser(array('bypass node access', 'create url aliases'));
+
+    $node = $this->drupalCreateNode(array(
+      'title' => 'Node version one',
+      'type' => 'page',
+      'path' => array(
+        'pathauto' => FALSE,
+      ),
+    ));
+
+    $this->assertNoEntityAlias('node', $node);
+
+    // Set a manual path alias for the node.
+    $node->path['alias'] = 'test-alias';
+    node_save($node);
+
+    // Ensure that the pathauto field was saved to the database.
+    $node = node_load($node->nid, NULL, TRUE);
+    $this->assertFalse($node->path['pathauto']);
+
+    // Ensure that the manual path alias was saved and an automatic alias was not generated.
+    $this->assertEntityAlias('node', $node, 'test-alias');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
+
+    // Save the node as a user who does not have access to path fieldset.
+    $this->drupalLogin($nodeNoAliasUser);
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertNoFieldByName('path[pathauto]');
+
+    $edit = array('title' => 'Node version two');
+    $this->drupalPost(NULL, $edit, 'Save');
+    $this->assertText('Basic page Node version two has been updated.');
+
+    $this->assertEntityAlias('node', $node, 'test-alias');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-two');
+
+    // Load the edit node page and check that the Pathauto checkbox is unchecked.
+    $this->drupalLogin($nodeAliasUser);
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertNoFieldChecked('edit-path-pathauto');
+
+    // Edit the manual alias and save the node.
+    $edit = array(
+      'title' => 'Node version three',
+      'path[alias]' => 'manually-edited-alias',
+    );
+    $this->drupalPost(NULL, $edit, 'Save');
+    $this->assertText('Basic page Node version three has been updated.');
+
+    $this->assertEntityAlias('node', $node, 'manually-edited-alias');
+    $this->assertNoEntityAliasExists('node', $node, 'test-alias');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-two');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-three');
+
+    // Programatically save the node with an automatic alias.
+    $node = node_load($node->nid, NULL, TRUE);
+    $node->path['pathauto'] = TRUE;
+    node_save($node);
+
+    // Ensure that the pathauto field was saved to the database.
+    $node = node_load($node->nid, NULL, TRUE);
+    $this->assertTrue($node->path['pathauto']);
+
+    $this->assertEntityAlias('node', $node, 'content/node-version-three');
+    $this->assertNoEntityAliasExists('node', $node, 'manually-edited-alias');
+    $this->assertNoEntityAliasExists('node', $node, 'test-alias');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
+    $this->assertNoEntityAliasExists('node', $node, 'content/node-version-two');
+  }
+
   /**
    * Basic functional testing of Pathauto with taxonomy terms.
    */
@@ -672,6 +846,14 @@ class PathautoLocaleTestCase extends PathautoFunctionalTestHelper {
     $this->assertEntityAlias('node', $node, 'content/english-node-0', 'en');
     $this->assertEntityAlias('node', $node, 'french-node', 'fr');
     $this->assertAliasExists(array('pid' => $english_alias['pid'], 'alias' => 'content/english-node-0'));
+
+    // Create a new node with the same title as before but without
+    // specifying a language.
+    $node = $this->drupalCreateNode(array('title' => 'English node'));
+
+    // Check that the new node had a unique alias generated with the '-1'
+    // suffix.
+    $this->assertEntityAlias('node', $node, 'content/english-node-1');
   }
 }
 
@@ -718,11 +900,11 @@ class PathautoBulkUpdateTestCase extends PathautoFunctionalTestHelper {
     // Add a new node.
     $new_node = $this->drupalCreateNode(array('path' => array('alias' => '', 'pathauto' => FALSE)));
 
-    // Run the update again which should only run against the new node.
+    // Run the update again which should not run against any nodes.
     $this->drupalPost('admin/config/search/path/update_bulk', $edit, t('Update'));
-    $this->assertText('Generated 1 URL alias.'); // 1 node + 0 users
+    $this->assertText('No new URL aliases to generate.');
 
-    $this->assertEntityAliasExists('node', $new_node);
+    $this->assertNoEntityAliasExists('node', $new_node);
   }
 }
 

+ 1 - 1
sites/all/modules/pathauto/pathauto.tokens.inc

@@ -35,7 +35,7 @@ function pathauto_tokens($type, $tokens, array $data = array(), array $options =
           $values = array();
           foreach (element_children($array) as $key) {
             $value = is_array($array[$key]) ? render($array[$key]) : (string) $array[$key];
-            $value = pathauto_cleanstring($value);
+            $value = pathauto_cleanstring($value, $options);
             $values[] = $value;
           }
           $replacements[$original] = implode('/', $values);

+ 31 - 0
sites/all/modules/token/README.txt

@@ -1,2 +1,33 @@
+INTRODUCTION
+------------
 
 Provides common and resuable token UI elements and missing core tokens.
+
+ * For a full description of the module, visit the project page:
+   https://drupal.org/project/token
+
+ * To submit bug reports and feature suggestions, or to track changes:
+   https://drupal.org/project/issues/token
+
+
+INSTALLATION
+------------
+
+Install as usual, see
+https://drupal.org/documentation/install/modules-themes/modules-7 for further
+information.
+
+
+TROUBLESHOOTING
+---------------
+
+Token module doesn't provide any visible functions to the user on its own, it
+just provides token handling services for other modules.
+
+
+MAINTAINERS
+-----------
+
+Current maintainers:
+
+ * Dave Reid (https://drupal.org/user/53892)

+ 3 - 3
sites/all/modules/token/tests/token_test.info

@@ -5,9 +5,9 @@ core = 7.x
 files[] = token_test.module
 hidden = TRUE
 
-; Information added by drupal.org packaging script on 2013-02-06
-version = "7.x-1.4+6-dev"
+; Information added by Drupal.org packaging script on 2017-01-25
+version = "7.x-1.7"
 core = "7.x"
 project = "token"
-datestamp = "1360159412"
+datestamp = "1485316088"
 

+ 13 - 0
sites/all/modules/token/tests/token_test.tokens.inc

@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * Implements hook_token_info()
+ */
+function token_test_token_info() {
+  $info['tokens']['node']['colons:in:name'] = array(
+    'name' => t('A test token with colons in the name'),
+    'description' => NULL,
+  );
+
+  return $info;
+}

+ 3 - 3
sites/all/modules/token/token.info

@@ -3,9 +3,9 @@ description = Provides a user interface for the Token API and some missing core
 core = 7.x
 files[] = token.test
 
-; Information added by drupal.org packaging script on 2013-02-06
-version = "7.x-1.4+6-dev"
+; Information added by Drupal.org packaging script on 2017-01-25
+version = "7.x-1.7"
 core = "7.x"
 project = "token"
-datestamp = "1360159412"
+datestamp = "1485316088"
 

+ 21 - 19
sites/all/modules/token/token.install

@@ -21,18 +21,12 @@ function token_requirements($phase = 'runtime') {
         $problems = array_unique($problem['problems']);
         $problems = array_map('check_plain', $problems);
         $token_problems[$problem_key] = $problem['label'] . theme('item_list', array('items' => $problems));
+        $requirements['token-' . $problem_key] = array(
+          'title' => $problem['label'],
+          'value' => theme('item_list', array('items' => $problems)),
+          'severity' => $problem['severity'],
+        );
       }
-      else {
-        unset($token_problems[$problem_key]);
-      }
-    }
-    if (!empty($token_problems)) {
-      $requirements['token_problems'] = array(
-        'title' => $t('Tokens'),
-        'value' => $t('Problems detected'),
-        'severity' => REQUIREMENT_WARNING,
-        'description' => '<p>' . implode('</p><p>', $token_problems) . '</p>', //theme('item_list', array('items' => $token_problems)),
-      );
     }
   }
 
@@ -272,19 +266,24 @@ function token_get_token_problems() {
   $token_info = token_info();
   $token_problems = array(
     'not-array' => array(
-      'label' => t('The following tokens or token types are not defined as arrays:'),
+      'label' => t('Tokens or token types not defined as arrays'),
+      'severity' => REQUIREMENT_ERROR,
     ),
     'missing-info' => array(
-      'label' => t('The following tokens or token types are missing required name and/or description information:'),
+      'label' => t('Tokens or token types missing name property'),
+      'severity' => REQUIREMENT_WARNING,
     ),
     'type-no-tokens' => array(
-      'label' => t('The following token types do not have any tokens defined:'),
+      'label' => t('Token types do not have any tokens defined'),
+      'severity' => REQUIREMENT_INFO,
     ),
     'tokens-no-type' => array(
-      'label' => t('The following token types are not defined but have tokens:'),
+      'label' => t('Token types are not defined but have tokens'),
+      'severity' => REQUIREMENT_INFO,
     ),
     'duplicate' => array(
-      'label' => t('The following token or token types are defined by multiple modules:')
+      'label' => t('Token or token types are defined by multiple modules'),
+      'severity' => REQUIREMENT_ERROR,
     ),
   );
 
@@ -295,9 +294,12 @@ function token_get_token_problems() {
       $token_problems['not-array']['problems'][] = "\$info['types']['$type']";
       continue;
     }
-    elseif (!isset($type_info['name']) || !isset($type_info['description'])) {
+    elseif (!isset($type_info['name'])) {
       $token_problems['missing-info']['problems'][] = "\$info['types']['$type']";
     }
+    elseif (is_array($type_info['name'])) {
+      $token_problems['duplicate']['problems'][] = "\$info['types']['$type']";
+    }
     elseif (empty($token_info['tokens'][$real_type])) {
       $token_problems['type-no-tokens']['problems'][] = "\$info['tokens']['$real_type']";
     }
@@ -315,10 +317,10 @@ function token_get_token_problems() {
           $token_problems['not-array']['problems'][] = "\$info['tokens']['$type']['$token']";
           continue;
         }
-        elseif (!isset($tokens[$token]['name']) || !isset($tokens[$token]['description'])) {
+        elseif (!isset($tokens[$token]['name'])) {
           $token_problems['missing-info']['problems'][] = "\$info['tokens']['$type']['$token']";
         }
-        elseif (is_array($tokens[$token]['name']) || is_array($tokens[$token]['description'])) {
+        elseif (is_array($tokens[$token]['name'])) {
           $token_problems['duplicate']['problems'][] = "\$info['tokens']['$type']['$token']";
         }
       }

+ 7 - 1
sites/all/modules/token/token.js

@@ -14,6 +14,12 @@ Drupal.behaviors.tokenDialog = {
     $('a.token-dialog', context).once('token-dialog').click(function() {
       var url = $(this).attr('href');
       var dialog = $('<div style="display: none" class="loading">' + Drupal.t('Loading token browser...') + '</div>').appendTo('body');
+
+      // Emulate the AJAX data sent normally so that we get the same theme.
+      var data = {};
+      data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme;
+      data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token;
+
       dialog.dialog({
         title: $(this).attr('title') || Drupal.t('Available tokens'),
         width: 700,
@@ -24,7 +30,7 @@ Drupal.behaviors.tokenDialog = {
       // Load the token tree using AJAX.
       dialog.load(
         url,
-        {},
+        data,
         function (responseText, textStatus, XMLHttpRequest) {
           dialog.removeClass('loading');
         }

+ 31 - 18
sites/all/modules/token/token.module

@@ -78,6 +78,7 @@ function token_menu() {
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
     'file' => 'token.pages.inc',
+    'theme callback' => 'ajax_base_page_theme',
   );
 
   // Devel token pages.
@@ -179,7 +180,7 @@ function token_theme() {
       'text' => NULL,
       'options' => array(),
       'dialog' => TRUE,
-    ),
+    ) + $info['token_tree']['variables'],
     'file' => 'token.pages.inc',
   );
 
@@ -265,15 +266,19 @@ function token_form_block_admin_configure_alter(&$form, $form_state) {
   $form['settings']['title']['#description'] .= ' ' . t('This field supports tokens.');
   // @todo Figure out why this token validation does not seem to be working here.
   $form['settings']['title']['#element_validate'][] = 'token_element_validate';
-  $form['settings']['title']['#token_types'] = array();
+  $form['settings']['title'] += array('#token_types' => array());
 }
 
 /**
  * Implements hook_widget_form_alter().
  */
 function token_field_widget_form_alter(&$element, &$form_state, $context) {
-  if (!empty($element['#description']) && is_string($element['#description'])) {
-    $element['#description'] = filter_xss_admin(token_replace($element['#description']));
+  if (!empty($element['#description']) && !empty($context['instance']['description'])) {
+    $instance = $context['instance'];
+    if (module_exists('i18n_field')) {
+      $instance = i18n_string_object_translate('field_instance', $instance);
+    }
+    $element['#description'] = field_filter_xss(token_replace($instance['description']));
   }
 }
 
@@ -389,7 +394,7 @@ function token_get_entity_mapping($value_type = 'token', $value = NULL, $fallbac
   }
 
   if (!isset($value)) {
-    return $mapping;
+    return $value_type == 'token' ? array_flip($mapping) : $mapping;
   }
   elseif ($value_type == 'token') {
     $return = array_search($value, $mapping);
@@ -411,12 +416,18 @@ function token_get_entity_mapping($value_type = 'token', $value = NULL, $fallbac
  */
 function token_entity_info_alter(&$info) {
   foreach (array_keys($info) as $entity_type) {
-    // Add a token view mode if it does not already exist.
-    if (!empty($info[$entity_type]['view modes']) && !isset($info[$entity_type]['view modes']['token'])) {
-      $info[$entity_type]['view modes']['token'] = array(
-        'label' => t('Tokens'),
-        'custom settings' => FALSE,
-      );
+    // Add a token view mode if it does not already exist. Only work with
+    // fieldable entities.
+    if (!empty($info[$entity_type]['fieldable'])) {
+      if (!isset($info[$entity_type])) {
+        $info[$entity_type]['view modes'] = array();
+      }
+      if (!isset($info[$entity_type]['view modes']['token'])) {
+        $info[$entity_type]['view modes']['token'] = array(
+          'label' => t('Tokens'),
+          'custom settings' => FALSE,
+        );
+      }
     }
 
     if (!empty($info[$entity_type]['token type'])) {
@@ -646,6 +657,10 @@ function token_get_invalid_tokens($type, $tokens) {
   $invalid_tokens = array();
 
   foreach ($tokens as $token => $full_token) {
+    if (isset($token_info['tokens'][$type][$token])) {
+      continue;
+    }
+
     // Split token up if it has chains.
     $parts = explode(':', $token, 2);
 
@@ -708,15 +723,13 @@ function token_element_validate(&$element, &$form_state) {
 
   // Validate if an element must have a minimum number of tokens.
   if (isset($element['#min_tokens']) && count($tokens) < $element['#min_tokens']) {
-    // @todo Change this error message to include the minimum number.
-    $error = format_plural($element['#min_tokens'], 'The %element-title cannot contain fewer than one token.', 'The %element-title must contain at least @count tokens.', array('%element-title' => $title));
+    $error = format_plural($element['#min_tokens'], '%name must contain at least one token.', '%name must contain at least @count tokens.', array('%name' => $title));
     form_error($element, $error);
   }
 
   // Validate if an element must have a maximum number of tokens.
   if (isset($element['#max_tokens']) && count($tokens) > $element['#max_tokens']) {
-    // @todo Change this error message to include the maximum number.
-    $error = format_plural($element['#max_tokens'], 'The %element-title must contain as most one token.', 'The %element-title must contain at most @count tokens.', array('%element-title' => $title));
+    $error = format_plural($element['#max_tokens'], '%name must contain at most one token.', '%name must contain at most @count tokens.', array('%name' => $title));
     form_error($element, $error);
   }
 
@@ -724,7 +737,7 @@ function token_element_validate(&$element, &$form_state) {
   if (isset($element['#token_types'])) {
     $invalid_tokens = token_get_invalid_tokens_by_context($tokens, $element['#token_types']);
     if ($invalid_tokens) {
-      form_error($element, t('The %element-title is using the following invalid tokens: @invalid-tokens.', array('%element-title' => $title, '@invalid-tokens' => implode(', ', $invalid_tokens))));
+      form_error($element, t('%name is using the following invalid tokens: @invalid-tokens.', array('%name' => $title, '@invalid-tokens' => implode(', ', $invalid_tokens))));
     }
   }
 
@@ -742,7 +755,7 @@ function token_element_validate_token_context(&$element, &$form_state) {
  * Implements hook_form_FORM_ID_alter().
  */
 function token_form_field_ui_field_edit_form_alter(&$form, $form_state) {
-  if (!isset($form['instance'])) {
+  if (!isset($form['instance']) || !empty($form['#field']['locked'])) {
     return;
   }
 
@@ -972,7 +985,7 @@ function _token_build_tree($token_type, array $options) {
       // parent.
       $token_parents[] = $token_type;
     }
-    elseif (in_array($token, array_slice($token_parents, 1))) {
+    elseif (in_array($token, array_slice($token_parents, 1), TRUE)) {
       // Prevent duplicate recursive tokens. For example, this will prevent
       // the tree from generating the following tokens or deeper:
       // [comment:parent:parent]

+ 9 - 3
sites/all/modules/token/token.pages.inc

@@ -19,7 +19,12 @@ function theme_token_tree_link($variables) {
   }
 
   $info = token_theme();
-  $variables['options']['query']['options'] = array_intersect_key($variables, $info['token_tree']['variables']);
+  $tree_variables = array_intersect_key($variables, $info['token_tree']['variables']);
+  $tree_variables = drupal_array_diff_assoc_recursive($tree_variables, $info['token_tree']['variables']);
+  if (!isset($variables['options']['query']['options'])) {
+    $variables['options']['query']['options'] = array();
+  }
+  $variables['options']['query']['options'] += $tree_variables;
 
   // We should never pass the dialog option to theme_token_tree(). It is only
   // used for this function.
@@ -57,7 +62,7 @@ function token_page_output_tree() {
   $options['dialog'] = FALSE;
 
   $output = theme('token_tree', $options);
-  print '<html><head><title></title>' . drupal_get_css() . drupal_get_js() . '</head>';
+  print '<html><head>' . drupal_get_css() . drupal_get_js() . '</head>';
   print '<body class="token-tree">' . $output . '</body></html>';
   drupal_exit();
 }
@@ -194,7 +199,7 @@ function _token_token_tree_format_row($token, array $token_info, $is_group = FAL
   $row = $defaults;
   $row['id'] = _token_clean_css_identifier($token);
   $row['data']['name'] = $token_info['name'];
-  $row['data']['description'] = $token_info['description'];
+  $row['data']['description'] = isset($token_info['description']) ? $token_info['description'] : '';
 
   if ($is_group) {
     // This is a token type/group.
@@ -202,6 +207,7 @@ function _token_token_tree_format_row($token, array $token_info, $is_group = FAL
   }
   else {
     // This is a token.
+    $row['data']['token'] = array();
     $row['data']['token']['data'] = $token;
     $row['data']['token']['class'][] = 'token-key';
     if (isset($token_info['value'])) {

+ 4 - 0
sites/all/modules/token/token.test

@@ -137,6 +137,7 @@ class TokenUnitTestCase extends TokenTestHelper {
         '[node:created:short]',
         '[node:created:custom:invalid]',
         '[node:created:custom:mm-YYYY]',
+        '[node:colons:in:name]',
         '[site:name]',
         '[site:slogan]',
         '[current-date:short]',
@@ -147,6 +148,7 @@ class TokenUnitTestCase extends TokenTestHelper {
         '[node:title:invalid]',
         '[node:created:invalid]',
         '[node:created:short:invalid]',
+        '[node:colons:in:name:invalid]',
         '[invalid:title]',
         '[site:invalid]',
         '[user:ip-address]',
@@ -166,6 +168,7 @@ class TokenUnitTestCase extends TokenTestHelper {
         '[node:created:short]',
         '[node:created:custom:invalid]',
         '[node:created:custom:mm-YYYY]',
+        '[node:colons:in:name]',
         '[site:name]',
         '[site:slogan]',
         '[user:uid]',
@@ -176,6 +179,7 @@ class TokenUnitTestCase extends TokenTestHelper {
         '[node:title:invalid]',
         '[node:created:invalid]',
         '[node:created:short:invalid]',
+        '[node:colons:in:name:invalid]',
         '[invalid:title]',
         '[site:invalid]',
         '[user:ip-address]',

+ 1 - 2
sites/all/modules/token/token.tokens.inc

@@ -1392,7 +1392,6 @@ function field_token_info_alter(&$info) {
  */
 function field_tokens($type, $tokens, array $data = array(), array $options = array()) {
   $replacements = array();
-  $sanitize = !empty($options['sanitize']);
   $langcode = isset($options['language']) ? $options['language']->language : NULL;
 
   // Entity tokens.
@@ -1437,7 +1436,7 @@ function field_tokens($type, $tokens, array $data = array(), array $options = ar
 /**
  * Pre-render callback for field output used with tokens.
  */
-function token_pre_render_field_token(&$elements) {
+function token_pre_render_field_token($elements) {
   // Remove the field theme hook, attachments, and JavaScript states.
   unset($elements['#theme']);
   unset($elements['#states']);

Some files were not shown because too many files changed in this diff