:source AND language = :lang", 0, 1, array(':alias' => $alias, ':source' => $source, ':lang' => $language))->fetchColumn(); 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 ($alias_pid || !empty($redirects)) { return TRUE; } else { return FALSE; } } /** * Returns old alias and pid if there is already an alias * pointing to a different item. * * @param $src * A string that is the internal path. * @return * An array with the keys "pid" and "old_alias" containing * the "pid" and old "alias", respectively, of the old alias. */ function _pathauto_existing_alias_data($source) { $output = array( 'pid' => '', 'old_alias' => '' ); $result = db_query("SELECT pid, alias FROM {url_alias} WHERE source = :source", array(':source' => $source)); if ($data = $result->fetchObject()) { // The item is already aliased, check what to do... switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) { // Replace old alias - remember the pid to update case PATHAUTO_UPDATE_ACTION_DELETE: case PATHAUTO_UPDATE_ACTION_REDIRECT: $output['pid'] = $data->pid; // Add new alias in addition to old one case PATHAUTO_UPDATE_ACTION_LEAVE: $output['old_alias'] = $data->alias; break; // Do nothing case PATHAUTO_UPDATE_ACTION_NO_NEW: default: break; } } return $output; } /** * Clean up a string value provided by a module. * * Resulting string contains only alphanumerics and separators. * * @param $string * A string to clean. * @param $clean_slash * Whether to clean slashes from the given string. * @return * The cleaned string. */ function pathauto_cleanstring($string, $clean_slash = TRUE) { // Default words to ignore $ignore_words = array( 'a', 'an', 'as', 'at', 'before', 'but', 'by', 'for', 'from', 'is', 'in', 'into', 'like', 'of', 'off', 'on', 'onto', 'per', 'since', 'than', 'the', 'this', 'that', 'to', 'up', 'via', 'with', ); // Replace or drop punctuation based on user settings $separator = variable_get('pathauto_separator', '-'); $output = $string; $punctuation = pathauto_punctuation_chars(); foreach ($punctuation as $name => $details) { $action = variable_get('pathauto_punctuation_' . $name, PATHAUTO_PUNCTUATION_REMOVE); if ($action != PATHAUTO_PUNCTUATION_DO_NOTHING) { // Slightly tricky inline if which either replaces with the separator or nothing $output = str_replace($details['value'], ($action ? $separator : ''), $output); } } // If something is already urlsafe then don't remove slashes if ($clean_slash) { $output = str_replace('/', '', $output); } // Optionally transliterate (by running through the Transliteration module) if (variable_get('pathauto_transliterate', FALSE)) { if (module_exists('transliteration')) { $output = transliteration_get($output); } else { drupal_set_message(t('Pathauto could not transliterate the path, as the Transliteration module has been disabled.'), 'error'); } } // Reduce strings to letters and numbers if (variable_get('pathauto_reduce_ascii', FALSE)) { $pattern = '/[^a-zA-Z0-9\/]+/ '; $output = preg_replace($pattern, $separator, $output); } // Get rid of words that are on the ignore list $ignore_re = '\b' . preg_replace('/,/', '\b|\b', variable_get('pathauto_ignore_words', $ignore_words)) . '\b'; if (function_exists('mb_eregi_replace')) { $output = mb_eregi_replace($ignore_re, '', $output); } else { $output = preg_replace("/$ignore_re/i", '', $output); } // Always replace whitespace with the separator. $output = preg_replace('/\s+/', $separator, $output); // 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); // Replace trailing separators around slashes. $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output); // Replace multiple separators with a single one. $output = preg_replace("/$seppattern+/", $separator, $output); } // Optionally convert to lower case. if (variable_get('pathauto_case', PATHAUTO_CASE_LOWER)) { $output = drupal_strtolower($output); } // Enforce the maximum component length $maxlength = min(variable_get('pathauto_max_component_length', 100), 128); $output = _pathauto_truncate_chars($output, $maxlength, $separator); 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 * The "real" URI of the content to be aliased (e.g., "node/$node->nid"). * @param $data * An associative array of objects needed to apply the alias' pattern, keyed * by their type. Example: array('term' => $term, 'vocabulary' => $vocabulary). * @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. */ function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $language = LANGUAGE_NONE) { if (($op != 'bulkupdate') and variable_get('pathauto_verbose', FALSE) && user_access('notify of path changes')) { $verbose = TRUE; } else { $verbose = FALSE; } // Retrieve and apply the pattern for this content type if (!empty($type)) { $pattern = trim(variable_get('pathauto_' . $module . '_' . $type . '_' . $language . '_pattern', '')); if (empty($pattern)) { $pattern = trim(variable_get('pathauto_' . $module . '_' . $type . '_pattern', '')); } } if (empty($pattern)) { $pattern = trim(variable_get('pathauto_' . $module . '_pattern', '')); } // No pattern? Do nothing (otherwise we may blow away existing aliases...) if (empty($pattern)) { return ''; } if ($module == 'taxonomy' && isset($data['term'])) { // Get proper path for term. $term_path = 'taxonomy/term/' . $data['term']->tid; if ($term_path != $source) { // Quietly alias 'taxonomy/term/[tid]' with proper path for term. $update_data = _pathauto_existing_alias_data($source); _pathauto_set_alias($source, $term_path, $update_data['pid'], FALSE, $update_data['old_alias'], $language); // Set $src as proper path. $source = $term_path; } } // Special handling when updating an item which is already aliased. $pid = NULL; $old_alias = NULL; if ($op == 'update' or $op == 'bulkupdate') { if (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE) == PATHAUTO_UPDATE_ACTION_NO_NEW) { // Do nothing return ''; } $update_data = _pathauto_existing_alias_data($source); $pid = $update_data['pid']; $old_alias = $update_data['old_alias']; } // Replace any tokens in the pattern. Uses callback option to clean replacements. No sanitization. $alias = token_replace($pattern, $data, array('sanitize' => FALSE, 'callback' => 'pathauto_clean_token_values', 'language' => (object)array('language' => $language))); // Two or more slashes should be collapsed into one $alias = preg_replace('/\/+/', '/', $alias); // Trim any leading or trailing slashes $alias = preg_replace('/^\/|\/+$/', '', $alias); // Shorten to a logical place based on the last separator. $separator = variable_get('pathauto_separator', '-'); $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength()); // Make sure we're not ending the alias in the middle of a word. $alias = _pathauto_truncate_chars($alias, $maxlength, $separator); // If the alias already exists, generate a new, hopefully unique, variant if (_pathauto_alias_exists($alias, $source, $language)) { $original_alias = $alias; for ($i = 0; _pathauto_alias_exists(drupal_substr($alias, 0, $maxlength - drupal_strlen($i)) . $separator . $i, $source, $language); $i++) { } // Make room for the sequence number $alias = drupal_substr($alias, 0, $maxlength - drupal_strlen($i)); $alias = $alias . $separator . $i; // If verbose is on, alert the user why this happened if ($verbose) { drupal_set_message(t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array('%original_alias' => $original_alias, '%alias' => $alias))); } } // Return the generated alias if requested. if ($op == 'return') { return $alias; } // If $pid is NULL, a new alias is created - otherwise, the existing // alias for the designated src is replaced _pathauto_set_alias($source, $alias, $pid, $verbose, $old_alias, $language); // Also create a related feed alias if requested, and if supported // by the module if (drupal_strlen(variable_get('pathauto_' . $module . '_applytofeeds', ''))) { $feedappend = variable_get('pathauto_' . $module . '_applytofeeds', ''); // For forums and taxonomies, the src doesn't always form the base of the rss feed (ie. image galleries) if (($module == 'taxonomy' || $module == 'forum') && isset($data['term'])) { $tid = $data['term']->tid; $update_data = _pathauto_existing_alias_data("taxonomy/term/$tid/$feedappend"); _pathauto_set_alias("taxonomy/term/$tid/$feedappend", "$alias/feed", $update_data['pid'], $verbose, $update_data['old_alias'], $language); } else { $update_data = _pathauto_existing_alias_data("$source/$feedappend"); _pathauto_set_alias("$source/$feedappend", "$alias/feed", $update_data['pid'], $verbose, $update_data['old_alias'], $language); } } return $alias; } /** * 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; } return FALSE; } /** * Private function for Pathauto to create an alias. * * @param $source * The internal path. * @param $alias * The visible externally used path. * @param $pid * If the item is currently aliased, the pid for that item. * @param $verbose * If the admin has enabled verbose, should be TRUE. Else FALSE or NULL. * @param $old_alias * If the item is currently aliased, the existing alias for that item. * @param $language * The path's language. */ function _pathauto_set_alias($source, $alias, $pid = NULL, $verbose = FALSE, $old_alias = NULL, $language = LANGUAGE_NONE) { // Alert users that an existing callback cannot be overridden automatically if (_pathauto_path_is_callback($alias)) { if ($verbose && user_access('notify of path changes')) { drupal_set_message(t('Ignoring alias %alias due to existing path conflict.', array('%alias' => $alias))); } return; } // Alert users if they are trying to create an alias that is the same as the internal path if ($source == $alias) { if ($verbose && user_access('notify of path changes')) { drupal_set_message(t('Ignoring alias %alias because it is the same as the internal path.', array('%alias' => $alias))); } return; } // Skip replacing the current alias with an identical alias if ($old_alias != $alias) { $path = array('source' => $source, 'alias' => $alias, 'pid' => $pid, 'language' => $language); path_save($path); if (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE) == PATHAUTO_UPDATE_ACTION_REDIRECT && module_exists('path_redirect') && function_exists('path_redirect_save')) { if (!empty($old_alias)) { $redirect = array( 'source' => $old_alias, 'redirect' => $source, ); path_redirect_save($redirect); } } if ($verbose && user_access('notify of path changes')) { if (!empty($redirect)) { drupal_set_message(t('Created new alias %alias for %source, replacing %old_alias. %old_alias now redirects to %alias.', array('%alias' => $alias, '%source' => $source, '%old_alias' => $old_alias))); } elseif ($pid) { drupal_set_message(t('Created new alias %alias for %source, replacing %old_alias.', array('%alias' => $alias, '%source' => $source, '%old_alias' => $old_alias))); } else { drupal_set_message(t('Created new alias %alias for %source.', array('%alias' => $alias, '%source' => $source))); } } } } /** * Clean tokens so they are URL friendly. * * @param $replacements * An array of token replacements that need to be "cleaned" for use in the URL. * @param $data * An array of objects used to generate the replacements. * @param $options * An array of options used to generate the replacements. */ function pathauto_clean_token_values(&$replacements, $data = array(), $options = array()) { foreach ($replacements as $token => $value) { // If it's a "path" or "url friendly" token don't remove the "/" character if (drupal_substr($token, -5, 4) === 'path' || drupal_substr($token, -6, 5) === 'alias') { $replacements[$token] = pathauto_cleanstring($value, FALSE); } else { $replacements[$token] = pathauto_cleanstring($value); } } } /** * 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 = array(); // Handle " ' ` , . - _ : ; | { [ } ] + = * & % ^ $ # @ ! ~ ( ) ? < > \ $punctuation['double_quotes'] = array('value' => '"', 'name' => t('Double quotes "')); $punctuation['quotes'] = array('value' => "'", 'name' => t("Single quotes (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('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 +')); $punctuation['equal'] = array('value' => '=', 'name' => t('Equal =')); $punctuation['asterisk'] = array('value' => '*', 'name' => t('Asterisk *')); $punctuation['ampersand'] = array('value' => '&', 'name' => t('Ampersand &')); $punctuation['percent'] = array('value' => '%', 'name' => t('Percent %')); $punctuation['caret'] = array('value' => '^', 'name' => t('Caret ^')); $punctuation['dollar'] = array('value' => '$', 'name' => t('Dollar $')); $punctuation['hash'] = array('value' => '#', 'name' => t('Hash #')); $punctuation['at'] = array('value' => '@', 'name' => t('At @')); $punctuation['exclamation'] = array('value' => '!', 'name' => t('Exclamation !')); $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 <')); $punctuation['greater_than'] = array('value' => '>', 'name' => t('Greater than >')); $punctuation['back_slash'] = array('value' => '\\', 'name' => t('Back slash \\')); return $punctuation; } /** * A Pathauto friendly version of truncate_utf8. * * @param $string * The string to be truncated. * @param $length * An integer for the maximum desired length. * @param $separator * A string which contains the word boundary such as - or _. * * @return * The string truncated below the maxlength. */ function _pathauto_truncate_chars($string, $length, $separator) { if (drupal_strlen($string) > $length) { $string = drupal_substr($string, 0, $length + 1); // leave one more character if ($last_break = strrpos($string, $separator)) { // space exists AND is not on position 0 $string = drupal_substr($string, 0, $last_break); } else { $string = drupal_substr($string, 0, $length); } } return $string; } /** * Fetch the maximum length of the {url_alias}.alias field from the schema. * * @return * An integer of the maximum url alias length allowed by the database. */ function _pathauto_get_schema_alias_maxlength() { $maxlength = &drupal_static(__FUNCTION__); if (!isset($maxlength)) { $schema = drupal_get_schema('url_alias'); $maxlength = $schema['fields']['alias']['length']; } return $maxlength; }