Provides a mechanism for modules to automatically generate aliases for the content they manage.

Settings

The Maximum Alias Length and Maximum component length values default to 100 and have a limit of 128 from pathauto. This length is limited by the length of the dst column of the url_alias database table. The default database schema for this column is 128. If you set a length that is equal to that of the one set in the dst column it will cause problems in situations where the system needs to append additional words to the aliased URL. For example... URLs generated for feeds will have '/feed' added to the end. You should enter a value that is the length of the dst 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.

"); break; } return $output; } function pathauto_perm() { return array('administer pathauto'); } function pathauto_admin_settings() { // Restrict administration of this module if (!user_access('administer pathauto')) { $form["error"] = array('#type' => 'item', '#title' => t('You are not authorized to access the pathauto settings.')); return system_settings_form($form); } $output = ''; // Check for any updates _pathauto_update(); // 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" ); // Generate the form - settings applying to all patterns first $group_weight = -20; $form["general"] = array('#type' => 'fieldset', '#weight' => $group_weight, '#title' => t('General settings'), '#collapsible' => TRUE, '#collapsed' => TRUE); $group_weight++; $form["general"]["pathauto_verbose"] = array('#type' => 'checkbox', '#title' => t('Verbose'), '#default_value' => variable_get('pathauto_verbose', FALSE), '#description' => t('Display alias changes (except during bulk updates).')); $form["general"]["pathauto_separator"] = array('#type' => 'textfield', '#title' => t('Separator'), '#size' => 1, '#maxlength' => 1, '#default_value' => variable_get('pathauto_separator', '-'), '#description' => t('Character used to separate words in titles. This will replace any spaces and punctuation characters.')); $form["general"]["pathauto_quotes"] = array('#type' => 'radios', '#title' => t('Quotation marks'), '#default_value' => variable_get('pathauto_quotes', 0), '#options' => array(t('Remove'), t('Replace by separator')), ); $form["general"]["pathauto_max_length"] = array('#type' => 'textfield', '#title' => t('Maximum alias length'), '#size' => 3, '#maxlength' => 3, '#default_value' => variable_get('pathauto_max_length', 100), '#description' => t('Maximum length of aliases to generate. 100 is recommended. See Pathauto help for details.', array('@pathauto-help' => url('admin/help/pathauto')))); $form["general"]["pathauto_max_component_length"] = array('#type' => 'textfield', '#title' => t('Maximum component length'), '#size' => 3, '#maxlength' => 3, '#default_value' => variable_get('pathauto_max_component_length', 100), '#description' => t('Maximum text length of any component in the alias (e.g., [title]). 100 is recommended. See Pathauto help for details.', array('@pathauto-help' => url('admin/help/pathauto')))); $form["general"]["pathauto_indexaliases"] = array('#type' => 'checkbox', '#title' => t('Create index aliases'), '#default_value' => variable_get('pathauto_indexaliases', FALSE), '#description' => t('When a pattern generates a hierarchical alias (i.e., any alias containing a slash), generate aliases for each step of the hierarchy which can be used to list content within that hierarchy. For example, if a node alias "music/concert/beethoven" is created, also create an alias "music/concert" which will list all concert nodes, and an alias "music" which will list all music nodes.')); // If requested, do a bulk generation of index aliases $do_index_bulkupdate = variable_get('pathauto_indexaliases_bulkupdate', FALSE); variable_set('pathauto_indexaliases_bulkupdate', FALSE); $form["general"]["pathauto_indexaliases_bulkupdate"] = array('#type' => 'checkbox', '#title' => t('Bulk generate index aliases'), '#default_value' => FALSE, '#description' => t('Generate index aliases based on all pre-existing aliases.')); $form["general"]["pathauto_update_action"] = array('#type' => 'radios', '#title' => t('Update action'), '#default_value' => variable_get('pathauto_update_action', 2), '#options' => array(t('Do nothing, leaving the old alias intact'), t('Create a new alias in addition to the old alias'), t('Create a new alias, replacing the old one')), '#description' => t('What should pathauto do when updating an existing content item which already has an alias?')); $form["general"]["pathauto_ignore_words"] = array('#type' => 'textarea', '#title' => t('Strings to Remove'), '#default_value' => variable_get('pathauto_ignore_words', implode(",", $ignore_words)), '#description' => t('Words to strip out of the URL alias, separated by commas') ); // Call the hook on all modules - an array of 'settings' objects is returned $all_settings = module_invoke_all('pathauto', 'settings'); $modulelist = ''; $indexcount = 0; foreach ($all_settings as $settings) { $items = ''; $module = $settings->module; $modulelist[] = $module; $patterndescr = $settings->patterndescr; $patterndefault = $settings->patterndefault; $groupheader = $settings->groupheader; $supportsfeeds = isset($settings->supportsfeeds) ? $settings->supportsfeeds : NULL; variable_set('pathauto_'. $module .'_supportsfeeds', $supportsfeeds); $form[$module] = array('#type' => 'fieldset', '#title' => $groupheader, '#weight' => $group_weight, '#collapsible' => TRUE, '#collapsed' => TRUE); $group_weight++; // Prompt for the default pattern for this module $variable = 'pathauto_'. $module .'_pattern'; $form[$module][$variable] = array('#type' => 'textfield', '#title' => $patterndescr, '#default_value' => variable_get($variable, $patterndefault), '#size' => 65, '#maxlength' => variable_get('pathauto_max_length', 100)); // If the module supports a set of specialized patterns, set // them up here if (isset($settings->patternitems)) { foreach ($settings->patternitems as $itemname => $itemlabel) { $variable = 'pathauto_'. $module .'_'. $itemname .'_pattern'; $form[$module][$variable] = array('#type' => 'textfield', '#title' => $itemlabel, '#default_value' => variable_get($variable, ''), '#size' => 65, '#maxlength' => variable_get('pathauto_max_length', 100)); } } // Display the user documentation of placeholders supported by // this module, as a description on the last pattern $doc = "
\n"; foreach ($settings->placeholders as $name => $description) { $doc .= '
'. $name .'
'; $doc .= '
'. $description .'
'; } $doc .= "
\n"; $form[$module][$variable]["#description"] = $doc; // If the module supports bulk updates, offer the update action here if ($settings->bulkname) { $variable = 'pathauto_'. $module .'_bulkupdate'; if (variable_get($variable, FALSE)) { variable_set($variable, FALSE); $function = $module .'_pathauto_bulkupdate'; call_user_func($function); } $form[$module][$variable] = array('#type' => 'checkbox', '#title' => $settings->bulkname, '#default_value' => FALSE, '#description' => $settings->bulkdescr); } // Perform bulk updates of indexes for this module, if asked if ($do_index_bulkupdate) { $function = $module .'_pathauto_bulkupdate_indexes'; if (is_callable($function)) { $indexcount += call_user_func($function); } } // If the module supports feeds, offer to generate aliases for them if ($supportsfeeds) { $variable = 'pathauto_'. $module .'_applytofeeds'; $form[$module][$variable] = array('#type' => 'checkbox', '#title' => t('Create feed aliases'), '#default_value' => variable_get($variable, FALSE), '#description' => t('Also generate aliases for RSS feeds.')); } } if ($do_index_bulkupdate) { drupal_set_message(format_plural($indexcount, "Bulk update of index aliases completed, one alias generated.", "Bulk update of index aliases completed, @count aliases generated.")); } // Keep track of which modules currently support pathauto variable_set('pathauto_modulelist', $modulelist); return system_settings_form($form); } // Make sure there isn't already an alias pointing to a different item function _pathauto_alias_exists($alias, $src) { return db_result(db_query( "SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s' AND src != '%s'", $alias, $src)); } // Clean up a string value provided by a module, resulting in a // string containing only alphanumerics and separators function pathauto_cleanstring($string) { // 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" ); static $i18n_loaded = false; static $translations = array(); if (!$i18n_loaded) { $path = drupal_get_path('module', 'pathauto'); if (is_file($path .'/i18n-ascii.txt')) { $translations = parse_ini_file($path .'/i18n-ascii.txt'); } $i18n_loaded = true; } $output = strtr($output, $translations); // Replace or drop apostrophes based on user settings $separator = variable_get('pathauto_separator', '-'); $quotes = variable_get('pathauto_quotes', 0); $output = str_replace("'", ($quotes ? $separator : ''), $string); // Convert accented characters to their ASCII counterparts... /* $output = strtr(utf8_decode($output), "\xA1\xAA\xBA\xBF". "\xC0\xC1\xC2\xC3\xC5\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF". "\xD0\xD1\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD". "\xE0\xE1\xE2\xE3\xE5\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF". "\xF0\xF1\xF2\xF3\xF4\xF5\xF8\xF9\xFA\xFB\xFD\xFF", "!ao?AAAAACEEEEIIIIDNOOOOOUUUYaaaaaceeeeiiiidnooooouuuyy"); // ...and ligatures too $output = utf8_encode(strtr($output, array("\xC4"=>"Ae", "\xC6"=>"AE", "\xD6"=>"Oe", "\xDC"=>"Ue", "\xDE"=>"TH", "\xDF"=>"ss", "\xE4"=>"ae", "\xE6"=>"ae", "\xF6"=>"oe", "\xFC"=>"ue", "\xFE"=>"th")));*/ $output = strtr($output, $translations); // 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"; $output = preg_replace("/$ignore_re/ie", "", $output); // Preserve alphanumerics, everything else becomes a separator $pattern = '/[^a-zA-Z0-9]+/ '; $output = preg_replace($pattern, $separator, $output); // Trim any leading or trailing separators (note the need to // escape the separator if and only if it is not alphanumeric) if ($separator) { if (ctype_alnum($separator)) { $seppattern = $separator; } else { $seppattern = '\\'. $separator; } $output = preg_replace("/^$seppattern+|$seppattern+$/", "", $output); } // Enforce the maximum component length $maxlength = min(variable_get('pathauto_max_component_length', 100), 128); $output = drupal_substr($output, 0, $maxlength); 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', or 'bulkupdate') * @param $placeholders * An array whose keys consist of the translated placeholders * which appear in patterns (e.g., t('[title]')) and values are the * actual values to be substituted into the pattern (e.g., $node->title) * @param $src * The "real" URI of the content to be aliased (e.g., "node/$node->nid") * @param $type * For modules which provided patternitems in hook_autopath(), * the relevant identifier for the specific item to be aliased (e.g., * $node->type) * @return * The alias that was created */ function pathauto_create_alias($module, $op, $placeholders, $src, $type = NULL) { if (($op != 'bulkupdate') and variable_get('pathauto_verbose', FALSE)) { $verbose = TRUE; } else { $verbose = FALSE; } // Retrieve and apply the pattern for this content type $pattern = ''; if ($type) { $pattern = drupal_strtolower(variable_get('pathauto_'. $module .'_'. $type .'_pattern', '')); } if (!trim($pattern)) { $pattern = drupal_strtolower(variable_get('pathauto_'. $module .'_pattern', '')); } // No pattern? Do nothing (otherwise we may blow away existing aliases...) if (!trim($pattern)) { return ''; } // Special handling when updating an item which is already aliased $pid = NULL; if ($op == 'update' or $op == 'bulkupdate') { $result = db_query("SELECT pid,dst FROM {url_alias} WHERE src='%s'", $src); if ($data = db_fetch_object($result)) { // The item is already aliased, check what to do... switch (variable_get('pathauto_update_action', 2)) { // Do nothing case 0: return ''; // Add new alias in addition to old one case 1: $oldalias = $data->dst; break; // Replace old alias - remember the pid to update case 2: $pid = $data->pid; $oldalias = $data->dst; break; default: break; } } } // Replace the placeholders with the values provided by the module, // and lower-case the result $alias = str_replace(array_keys($placeholders), $placeholders, $pattern); $alias = drupal_strtolower($alias); // Two or more slashes should be collapsed into one $alias = preg_replace("/\/+/", "/", $alias); // Trim any leading or trailing slashes $alias = preg_replace("/^\/|\/+$/", "", $alias); $maxlength = min(variable_get('pathauto_max_length', 100), 128); $alias = drupal_substr($alias, 0, $maxlength); // If the alias already exists, generate a new variant $separator = variable_get('pathauto_separator', '-'); if (_pathauto_alias_exists($alias, $src)) { for ($i = 0; _pathauto_alias_exists($alias . $separator . $i, $src); $i++) { } // Make room for the sequence number $alias = drupal_substr($alias, 0, $maxlength - 1 -($i/10 + 1)); $alias = $alias . $separator . $i; } // If $pid is NULL, a new alias is created - otherwise, the existing // alias for the designated src is replaced _pathauto_set_alias($src, $alias, $pid, $verbose, $oldalias); // Also create a related feed alias if requested, and if supported // by the module if (variable_get('pathauto_'. $module .'_applytofeeds', FALSE)) { $feedappend = variable_get('pathauto_'. $module .'_supportsfeeds', ''); // Handle replace case (get existing pid) _pathauto_set_alias("$src/$feedappend", "$alias/feed", NULL, $verbose); } // Create any relevant index aliases if requested if (variable_get('pathauto_indexaliases', FALSE)) { pathauto_create_index_alias($alias, $module); } return $alias; } /** * Verifies 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 is already */ function _pathauto_path_is_callback($path) { static $menu = NULL; if (is_null($menu)) { $menu = menu_get_menu(); } // Determine the menu item containing the callback. return isset($menu["callbacks"][$path]); } function _pathauto_set_alias($src, $dst, $pid = NULL, $verbose = FALSE, $oldalias = NULL) { // Alert users that an existing callback cannot be overridden automatically // if (_pathauto_path_is_callback($dst)) { if ($verbose and user_access('create url aliases')) { drupal_set_message(t('Ignoring alias ') . $dst . t(' due to existing path conflict')); } return; } // Skip replacing the current alias with an identical alias if ($oldalias != $dst) { path_set_alias($src, $dst, $pid, 1, 10); if ($verbose and user_access('create url aliases')) { if ($pid) { drupal_set_message(t('Created new alias %dst for %src, replacing %oldalias', array('%dst' => $dst, '%src' => $src, '%oldalias' => $oldalias))); } else { drupal_set_message(t('Created new alias %dst for %src', array('%dst' => $dst, '%src' => $src))); } } } } function pathauto_create_index_alias($alias, $module) { $count = 0; $components = explode('/', $alias); // Not interested in the trailing component array_pop($components); $alias = ''; foreach ($components as $component) { if ($alias) { $alias .= '/'. $component; } else { $alias .= $component; } $alias_count = db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s'", $alias)); if (!$alias_count) { $src = "pathauto/$module/$alias"; _pathauto_set_alias($src, $alias); $count++; } } return $count; } function pathauto_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/settings/pathauto', 'title' => t('Pathauto'), 'description' => t('Configure how pathauto generates clean URLs for your content.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('pathauto_admin_settings'), 'access' => user_access('administer site configuration'), 'type' => MENU_NORMAL_ITEM, ); } $modulelist = variable_get('pathauto_modulelist', array()); if (is_array($modulelist)) { foreach ($modulelist as $module) { $indexfunc = $module .'_pathauto_page'; if (function_exists($indexfunc)) { $items[] = array('path' => 'pathauto/'. $module, 'title' => t('Pathauto'), 'callback' => $indexfunc, 'access' => 1, 'type' => MENU_CALLBACK); } } } return $items; } /** * Returns the version of this release of the pathauto module. * * @return array An array with keys 'text' and 'build' containing the * text version and build ID of this release, respectively. */ function _pathauto_version() { return array("text" => "2005-9-18", "build" => 5); } // function _pathauto_version /** * Makes updates to saved variables and the database structure. **/ function _pathauto_update() { $installed_version = variable_get('pathauto_version', array('text' => 'Unknown', 'build' => 1)); $current_version = _pathauto_version(); if ( $installed_version["build"] < $current_version["build"]) { // Upgrading from original version - variable names were changed if ( $installed_version["build"] <= 1 ) { // Remove obsolete bulkupdate variables variable_del('pathauto_bulkupdate'); variable_del('pathauto_cat_bulkupdate'); // The original global node and taxonomy patterns got renamed $old_pattern = variable_get('pathauto_pattern', 0); if ($old_pattern != 0) { variable_set('pathauto_node_pattern', $old_pattern); variable_del('pathauto_pattern'); } $old_pattern = variable_get('pathauto_cat_pattern', 0); if ($old_pattern != 0) { variable_set('pathauto_taxonomy_pattern', $old_pattern); variable_del('pathauto_cat_pattern'); } // And the form of the type-specific patterns was changed $query = 'SELECT name,value FROM {variable} '. "WHERE name LIKE '%_pathauto_pattern'"; $result = db_query($query); $var = db_fetch_object($result); while ($var) { $type = drupal_substr($var->name, 0, drupal_strlen($var->name)-drupal_strlen('_pathauto_pattern')); $old_pattern = variable_get($var->name, ''); $new_name = 'pathauto_node_'. $type .'_pattern'; variable_set($new_name, $old_pattern); variable_del($var->name); $var = db_fetch_object($result); } $query = 'SELECT name,value FROM {variable} '. "WHERE name LIKE '%_pathauto_cat_pattern'"; $result = db_query($query); $var = db_fetch_object($result); while ($var) { $type = drupal_substr($var->name, 0, drupal_strlen($var->name)-drupal_strlen('_pathauto_cat_pattern')); $old_pattern = variable_get($var->name, ''); $new_name = 'pathauto_taxonomy_'. $type .'_pattern'; variable_set($new_name, $old_pattern); variable_del($var->name); $var = db_fetch_object($result); } } if ( $installed_version["build"] <= 2 ) { // Change feed support variables from booleans to appended strings $query = 'SELECT name,value FROM {variable} '. "WHERE name LIKE 'pathauto_%_supportsfeeds'"; $result = db_query($query); $var = db_fetch_object($result); while ($var) { $type = drupal_substr($var->name, 9, drupal_strlen($var->name)-23); $old_value = variable_get($var->name, FALSE); if ($old_value) { switch ($type) { case 'blog': case 'node': variable_set($var->name, 'feed'); break; case 'taxonomy': variable_set($var->name, '0/feed'); break; } } else { variable_set($var->name, ''); } $var = db_fetch_object($result); } // Update previously-generated taxonomy aliases to remove the "/0" $query = "SELECT * FROM {url_alias} WHERE src LIKE 'taxonomy/term/%/0'"; $result = db_query($query); $aliasrow = db_fetch_object($result); while ($aliasrow) { $src = drupal_substr($aliasrow->src, 0, -2); _pathauto_set_alias($src, $aliasrow->dst, $aliasrow->pid, FALSE); $aliasrow = db_fetch_object($result); } } if ($installed_version["build"] <= 3) { // Change taxonomy pattern variables to use IDs instead // of names $query = 'SELECT name,value FROM {variable} '. "WHERE name LIKE 'pathauto_taxonomy_%_pattern'"; $result = db_query($query); $var = db_fetch_object($result); while ($var) { $vocabname = drupal_substr($var->name, 18, drupal_strlen($var->name)-26); $query = "SELECT vid FROM {vocabulary} WHERE name='$vocabname'"; $vid = db_result(db_query($query)); variable_set("pathauto_taxonomy_$vid_pattern", $var->value); variable_del($var->name); $var = db_fetch_object($result); } } if ($installed_version["build"] <= 4) { // Make feed generation variables module-specific. $applytofeed = variable_get('pathauto_applytofeed', FALSE); if ($applytofeed) { // Feeds were enabled, enable for each module supporting them $query = 'SELECT name,value FROM {variable} '. "WHERE name LIKE 'pathauto_%_supportsfeeds'"; $result = db_query($query); while ($var = db_fetch_object($result)) { if (variable_get($var->name, FALSE)) { $varname = str_replace('_supportsfeed', '_applytofeed', $var->name); variable_set($varname, TRUE); } } } variable_del('pathauto_applytofeed'); } // Set the current version variable_set('pathauto_version', $current_version); drupal_set_message('Upgraded pathauto from '. $installed_version["build"] . ' to '. $current_version["build"]); } } // end function _pathauto_update