'%s' AND dst = '%s' AND language IN ('%s', '') ORDER BY language DESC, pid DESC", $source, $alias, $language, 0, 1)); if (module_exists('path_redirect') && function_exists('path_redirect_delete_multiple')) { // Delete from path_redirect the exact same alias to the same node. path_redirect_delete_multiple(NULL, array('source' => $alias, 'redirect' => $source)); // If there still is this alias used in path_redirect, then create a different alias. $redirects = path_redirect_load_multiple(NULL, array('source' => $alias)); } if ($pid || !empty($redirects)) { return TRUE; } else { return FALSE; } } /** * Fetches an existing URL alias given a path and optional language. * * @param $source * An internal Drupal path. * @param $language * An optional language code to look up the path in. * @return * FALSE if no alias was found or an associative array containing the * following keys: * - pid: Unique path alias identifier. * - alias: The URL alias. */ function _pathauto_existing_alias_data($source, $language = '') { return db_fetch_array(db_query_range("SELECT pid, dst AS alias, language FROM {url_alias} WHERE src = '%s' AND language IN ('%s', '') ORDER BY language DESC, pid DESC", $source, $language, 0, 1)); } /** * Clean up a string segment to be used in an URL alias. * * Performs the following possible alterations: * - Remove all HTML tags. * - Process the string through the transliteration module. * - Replace or remove punctuation with the separator character. * - Remove back-slashes. * - Replace non-ascii and non-numeric characters with the separator. * - Remove common words. * - Replace whitespace with the separator character. * - Trim duplicate, leading, and trailing separators. * - Convert to lower-case. * - Shorten to a desired length and logical position based on word boundaries. * * This function should *not* be called on URL alias or path strings because it * is assumed that they are already clean. * * @param $string * A string to clean. * @param 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 * The cleaned string. */ function pathauto_cleanstring($string) { // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['cache'] = &pathauto_static(__FUNCTION__); } $cache = &$drupal_static_fast['cache']; // Generate and cache variables used in this function so that on the second // call to pathauto_cleanstring() we focus on processing. if (!isset($cache)) { $cache = array( 'separator' => variable_get('pathauto_separator', '-'), 'strings' => array(), 'transliterate' => variable_get('pathauto_transliterate', FALSE) && module_exists('transliteration'), 'punctuation' => array(), 'reduce_ascii' => (bool) variable_get('pathauto_reduce_ascii', FALSE), 'ignore_words_regex' => FALSE, 'lowercase' => (bool) variable_get('pathauto_case', PATHAUTO_CASE_LOWER), 'maxlength' => min(variable_get('pathauto_max_component_length', 100), _pathauto_get_schema_alias_maxlength()), ); // Generate and cache the punctuation replacements for strtr(). $punctuation = pathauto_punctuation_chars(); foreach ($punctuation as $name => $details) { $action = variable_get('pathauto_punctuation_' . $name, PATHAUTO_PUNCTUATION_REMOVE); switch ($action) { case PATHAUTO_PUNCTUATION_REMOVE: $cache['punctuation'][$details['value']] = ''; break; case PATHAUTO_PUNCTUATION_REPLACE: $cache['punctuation'][$details['value']] = $cache['separator']; break; case PATHAUTO_PUNCTUATION_DO_NOTHING: // Literally do nothing. break; } } // Generate and cache the ignored words regular expression. $ignore_words = variable_get('pathauto_ignore_words', PATHAUTO_IGNORE_WORDS); $ignore_words_regex = preg_replace(array('/^[,\s]+|[,\s]+$/', '/[,\s]+/'), array('', '\b|\b'), $ignore_words); if ($ignore_words_regex) { $cache['ignore_words_regex'] = '\b' . $ignore_words_regex . '\b'; if (function_exists('mb_eregi_replace')) { $cache['ignore_words_callback'] = 'mb_eregi_replace'; } else { $cache['ignore_words_callback'] = 'preg_replace'; $cache['ignore_words_regex'] = '/' . $cache['ignore_words_regex'] . '/i'; } } } // Empty strings do not need any proccessing. 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'][$langcode][$string])) { return $cache['strings'][$langcode][$string]; } // Remove all HTML tags from the string. $output = strip_tags(decode_entities($string)); // Optionally transliterate (by running through the Transliteration module) if ($cache['transliterate']) { $output = transliteration_get($output, $cache['reduce_ascii'] ? '' : '?', $langcode); } // Replace or drop punctuation based on user settings $output = strtr($output, $cache['punctuation']); // Reduce strings to letters and numbers if ($cache['reduce_ascii']) { $output = preg_replace('/[^a-zA-Z0-9\/]+/', $cache['separator'], $output); } // Get rid of words that are on the ignore list if ($cache['ignore_words_regex']) { $words_removed = $cache['ignore_words_callback']($cache['ignore_words_regex'], '', $output); if (drupal_strlen(trim($words_removed)) > 0) { $output = $words_removed; } } // Always replace whitespace with the separator. $output = preg_replace('/\s+/', $cache['separator'], $output); // Trim duplicates and remove trailing and leading separators. $output = _pathauto_clean_separators($output, $cache['separator']); // Optionally convert to lower case. if ($cache['lowercase']) { $output = drupal_strtolower($output); } // Shorten to a logical place based on word boundaries. $output = pathauto_truncate_utf8($output, $cache['maxlength'], TRUE); // Cache this result in the static array. $cache['strings'][$langcode][$string] = $output; return $output; } /** * Trims duplicate, leading, and trailing separators from a string. * * @param $string * The string to clean path separators from. * @param $separator * The path separator to use when cleaning. * @return * The cleaned version of the string. * * @see pathauto_cleanstring() * @see pathauto_clean_alias() */ function _pathauto_clean_separators($string, $separator = NULL) { static $default_separator; if (!isset($separator)) { if (!isset($default_separator)) { $default_separator = variable_get('pathauto_separator', '-'); } $separator = $default_separator; } $output = $string; if (strlen($separator)) { // Trim any leading or trailing separators. $output = trim($output, $separator); // 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; } /** * Clean up an URL alias. * * Performs the following alterations: * - Trim duplicate, leading, and trailing back-slashes. * - Trim duplicate, leading, and trailing separators. * - Shorten to a desired length and logical position based on word boundaries. * * @param $alias * A string with the URL alias to clean up. * @return * The cleaned URL alias. */ function pathauto_clean_alias($alias) { $cache = &pathauto_static(__FUNCTION__); if (!isset($cache)) { $cache = array( 'maxlength' => min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength()), ); } $output = $alias; // 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 = pathauto_truncate_utf8($output, $cache['maxlength'], TRUE); return $output; } /** * Apply patterns to create an alias. * * @param $module * The name of your module (e.g., 'node'). * @param $op * Operation being performed on the content being aliased * ('insert', 'update', 'return', or 'bulkupdate'). * @param $source * An internal Drupal path to be aliased. * @param $data * An array of keyed objects to pass to token_replace(). For simple * replacement scenarios 'node', 'user', and others are common keys, with an * accompanying node or user object being the value. Only one key/value pair * is supported, otherwise you may pass the results from * pathauto_get_placeholders() here. * @param $entity_id * (deprecated) The entity ID (node ID, user ID, etc.). This parameter is * deprecated and is not actually used. * @param $type * For modules which provided pattern items in hook_pathauto(), * the relevant identifier for the specific item to be aliased * (e.g., $node->type). * @param $language * A string specify the path's language. * @return * The alias that was created. * * @see _pathauto_set_alias() * @see pathauto_get_placeholders() */ function pathauto_create_alias($module, $op, $source, $data, $entity_id = NULL, $type = NULL, $language = '') { // 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 ''; } // Support for when $source and $placeholders were swapped. if (is_array($source) && is_string($data)) { $placeholders = $source; $source = $data; } elseif (is_array($data) && !isset($data['tokens']) && !isset($data['values'])) { $placeholders = pathauto_get_placeholders(key($data), current($data), $pattern, array('language' => (object) array('language' => $language))); } else { $placeholders = $data; } // Special handling when updating an item which is already aliased. $existing_alias = NULL; if ($op == 'update' || $op == 'bulkupdate') { 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 ''; } } } // Replace the placeholders with the values provided by the module. $alias = str_replace($placeholders['tokens'], $placeholders['values'], $pattern); // Check if the token replacement has not actually replaced any values. If // that is the case, then stop because we should not generate an alias. // @see token_scan() $pattern_tokens_removed = preg_replace('/\[([^\s]+?)\]/', '', $pattern); if ($alias === $pattern_tokens_removed) { return ''; } $alias = pathauto_clean_alias($alias); // Allow other modules to alter the alias. $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 ''; } // If the alias already exists, generate a new, hopefully unique, variant. $original_alias = $alias; pathauto_alias_uniquify($alias, $source, $language); if ($original_alias != $alias) { // Alert the user why this happened. _pathauto_verbose(t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array( '%original_alias' => $original_alias, '%alias' => $alias, )), $op); } // Return the generated alias if requested. if ($op == 'return') { return $alias; } // Build the new path alias array and send it off to be created. $path = array( 'source' => $source, 'alias' => $alias, 'language' => $language, ); $success = _pathauto_set_alias($path, $existing_alias, $op); return $success ? $alias : NULL; } /** * Check to ensure a path alias is unique and add suffix variants if necessary. * * Given an alias 'content/test' if a path alias with the exact alias already * exists, the function will change the alias to 'content/test-0' and will * increase the number suffix until it finds a unique alias. * * @param $alias * A string with the alias. Can be altered by reference. * @param $source * A string with the path source. * @param $langcode * A string with a language code. */ function pathauto_alias_uniquify(&$alias, $source, $langcode) { if (!_pathauto_alias_exists($alias, $source, $langcode)) { return; } // If the alias already exists, generate a new, hopefully unique, variant $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength()); $separator = variable_get('pathauto_separator', '-'); $original_alias = $alias; $i = 0; do { // Append an incrementing numeric suffix until we find a unique alias. $unique_suffix = $separator . $i; $alias = pathauto_truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix), TRUE) . $unique_suffix; $i++; } while (_pathauto_alias_exists($alias, $source, $langcode)); } /** * Verify if the given path is a valid menu callback. * * Taken from menu_execute_active_handler(). * * @param $path * A string containing a relative path. * @return * TRUE if the path already exists. */ function _pathauto_path_is_callback($path) { $menu = menu_get_item($path); if (isset($menu['path']) && $menu['path'] == $path) { return TRUE; } elseif (is_file('./' . $path) || is_dir('./' . $path)) { // Do not allow existing files or directories to get assigned an automatic // alias. Note that we do not need to use is_link() to check for symbolic // links since this returns TRUE for either is_file() or is_dir() already. return TRUE; } return FALSE; } /** * Private function for Pathauto to create an alias. * * @param $path * An associative array containing the following keys: * - source: The internal system path. * - alias: The URL alias. * - pid: (optional) Unique path alias identifier. * - language: (optional) The language of the alias. * @param $existing_alias * (optional) An associative array of the existing path alias. * @param $op * An optional string with the operation being performed. * * @return * The saved path from path_save() or NULL if the path was not saved. * * @see path_set_alias() */ function _pathauto_set_alias($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; } // Skip replacing the current alias with an identical alias if (empty($existing_alias) || $existing_alias['alias'] != $path['alias']) { $path += array('pid' => NULL, 'language' => ''); // If there is already an alias, respect some update actions. if (!empty($existing_alias)) { switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) { case PATHAUTO_UPDATE_ACTION_NO_NEW: // Do not create the alias. return; case PATHAUTO_UPDATE_ACTION_LEAVE: // Create a new alias instead of overwriting the existing by leaving // $path['pid'] empty. break; case PATHAUTO_UPDATE_ACTION_REDIRECT: // Create a redirect if (module_exists('path_redirect') && function_exists('path_redirect_save')) { $redirect = array( 'source' => $existing_alias['alias'], 'language' => $existing_alias['language'], 'redirect' => $path['source'], ); path_redirect_save($redirect); } // Intentionally fall through to the next condition since we still // want to replace the existing alias. case PATHAUTO_UPDATE_ACTION_DELETE: // Both the redirect and delete actions should overwrite the existing // alias. $path['pid'] = $existing_alias['pid']; break; } } // Save the path array. path_set_alias($path['source'], $path['alias'], $path['pid'], $path['language']); if ($verbose) { if (!empty($redirect)) { _pathauto_verbose(t('Created new alias %alias for %source, replacing %old_alias. %old_alias now redirects to %alias.', array('%alias' => $path['alias'], '%source' => $path['source'], '%old_alias' => $existing_alias['alias']))); } elseif (!empty($existing_alias['pid'])) { _pathauto_verbose(t('Created new alias %alias for %source, replacing %old_alias.', array('%alias' => $path['alias'], '%source' => $path['source'], '%old_alias' => $existing_alias['alias']))); } else { _pathauto_verbose(t('Created new alias %alias for %source.', array('%alias' => $path['alias'], '%source' => $path['source']))); } } return $path; } } /** * Output a helpful message if verbose output is enabled. * * Verbose output is only enabled when: * - The 'pathauto_verbose' setting is enabled. * - The current user has the 'notify of path changes' permission. * - The $op parameter is anything but 'bulkupdate' or 'return'. * * @param $message * An optional string of the verbose message to display. This string should * already be run through t(). * @param $op * An optional string with the operation being performed. * @return * TRUE if verbose output is enabled, or FALSE otherwise. */ function _pathauto_verbose($message = NULL, $op = NULL) { static $verbose; if (!isset($verbose)) { $verbose = variable_get('pathauto_verbose', FALSE) && user_access('notify of path changes'); } if (!$verbose || (isset($op) && in_array($op, array('bulkupdate', 'return')))) { return FALSE; } if ($message) { drupal_set_message($message); } return $verbose; } /** * Generalized function to get tokens across all Pathauto types. * * @param $type * The token type to pass to token_get_values(). * @param $object * The object to pass to token_get_values(). * @param $text * (optional) The string that will be replaced with tokens. If provided * and the token_scan() function is available it will be used to trim down * the number of tokens that need to be passed to * pathauto_clean_token_values(). * * @return * Tokens for that object formatted in the way that * Pathauto expects to see them. */ function pathauto_get_placeholders($type, $object, $text = '', array $options = array()) { $options += array('pathauto' => TRUE); $full = token_get_values($type, $object, TRUE, $options); // Attempt to reduce the tokens to only the ones that will actually be used // before passing into pathauto_clean_token_values(). static $token_scan_exists; if (!isset($token_scan_exists)) { $token_scan_exists = function_exists('token_scan'); } if ($token_scan_exists && !empty($text)) { $used_tokens = token_scan($text); foreach ($full->tokens as $index => $token) { if (!in_array($token, $used_tokens)) { unset($full->tokens[$index]); unset($full->values[$index]); } } } $tokens = token_prepare_tokens($full->tokens); $values = pathauto_clean_token_values($full, $options); return array('tokens' => $tokens, 'values' => $values); } /** * Clean tokens so they are URL friendly. * * @param $full * An array of token values from token_get_values() that need to be "cleaned" * for use in the URL. * * @return * An array of the cleaned tokens. */ function pathauto_clean_token_values($full, $options = array()) { $replacements = array(); foreach ($full->values as $key => $value) { $token = $full->tokens[$key]; if (strpos($token, 'path') !== FALSE && is_array($value) && !empty($options['pathauto'])) { // If the token name contains 'path', the token value is an array, and // the 'pathauto' option was passed to token_get_values(), then the token // should have each segment cleaned, and then glued back together to // construct a value resembling an URL. $segments = array(); foreach ($value as $segment) { $segments[] = pathauto_cleanstring($segment, $options); } $replacements[$token] = implode('/', $segments); } elseif (preg_match('/(path|alias|url|url-brief)(-raw)?$/', $token)) { // Token name matches an URL-type name and should be left raw. $replacements[$token] = $value; } else { // Token is not an URL, so it should have its value cleaned. $replacements[$token] = pathauto_cleanstring($value, $options); } } return $replacements; } /** * Return an array of arrays for punctuation values. * * Returns an array of arrays for punctuation values keyed by a name, including * the value and a textual description. * Can and should be expanded to include "all" non text punctuation values. * * @return * An array of arrays for punctuation values keyed by a name, including the * value and a textual description. */ function pathauto_punctuation_chars() { $punctuation = &pathauto_static(__FUNCTION__); if (!isset($punctuation)) { $cid = 'pathauto:punctuation:' . $GLOBALS['language']->language; if ($cache = cache_get($cid)) { $punctuation = $cache->data; } else { $punctuation = array(); $punctuation['double_quotes'] = array('value' => '"', 'name' => t('Double quotation marks')); $punctuation['quotes'] = array('value' => '\'', 'name' => t("Single quotation marks (apostrophe)")); $punctuation['backtick'] = array('value' => '`', 'name' => t('Back tick')); $punctuation['comma'] = array('value' => ',', 'name' => t('Comma')); $punctuation['period'] = array('value' => '.', 'name' => t('Period')); $punctuation['hyphen'] = array('value' => '-', 'name' => t('Hyphen')); $punctuation['underscore'] = array('value' => '_', 'name' => t('Underscore')); $punctuation['colon'] = array('value' => ':', 'name' => t('Colon')); $punctuation['semicolon'] = array('value' => ';', 'name' => t('Semicolon')); $punctuation['pipe'] = array('value' => '|', 'name' => t('Vertical bar (pipe)')); $punctuation['left_curly'] = array('value' => '{', 'name' => t('Left curly bracket')); $punctuation['left_square'] = array('value' => '[', 'name' => t('Left square bracket')); $punctuation['right_curly'] = array('value' => '}', 'name' => t('Right curly bracket')); $punctuation['right_square'] = array('value' => ']', 'name' => t('Right square bracket')); $punctuation['plus'] = array('value' => '+', 'name' => t('Plus sign')); $punctuation['equal'] = array('value' => '=', 'name' => t('Equal sign')); $punctuation['asterisk'] = array('value' => '*', 'name' => t('Asterisk')); $punctuation['ampersand'] = array('value' => '&', 'name' => t('Ampersand')); $punctuation['percent'] = array('value' => '%', 'name' => t('Percent sign')); $punctuation['caret'] = array('value' => '^', 'name' => t('Caret')); $punctuation['dollar'] = array('value' => '$', 'name' => t('Dollar sign')); $punctuation['hash'] = array('value' => '#', 'name' => t('Number sign (pound sign, hash)')); $punctuation['at'] = array('value' => '@', 'name' => t('At sign')); $punctuation['exclamation'] = array('value' => '!', 'name' => t('Exclamation mark')); $punctuation['tilde'] = array('value' => '~', 'name' => t('Tilde')); $punctuation['left_parenthesis'] = array('value' => '(', 'name' => t('Left parenthesis')); $punctuation['right_parenthesis'] = array('value' => ')', 'name' => t('Right parenthesis')); $punctuation['question_mark'] = array('value' => '?', 'name' => t('Question mark')); $punctuation['less_than'] = array('value' => '<', 'name' => t('Less-than sign')); $punctuation['greater_than'] = array('value' => '>', 'name' => t('Greater-than sign')); $punctuation['slash'] = array('value' => '/', 'name' => t('Slash')); $punctuation['back_slash'] = array('value' => '\\', 'name' => t('Backslash')); // Allow modules to alter the punctuation list and cache the result. drupal_alter('pathauto_punctuation_chars', $punctuation); cache_set($cid, $punctuation); } } return $punctuation; } /** * Fetch the maximum length of the {url_alias}.dst field from the schema. * * @return * An integer of the maximum URL alias length allowed by the database. */ function _pathauto_get_schema_alias_maxlength() { static $maxlength; if (!isset($maxlength)) { $schema = drupal_get_schema('url_alias'); $maxlength = $schema['fields']['dst']['length']; } return $maxlength; } /** * Truncates a UTF-8-encoded string safely to a number of characters. * * This is a functional backport of Drupal 7's truncate_utf8(). * * @param $string * The string to truncate. * @param $max_length * An upper limit on the returned string length, including trailing ellipsis * if $add_ellipsis is TRUE. * @param $wordsafe * If TRUE, attempt to truncate on a word boundary. Word boundaries are * spaces, punctuation, and Unicode characters used as word boundaries in * non-Latin languages; see PREG_CLASS_UNICODE_WORD_BOUNDARY for more * information. If a word boundary cannot be found that would make the length * of the returned string fall within length guidelines (see parameters * $max_return_length and $min_wordsafe_length), word boundaries are ignored. * @param $add_ellipsis * If TRUE, add t('...') to the end of the truncated string (defaults to * FALSE). The string length will still fall within $max_return_length. * @param $min_wordsafe_length * If $wordsafe is TRUE, the minimum acceptable length for truncation (before * adding an ellipsis, if $add_ellipsis is TRUE). Has no effect if $wordsafe * is FALSE. This can be used to prevent having a very short resulting string * that will not be understandable. For instance, if you are truncating the * string "See myverylongurlexample.com for more information" to a word-safe * return length of 20, the only available word boundary within 20 characters * is after the word "See", which wouldn't leave a very informative string. If * you had set $min_wordsafe_length to 10, though, the function would realize * that "See" alone is too short, and would then just truncate ignoring word * boundaries, giving you "See myverylongurl..." (assuming you had set * $add_ellipses to TRUE). * * @return * The truncated string. */ function pathauto_truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) { $ellipsis = ''; $max_length = max($max_length, 0); $min_wordsafe_length = max($min_wordsafe_length, 0); if (drupal_strlen($string) <= $max_length) { // No truncation needed, so don't add ellipsis, just return. return $string; } if ($add_ellipsis) { // Truncate ellipsis in case $max_length is small. $ellipsis = drupal_substr(t('...'), 0, $max_length); $max_length -= drupal_strlen($ellipsis); $max_length = max($max_length, 0); } if ($max_length <= $min_wordsafe_length) { // Do not attempt word-safe if lengths are bad. $wordsafe = FALSE; } if ($wordsafe) { $matches = array(); // Find the last word boundary, if there is one within $min_wordsafe_length // to $max_length characters. preg_match() is always greedy, so it will // find the longest string possible. $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . PATHAUTO_PREG_CLASS_UNICODE_WORD_BOUNDARY . ']/u', $string, $matches); if ($found) { $string = $matches[1]; } else { $string = drupal_substr($string, 0, $max_length); } } else { $string = drupal_substr($string, 0, $max_length); } if ($add_ellipsis) { $string .= $ellipsis; } return $string; } /** * Fetch an array of non-raw tokens that have matching raw tokens. * * @return * An array of tokens. */ function _pathauto_get_raw_tokens() { static $raw_tokens; if (!isset($raw_tokens)) { $raw_tokens = array(); // Build one big list of tokens. foreach (token_get_list('all') as $tokens) { $raw_tokens = array_merge($raw_tokens, array_keys($tokens)); } // Filter out any tokens without -raw as a suffix. foreach ($raw_tokens as $index => $token) { if (substr($token, -4) !== '-raw') { unset($raw_tokens[$index]); } } array_unique($raw_tokens); } return $raw_tokens; }