From 4e0d4316d6f4278df544debbe35e0f50cb363762 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Sat, 9 Feb 2019 16:22:26 +0100 Subject: [PATCH] updated pathauto token --- sites/all/modules/pathauto/API.txt | 149 ------ sites/all/modules/pathauto/README.txt | 77 ++- sites/all/modules/pathauto/pathauto.admin.inc | 14 +- sites/all/modules/pathauto/pathauto.api.php | 160 +++++- sites/all/modules/pathauto/pathauto.inc | 160 +++--- sites/all/modules/pathauto/pathauto.info | 7 +- sites/all/modules/pathauto/pathauto.install | 122 ++++- sites/all/modules/pathauto/pathauto.js | 6 +- .../all/modules/pathauto/pathauto.migrate.inc | 56 +++ sites/all/modules/pathauto/pathauto.module | 476 ++++++++++++++++-- .../modules/pathauto/pathauto.pathauto.inc | 165 ------ sites/all/modules/pathauto/pathauto.test | 202 +++++++- .../all/modules/pathauto/pathauto.tokens.inc | 2 +- sites/all/modules/token/README.txt | 31 ++ sites/all/modules/token/tests/token_test.info | 6 +- .../modules/token/tests/token_test.tokens.inc | 13 + sites/all/modules/token/token.info | 6 +- sites/all/modules/token/token.install | 40 +- sites/all/modules/token/token.js | 8 +- sites/all/modules/token/token.module | 49 +- sites/all/modules/token/token.pages.inc | 12 +- sites/all/modules/token/token.test | 4 + sites/all/modules/token/token.tokens.inc | 3 +- 23 files changed, 1204 insertions(+), 564 deletions(-) delete mode 100644 sites/all/modules/pathauto/API.txt create mode 100644 sites/all/modules/pathauto/pathauto.migrate.inc create mode 100644 sites/all/modules/token/tests/token_test.tokens.inc diff --git a/sites/all/modules/pathauto/API.txt b/sites/all/modules/pathauto/API.txt deleted file mode 100644 index e585cd4..0000000 --- a/sites/all/modules/pathauto/API.txt +++ /dev/null @@ -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 _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 _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. diff --git a/sites/all/modules/pathauto/README.txt b/sites/all/modules/pathauto/README.txt index 4c458a1..fc56959 100644 --- a/sites/all/modules/pathauto/README.txt +++ b/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

-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. - diff --git a/sites/all/modules/pathauto/pathauto.admin.inc b/sites/all/modules/pathauto/pathauto.admin.inc index 9030f48..b210386 100644 --- a/sites/all/modules/pathauto/pathauto.admin.inc +++ b/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 Redirect module settings affect whether a redirect is created when an alias is deleted.', array('!url' => url('admin/config/search/redirect'))); + $description .= ' ' . t('The Redirect module settings 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 Redirect module 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'), ); diff --git a/sites/all/modules/pathauto/pathauto.api.php b/sites/all/modules/pathauto/pathauto.api.php index 41f1154..4c92188 100644 --- a/sites/all/modules/pathauto/pathauto.api.php +++ b/sites/all/modules/pathauto/pathauto.api.php @@ -1,17 +1,171 @@ $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); + } } /** diff --git a/sites/all/modules/pathauto/pathauto.inc b/sites/all/modules/pathauto/pathauto.inc index 9f98eec..9699aa0 100644 --- a/sites/all/modules/pathauto/pathauto.inc +++ b/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. - $seppattern = preg_quote($separator, '/'); - // Trim any leading or trailing separators. - $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output); + $output = trim($output, $separator); - // Replace trailing separators around slashes. - if ($separator !== '/') { - $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output); - } + // Escape the separator for use in regular expressions. + $seppattern = preg_quote($separator, '/'); // 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\/+|\/+$seppattern/", "/", $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); } } } diff --git a/sites/all/modules/pathauto/pathauto.info b/sites/all/modules/pathauto/pathauto.info index 1488faa..9a73e49 100644 --- a/sites/all/modules/pathauto/pathauto.info +++ b/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" diff --git a/sites/all/modules/pathauto/pathauto.install b/sites/all/modules/pathauto/pathauto.install index cd5211c..62a4ed2 100644 --- a/sites/all/modules/pathauto/pathauto.install +++ b/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. */ diff --git a/sites/all/modules/pathauto/pathauto.js b/sites/all/modules/pathauto/pathauto.js index be49cd6..52dcf12 100644 --- a/sites/all/modules/pathauto/pathauto.js +++ b/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 { diff --git a/sites/all/modules/pathauto/pathauto.migrate.inc b/sites/all/modules/pathauto/pathauto.migrate.inc new file mode 100644 index 0000000..bbb9f29 --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.migrate.inc @@ -0,0 +1,56 @@ +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; +} diff --git a/sites/all/modules/pathauto/pathauto.module b/sites/all/modules/pathauto/pathauto.module index dffc292..6305820 100644 --- a/sites/all/modules/pathauto/pathauto.module +++ b/sites/all/modules/pathauto/pathauto.module @@ -26,30 +26,14 @@ define('PATHAUTO_IGNORE_WORDS', 'a, an, as, at, before, but, by, for, from, is, * Implements hook_hook_info(). */ function pathauto_hook_info() { - $info['pathauto'] = array('group' => 'pathauto'); - $info['path_alias_types'] = array('group' => 'pathauto'); - return $info; -} - -/** - * Implements hook_module_implements_alter(). - * - * Adds pathauto support for core modules. - */ -function pathauto_module_implements_alter(&$implementations, $hook) { - $hooks = pathauto_hook_info(); - if (isset($hooks[$hook])) { - $modules = array('node', 'taxonomy', 'user', 'forum', 'blog'); - foreach ($modules as $module) { - if (module_exists($module)) { - $implementations[$module] = TRUE; - } - } - // Move pathauto.module to get included first since it is responsible for - // other modules. - unset($implementations['pathauto']); - $implementations = array_merge(array('pathauto' => 'pathauto'), $implementations); - } + $hooks = array( + 'pathauto', + 'path_alias_types', + 'pathauto_pattern_alter', + 'pathauto_alias_alter', + 'pathauto_is_alias_reserved', + ); + return array_fill_keys($hooks, array('group' => 'pathauto')); } /** @@ -67,6 +51,9 @@ function pathauto_help($path, $arg) { $output .= '

' . t('The maximum alias length and maximum component length values default to 100 and have a limit of @max from Pathauto. This length is limited by the length of the "alias" column of the url_alias database table. The default database schema for this column is @max. If you set a length that is equal to that of the one set in the "alias" column it will cause problems in situations where the system needs to append additional words to the aliased URL. You should enter a value that is the length of the "alias" column minus the length of any strings that might get added to the end of the URL. The length of strings that might get added to the end of your URLs depends on which modules you have enabled and on your Pathauto settings. The recommended and default value is 100.', array('@max' => _pathauto_get_schema_alias_maxlength())) . '
'; $output .= ''; return $output; + case 'admin/config/search/path/update_bulk': + $output = '

' . t('Bulk generation will only generate URL aliases for items that currently have no aliases. This is typically used when installing Pathauto on a site that has existing un-aliased content that needs to be aliased in bulk.') . '

'; + return $output; } } @@ -109,7 +96,7 @@ function pathauto_menu() { 'file' => 'pathauto.admin.inc', ); $items['admin/config/search/path/update_bulk'] = array( - 'title' => 'Bulk update', + 'title' => 'Bulk generate', 'page callback' => 'drupal_get_form', 'page arguments' => array('pathauto_bulk_update_form'), 'access arguments' => array('administer url aliases'), @@ -295,9 +282,19 @@ function pathauto_field_attach_form($entity_type, $entity, &$form, &$form_state, if (!empty($id)) { module_load_include('inc', 'pathauto'); $uri = entity_uri($entity_type, $entity); - $path = drupal_get_path_alias($uri['path'], $langcode); $pathauto_alias = pathauto_create_alias($entity_type, 'return', $uri['path'], array($entity_type => $entity), $bundle, $langcode); - $entity->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias); + if ($pathauto_alias === FALSE) { + // If Pathauto is not going to be able to generate an alias, then we + // should not bother to show the checkbox since it wouldn't do anything. + // Note that if a pattern does apply, but all the tokens currently + // evaluate to empty strings, then $pathauto_alias would equal null and + // not false. + return; + } + else { + $path = drupal_get_path_alias($uri['path'], $langcode); + $entity->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias); + } } else { $entity->path['pathauto'] = TRUE; @@ -341,10 +338,54 @@ function pathauto_field_attach_form($entity_type, $entity, &$form, &$form_state, } } +/** + * Implements hook_entity_load(). + */ +function pathauto_entity_load($entities, $entity_type) { + // Statically cache which entity types have data in the pathauto_state + // table to avoid unnecessary queries for entities that would not have any + // data anyway. + static $loadable_types; + if (!isset($loadable_types)) { + $loadable_types = &drupal_static(__FUNCTION__); + if (!isset($loadable_types)) { + // Prevent errors if pathauto_update_7006() has not yet been run. + if (!db_table_exists('pathauto_state')) { + $loadable_types = array(); + } + else { + $loadable_types = db_query("SELECT DISTINCT entity_type FROM {pathauto_state}")->fetchCol(); + } + } + } + + // Check if this entity type has loadable records. + if (!in_array($entity_type, $loadable_types)) { + return; + } + + $states = pathauto_entity_state_load_multiple($entity_type, array_keys($entities)); + foreach ($states as $id => $state) { + if (!isset($entities[$id]->path)) { + $entities[$id]->path = array(); + } + + if (is_array($entities[$id]->path) && !isset($entities[$id]->path['pathauto'])) { + $entities[$id]->path['pathauto'] = $state; + } + } +} + /** * Implements hook_entity_presave(). */ -function pathauto_entity_presave($entity, $type) { +function pathauto_entity_presave($entity, $entity_type) { + if (isset($entity->path['pathauto']) && is_array($entity->path)) { + // We must set an empty alias string for the path to prevent saving an + // alias. + $entity->path += array('alias' => ''); + } + // About to be saved (before insert/update) if (!empty($entity->path['pathauto']) && isset($entity->path['old_alias']) && $entity->path['alias'] == '' && $entity->path['old_alias'] != '') { @@ -366,6 +407,109 @@ function pathauto_entity_presave($entity, $type) { } } +/** + * Implements hook_entity_insert(). + */ +function pathauto_entity_insert($entity, $entity_type) { + if (isset($entity->path['pathauto'])) { + pathauto_entity_state_save($entity_type, $entity, $entity->path['pathauto']); + } +} + +/** + * Implements hook_entity_update(). + */ +function pathauto_entity_update($entity, $entity_type) { + if (isset($entity->path['pathauto'])) { + pathauto_entity_state_save($entity_type, $entity, $entity->path['pathauto']); + } +} + +/** + * Implements hook_entity_delete(). + */ +function pathauto_entity_delete($entity, $entity_type) { + if (isset($entity->path['pathauto'])) { + pathauto_entity_state_delete($entity_type, $entity); + } +} + +/** + * Load a pathauto state for an entity. + * + * @param string $entity_type + * An entity type. + * @param int $entity_id + * An entity ID. + * + * @return bool + * A value that evaluates to TRUE if Pathauto should control this entity's + * path. A value that evaluates to FALSE if Pathauto should not manage the + * entity's path. + */ +function pathauto_entity_state_load($entity_type, $entity_id) { + $pathauto_state = pathauto_entity_state_load_multiple($entity_type, array($entity_id)); + return !empty($pathauto_state) ? reset($pathauto_state) : FALSE; +} + +/** + * Load a pathauto state for multiple entities. + * + * @param string $entity_type + * The entity type. + * @param int[] $entity_ids + * The array of entity IDs. + * + * @return bool[] + * An array of Pathauto states keyed by entity ID. + */ +function pathauto_entity_state_load_multiple($entity_type, $entity_ids) { + return db_query("SELECT entity_id, pathauto FROM {pathauto_state} WHERE entity_type = :entity_type AND entity_id IN (:entity_ids)", array(':entity_type' => $entity_type, ':entity_ids' => $entity_ids))->fetchAllKeyed(); +} + +/** + * Save the pathauto state for an entity. + * + * @param string $entity_type + * The entity type. + * @param object $entity + * The entity object. + * @param bool $pathauto_state + * A value that evaluates to TRUE means that Pathauto should keep controlling + * this entity's path in the future. A value that evaluates to FALSE means + * that Pathauto should not manage the entity's path. + */ +function pathauto_entity_state_save($entity_type, $entity, $pathauto_state) { + list($entity_id) = entity_extract_ids($entity_type, $entity); + db_merge('pathauto_state') + ->key(array( + 'entity_type' => $entity_type, + 'entity_id' => $entity_id, + )) + ->fields(array( + 'pathauto' => $pathauto_state ? 1 : 0, + )) + ->execute(); + drupal_static_reset('pathauto_entity_load'); +} + +/** + * Delete the pathauto state for an entity. + * + * @param string $entity_type + * The entity type. + * @param object $entity + * The entity object. + */ +function pathauto_entity_state_delete($entity_type, $entity) { + list($entity_id) = entity_extract_ids($entity_type, $entity); + db_delete('pathauto_state') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->execute(); + drupal_static_reset('pathauto_entity_load'); +} + /** * Implements hook_action_info(). */ @@ -374,16 +518,19 @@ function pathauto_action_info() { 'type' => 'node', 'label' => t('Update node alias'), 'configurable' => FALSE, + 'triggers' => array(), ); $info['pathauto_taxonomy_term_update_action'] = array( 'type' => 'taxonomy_term', 'label' => t('Update taxonomy term alias'), 'configurable' => FALSE, + 'triggers' => array(), ); $info['pathauto_user_update_action'] = array( 'type' => 'user', 'label' => t('Update user alias'), 'configurable' => FALSE, + 'triggers' => array(), ); return $info; @@ -393,7 +540,7 @@ function pathauto_action_info() { * Returns the language code of the given entity. * * Backward compatibility layer to ensure that installations running an older - * version of core where entity_language() is not avilable do not break. + * version of core where entity_language() is not available do not break. * * @param string $entity_type * An entity type. @@ -417,6 +564,46 @@ function pathauto_entity_language($entity_type, $entity, $check_language_propert return !empty($langcode) ? $langcode : LANGUAGE_NONE; } +function pathauto_is_alias_reserved($alias, $source, $langcode = LANGUAGE_NONE) { + foreach (module_implements('pathauto_is_alias_reserved') as $module) { + $result = module_invoke($module, 'pathauto_is_alias_reserved', $alias, $source, $langcode); + if (!empty($result)) { + // As soon as the first module says that an alias is in fact reserved, + // then there is no point in checking the rest of the modules. + return TRUE; + } + } + + return FALSE; +} + +/** + * Implements hook_pathauto_is_alias_reserved() on behalf of path.module. + */ +function path_pathauto_is_alias_reserved($alias, $source, $langcode) { + // For language neutral content, we need to make sure the alias doesn't + // collide with any existing aliases. For localized content, just make sure + // it doesn't collide with same language or language neutral aliases. + $query = db_select('url_alias', 'ua') + ->fields('ua', array('pid')) + ->condition('source', $source, '<>') + ->condition('alias', $alias); + + if ($langcode != LANGUAGE_NONE) { + $query->condition('language', array($langcode, LANGUAGE_NONE), 'IN'); + } + + return $query->execute()->rowCount() > 0; +} + +/** + * Implements hook_pathauto_is_alias_reserved(). + */ +function pathauto_pathauto_is_alias_reserved($alias, $source, $langcode) { + module_load_include('inc', 'pathauto'); + return _pathauto_path_is_callback($alias); +} + if (!function_exists('path_field_extra_fields')) { /** * Implements hook_field_extra_fields() on behalf of path.module. @@ -459,6 +646,47 @@ function path_field_extra_fields() { * @{ */ +/** + * Implements hook_path_alias_types() on behalf of node module. + */ +function node_path_alias_types() { + return array('node/' => t('Content')); +} + +/** + * Implements hook_pathauto() on behalf of node module. + */ +function node_pathauto($op) { + if ($op == '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; + } +} + /** * Implements hook_node_insert(). */ @@ -517,20 +745,20 @@ function pathauto_node_operations() { */ function pathauto_node_update_alias(stdClass $node, $op, array $options = array()) { // Skip processing if the user has disabled pathauto for the node. - if (isset($node->path['pathauto']) && empty($node->path['pathauto'])) { - return; + if (isset($node->path['pathauto']) && empty($node->path['pathauto']) && empty($options['force'])) { + return FALSE; } $options += array('language' => pathauto_entity_language('node', $node)); // Skip processing if the node has no pattern. if (!pathauto_pattern_load_by_entity('node', $node->type, $options['language'])) { - return; + return FALSE; } module_load_include('inc', 'pathauto'); $uri = entity_uri('node', $node); - pathauto_create_alias('node', $op, $uri['path'], array('node' => $node), $node->type, $options['language']); + return pathauto_create_alias('node', $op, $uri['path'], array('node' => $node), $node->type, $options['language']); } /** @@ -573,6 +801,42 @@ function pathauto_node_update_action($node, $context = array()) { * @{ */ +/** + * Implements hook_path_alias_types() on behalf of taxonomy module. + */ +function taxonomy_path_alias_types() { + return array('taxonomy/term/' => t('Taxonomy terms')); +} + +/** + * Implements hook_pathauto() on behalf of taxonomy module. + */ +function taxonomy_pathauto($op) { + if ($op == '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; + } +} + /** * Implements hook_taxonomy_term_insert(). */ @@ -617,8 +881,8 @@ function pathauto_form_taxonomy_form_term_alter(&$form, $form_state) { */ function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options = array()) { // Skip processing if the user has disabled pathauto for the term. - if (isset($term->path['pathauto']) && empty($term->path['pathauto'])) { - return; + if (isset($term->path['pathauto']) && empty($term->path['pathauto']) && empty($options['force'])) { + return FALSE; } $module = 'taxonomy_term'; @@ -627,7 +891,7 @@ function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options $module = 'forum'; } else { - return; + return FALSE; } } @@ -644,21 +908,22 @@ function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options // Skip processing if the term has no pattern. if (!pathauto_pattern_load_by_entity($module, $term->vocabulary_machine_name)) { - return; + return FALSE; } module_load_include('inc', 'pathauto'); $uri = entity_uri('taxonomy_term', $term); - pathauto_create_alias($module, $op, $uri['path'], array('term' => $term), $term->vocabulary_machine_name, $options['language']); + $result = pathauto_create_alias($module, $op, $uri['path'], array('term' => $term), $term->vocabulary_machine_name, $options['language']); if (!empty($options['alias children'])) { // For all children generate new aliases. - $options['alias children'] = FALSE; unset($options['language']); - foreach (taxonomy_get_tree($term->vid, $term->tid) as $subterm) { + foreach (taxonomy_get_children($term->tid, $term->vid) as $subterm) { pathauto_taxonomy_term_update_alias($subterm, $op, $options); } } + + return $result; } /** @@ -696,11 +961,68 @@ function pathauto_taxonomy_term_update_action($term, $context = array()) { * @} End of "name pathauto_taxonomy". */ +/** + * @name pathauto_forum Pathauto integration for the core forum module. + * @{ + */ + +/** + * Implements hook_path_alias_types() on behalf of forum module. + */ +function forum_path_alias_types() { + return array('forum/' => t('Forums')); +} + +/** + * Implements hook_pathauto() for forum module. + */ +function forum_pathauto($op) { + if ($op == '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; + } +} + +/** + * @} End of "name pathauto_forum". + */ + /** * @name pathauto_user Pathauto integration for the core user and blog modules. * @{ */ +/** + * Implements hook_path_alias_types() on behalf of user module. + */ +function user_path_alias_types() { + return array('user/' => t('Users')); +} + +/** + * Implements hook_pathauto() on behalf of user module. + */ +function user_pathauto($op) { + if ($op == '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; + } +} + /** * Implements hook_user_insert(). */ @@ -748,8 +1070,8 @@ function pathauto_user_operations() { */ function pathauto_user_update_alias(stdClass $account, $op, array $options = array()) { // Skip processing if the user has disabled pathauto for the account. - if (isset($account->path['pathauto']) && empty($account->path['pathauto'])) { - return; + if (isset($account->path['pathauto']) && empty($account->path['pathauto']) && empty($options['force'])) { + return FALSE; } $options += array( @@ -761,17 +1083,19 @@ function pathauto_user_update_alias(stdClass $account, $op, array $options = arr // Skip processing if the account has no pattern. if (!pathauto_pattern_load_by_entity('user', '', $options['language'])) { - return; + return FALSE; } module_load_include('inc', 'pathauto'); $uri = entity_uri('user', $account); - pathauto_create_alias('user', $op, $uri['path'], array('user' => $account), NULL, $options['language']); + $return = pathauto_create_alias('user', $op, $uri['path'], array('user' => $account), NULL, $options['language']); // Because blogs are also associated with users, also generate the blog paths. if (!empty($options['alias blog'])) { pathauto_blog_update_alias($account, $op, $options); } + + return $return; } /** @@ -805,6 +1129,39 @@ function pathauto_user_update_action($account, $context = array()) { pathauto_user_update_alias($account, 'bulkupdate', array('message' => TRUE)); } +/** + * @} End of "name pathauto_user". + */ + +/** + * @name pathauto_blog Pathauto integration for the core blog module. + * @{ + */ + +/** + * Implements hook_path_alias_types() on behalf of blog module. + */ +function blog_path_alias_types() { + return array('blog/' => t('User blogs')); +} + +/** + * Implements hook_pathauto() on behalf of blog module. + */ +function blog_pathauto($op) { + if ($op == '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; + } +} + /** * Update the blog URL aliases for an individual user account. * @@ -819,7 +1176,7 @@ function pathauto_user_update_action($account, $context = array()) { function pathauto_blog_update_alias(stdClass $account, $op, array $options = array()) { // Skip processing if the blog has no pattern. if (!pathauto_pattern_load_by_entity('blog')) { - return; + return FALSE; } $options += array( @@ -828,7 +1185,7 @@ function pathauto_blog_update_alias(stdClass $account, $op, array $options = arr module_load_include('inc', 'pathauto'); if (node_access('create', 'blog', $account)) { - pathauto_create_alias('blog', $op, "blog/{$account->uid}", array('user' => $account), NULL, $options['language']); + return pathauto_create_alias('blog', $op, "blog/{$account->uid}", array('user' => $account), NULL, $options['language']); } else { pathauto_path_delete_all("blog/{$account->uid}"); @@ -836,5 +1193,30 @@ function pathauto_blog_update_alias(stdClass $account, $op, array $options = arr } /** - * @} End of "name pathauto_user". + * @} End of "name pathauto_blog". */ + +/** + * Implements hook_features_pipe_COMPONENT_alter(). + */ +function pathauto_features_pipe_node_alter(&$pipe, $data, $export) { + foreach ($data as $node_type) { + $pipe['variable'][] = "pathauto_node_{$node_type}_pattern"; + if (module_exists('locale')) { + $langcodes = array_keys(locale_language_list('name')); + $langcodes[] = LANGUAGE_NONE; + foreach ($langcodes as $langcode) { + $pipe['variable'][] = "pathauto_node_{$node_type}_{$langcode}_pattern"; + } + } + } +} + +/** + * Implements hook_features_pipe_COMPONENT_alter(). + */ +function pathauto_features_pipe_taxonomy_alter(&$pipe, $data, $export) { + foreach ($data as $vocabulary) { + $pipe['variable'][] = "pathauto_taxonomy_term_{$vocabulary}_pattern"; + } +} diff --git a/sites/all/modules/pathauto/pathauto.pathauto.inc b/sites/all/modules/pathauto/pathauto.pathauto.inc index 28d3ce0..23df259 100644 --- a/sites/all/modules/pathauto/pathauto.pathauto.inc +++ b/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. */ diff --git a/sites/all/modules/pathauto/pathauto.test b/sites/all/modules/pathauto/pathauto.test index 6fa096e..298f882 100644 --- a/sites/all/modules/pathauto/pathauto.test +++ b/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); } } diff --git a/sites/all/modules/pathauto/pathauto.tokens.inc b/sites/all/modules/pathauto/pathauto.tokens.inc index a0f0b49..dabcaa8 100644 --- a/sites/all/modules/pathauto/pathauto.tokens.inc +++ b/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); diff --git a/sites/all/modules/token/README.txt b/sites/all/modules/token/README.txt index 230774e..d393fb2 100644 --- a/sites/all/modules/token/README.txt +++ b/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) \ No newline at end of file diff --git a/sites/all/modules/token/tests/token_test.info b/sites/all/modules/token/tests/token_test.info index c9d385b..02d9379 100644 --- a/sites/all/modules/token/tests/token_test.info +++ b/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" diff --git a/sites/all/modules/token/tests/token_test.tokens.inc b/sites/all/modules/token/tests/token_test.tokens.inc new file mode 100644 index 0000000..19cbbb0 --- /dev/null +++ b/sites/all/modules/token/tests/token_test.tokens.inc @@ -0,0 +1,13 @@ + t('A test token with colons in the name'), + 'description' => NULL, + ); + + return $info; +} diff --git a/sites/all/modules/token/token.info b/sites/all/modules/token/token.info index c454abf..afa8c62 100644 --- a/sites/all/modules/token/token.info +++ b/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" diff --git a/sites/all/modules/token/token.install b/sites/all/modules/token/token.install index c2b57fd..24d09f3 100644 --- a/sites/all/modules/token/token.install +++ b/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' => '

' . implode('

', $token_problems) . '

', //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']"; } } diff --git a/sites/all/modules/token/token.js b/sites/all/modules/token/token.js index 88a75c1..98d1ac3 100644 --- a/sites/all/modules/token/token.js +++ b/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 = $('').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'); } diff --git a/sites/all/modules/token/token.module b/sites/all/modules/token/token.module index f5ebdab..bbca2aa 100644 --- a/sites/all/modules/token/token.module +++ b/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] diff --git a/sites/all/modules/token/token.pages.inc b/sites/all/modules/token/token.pages.inc index dbe3f8f..4fe3968 100644 --- a/sites/all/modules/token/token.pages.inc +++ b/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 '' . drupal_get_css() . drupal_get_js() . ''; + print '' . drupal_get_css() . drupal_get_js() . ''; print '' . $output . ''; 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'])) { diff --git a/sites/all/modules/token/token.test b/sites/all/modules/token/token.test index 59fa645..cd09e15 100644 --- a/sites/all/modules/token/token.test +++ b/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]', diff --git a/sites/all/modules/token/token.tokens.inc b/sites/all/modules/token/token.tokens.inc index e0c0b5e..f584bda 100644 --- a/sites/all/modules/token/token.tokens.inc +++ b/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']);