pathauto.inc 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. <?php
  2. /**
  3. * @file
  4. * Miscellaneous functions for Pathauto.
  5. *
  6. * This also contains some constants giving human readable names to some numeric
  7. * settings; they're included here as they're only rarely used outside this file
  8. * anyway. Use module_load_include('inc', 'pathauto') if the constants need to
  9. * be available.
  10. *
  11. * @ingroup pathauto
  12. */
  13. /**
  14. * Case should be left as is in the generated path.
  15. */
  16. define('PATHAUTO_CASE_LEAVE_ASIS', 0);
  17. /**
  18. * Case should be lowercased in the generated path.
  19. */
  20. define('PATHAUTO_CASE_LOWER', 1);
  21. /**
  22. * "Do nothing. Leave the old alias intact."
  23. */
  24. define('PATHAUTO_UPDATE_ACTION_NO_NEW', 0);
  25. /**
  26. * "Create a new alias. Leave the existing alias functioning."
  27. */
  28. define('PATHAUTO_UPDATE_ACTION_LEAVE', 1);
  29. /**
  30. * "Create a new alias. Delete the old alias."
  31. */
  32. define('PATHAUTO_UPDATE_ACTION_DELETE', 2);
  33. /**
  34. * Remove the punctuation from the alias.
  35. */
  36. define('PATHAUTO_PUNCTUATION_REMOVE', 0);
  37. /**
  38. * Replace the punctuation with the separator in the alias.
  39. */
  40. define('PATHAUTO_PUNCTUATION_REPLACE', 1);
  41. /**
  42. * Leave the punctuation as it is in the alias.
  43. */
  44. define('PATHAUTO_PUNCTUATION_DO_NOTHING', 2);
  45. /**
  46. * Check to see if there is already an alias pointing to a different item.
  47. *
  48. * @param $alias
  49. * A string alias.
  50. * @param $source
  51. * A string that is the internal path.
  52. * @param $language
  53. * A string indicating the path's language.
  54. * @return
  55. * TRUE if an alias exists, FALSE if not.
  56. */
  57. function _pathauto_alias_exists($alias, $source, $language = LANGUAGE_NONE) {
  58. $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(
  59. ':source' => $source,
  60. ':alias' => $alias,
  61. ':language' => $language,
  62. ':language_none' => LANGUAGE_NONE,
  63. ))->fetchField();
  64. return !empty($pid);
  65. }
  66. /**
  67. * Fetches an existing URL alias given a path and optional language.
  68. *
  69. * @param $source
  70. * An internal Drupal path.
  71. * @param $language
  72. * An optional language code to look up the path in.
  73. * @return
  74. * FALSE if no alias was found or an associative array containing the
  75. * following keys:
  76. * - pid: Unique path alias identifier.
  77. * - alias: The URL alias.
  78. */
  79. function _pathauto_existing_alias_data($source, $language = LANGUAGE_NONE) {
  80. $pid = db_query_range("SELECT pid FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", 0, 1, array(':source' => $source, ':language' => $language, ':language_none' => LANGUAGE_NONE))->fetchField();
  81. return path_load(array('pid' => $pid));
  82. }
  83. /**
  84. * Clean up a string segment to be used in an URL alias.
  85. *
  86. * Performs the following possible alterations:
  87. * - Remove all HTML tags.
  88. * - Process the string through the transliteration module.
  89. * - Replace or remove punctuation with the separator character.
  90. * - Remove back-slashes.
  91. * - Replace non-ascii and non-numeric characters with the separator.
  92. * - Remove common words.
  93. * - Replace whitespace with the separator character.
  94. * - Trim duplicate, leading, and trailing separators.
  95. * - Convert to lower-case.
  96. * - Shorten to a desired length and logical position based on word boundaries.
  97. *
  98. * This function should *not* be called on URL alias or path strings because it
  99. * is assumed that they are already clean.
  100. *
  101. * @param $string
  102. * A string to clean.
  103. * @return
  104. * The cleaned string.
  105. */
  106. function pathauto_cleanstring($string) {
  107. // Use the advanced drupal_static() pattern, since this is called very often.
  108. static $drupal_static_fast;
  109. if (!isset($drupal_static_fast)) {
  110. $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
  111. }
  112. $cache = &$drupal_static_fast['cache'];
  113. // Generate and cache variables used in this function so that on the second
  114. // call to pathauto_cleanstring() we focus on processing.
  115. if (!isset($cache)) {
  116. $cache = array(
  117. 'separator' => variable_get('pathauto_separator', '-'),
  118. 'strings' => array(),
  119. 'transliterate' => variable_get('pathauto_transliterate', FALSE) && module_exists('transliteration'),
  120. 'punctuation' => array(),
  121. 'reduce_ascii' => (bool) variable_get('pathauto_reduce_ascii', FALSE),
  122. 'ignore_words_regex' => FALSE,
  123. 'lowercase' => (bool) variable_get('pathauto_case', PATHAUTO_CASE_LOWER),
  124. 'maxlength' => min(variable_get('pathauto_max_component_length', 100), _pathauto_get_schema_alias_maxlength()),
  125. );
  126. // Generate and cache the punctuation replacements for strtr().
  127. $punctuation = pathauto_punctuation_chars();
  128. foreach ($punctuation as $name => $details) {
  129. $action = variable_get('pathauto_punctuation_' . $name, PATHAUTO_PUNCTUATION_REMOVE);
  130. switch ($action) {
  131. case PATHAUTO_PUNCTUATION_REMOVE:
  132. $cache['punctuation'][$details['value']] = '';
  133. break;
  134. case PATHAUTO_PUNCTUATION_REPLACE:
  135. $cache['punctuation'][$details['value']] = $cache['separator'];
  136. break;
  137. case PATHAUTO_PUNCTUATION_DO_NOTHING:
  138. // Literally do nothing.
  139. break;
  140. }
  141. }
  142. // Generate and cache the ignored words regular expression.
  143. $ignore_words = variable_get('pathauto_ignore_words', PATHAUTO_IGNORE_WORDS);
  144. $ignore_words_regex = preg_replace(array('/^[,\s]+|[,\s]+$/', '/[,\s]+/'), array('', '\b|\b'), $ignore_words);
  145. if ($ignore_words_regex) {
  146. $cache['ignore_words_regex'] = '\b' . $ignore_words_regex . '\b';
  147. if (function_exists('mb_eregi_replace')) {
  148. $cache['ignore_words_callback'] = 'mb_eregi_replace';
  149. }
  150. else {
  151. $cache['ignore_words_callback'] = 'preg_replace';
  152. $cache['ignore_words_regex'] = '/' . $cache['ignore_words_regex'] . '/i';
  153. }
  154. }
  155. }
  156. // Empty strings do not need any proccessing.
  157. if ($string === '' || $string === NULL) {
  158. return '';
  159. }
  160. // Check if the string has already been processed, and if so return the
  161. // cached result.
  162. if (isset($cache['strings'][$string])) {
  163. return $cache['strings'][$string];
  164. }
  165. // Remove all HTML tags from the string.
  166. $output = strip_tags(decode_entities($string));
  167. // Optionally transliterate (by running through the Transliteration module)
  168. if ($cache['transliterate']) {
  169. $output = transliteration_get($output);
  170. }
  171. // Replace or drop punctuation based on user settings
  172. $output = strtr($output, $cache['punctuation']);
  173. // Reduce strings to letters and numbers
  174. if ($cache['reduce_ascii']) {
  175. $output = preg_replace('/[^a-zA-Z0-9\/]+/', $cache['separator'], $output);
  176. }
  177. // Get rid of words that are on the ignore list
  178. if ($cache['ignore_words_regex']) {
  179. $words_removed = $cache['ignore_words_callback']($cache['ignore_words_regex'], '', $output);
  180. if (drupal_strlen(trim($words_removed)) > 0) {
  181. $output = $words_removed;
  182. }
  183. }
  184. // Always replace whitespace with the separator.
  185. $output = preg_replace('/\s+/', $cache['separator'], $output);
  186. // Trim duplicates and remove trailing and leading separators.
  187. $output = _pathauto_clean_separators($output, $cache['separator']);
  188. // Optionally convert to lower case.
  189. if ($cache['lowercase']) {
  190. $output = drupal_strtolower($output);
  191. }
  192. // Shorten to a logical place based on word boundaries.
  193. $output = truncate_utf8($output, $cache['maxlength'], TRUE);
  194. // Cache this result in the static array.
  195. $cache['strings'][$string] = $output;
  196. return $output;
  197. }
  198. /**
  199. * Trims duplicate, leading, and trailing separators from a string.
  200. *
  201. * @param $string
  202. * The string to clean path separators from.
  203. * @param $separator
  204. * The path separator to use when cleaning.
  205. * @return
  206. * The cleaned version of the string.
  207. *
  208. * @see pathauto_cleanstring()
  209. * @see pathauto_clean_alias()
  210. */
  211. function _pathauto_clean_separators($string, $separator = NULL) {
  212. static $default_separator;
  213. if (!isset($separator)) {
  214. if (!isset($default_separator)) {
  215. $default_separator = variable_get('pathauto_separator', '-');
  216. }
  217. $separator = $default_separator;
  218. }
  219. $output = $string;
  220. // Clean duplicate or trailing separators.
  221. if (strlen($separator)) {
  222. // Escape the separator.
  223. $seppattern = preg_quote($separator, '/');
  224. // Trim any leading or trailing separators.
  225. $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output);
  226. // Replace trailing separators around slashes.
  227. if ($separator !== '/') {
  228. $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output);
  229. }
  230. // Replace multiple separators with a single one.
  231. $output = preg_replace("/$seppattern+/", $separator, $output);
  232. }
  233. return $output;
  234. }
  235. /**
  236. * Clean up an URL alias.
  237. *
  238. * Performs the following alterations:
  239. * - Trim duplicate, leading, and trailing back-slashes.
  240. * - Trim duplicate, leading, and trailing separators.
  241. * - Shorten to a desired length and logical position based on word boundaries.
  242. *
  243. * @param $alias
  244. * A string with the URL alias to clean up.
  245. * @return
  246. * The cleaned URL alias.
  247. */
  248. function pathauto_clean_alias($alias) {
  249. $cache = &drupal_static(__FUNCTION__);
  250. if (!isset($cache)) {
  251. $cache = array(
  252. 'maxlength' => min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength()),
  253. );
  254. }
  255. $output = $alias;
  256. // Trim duplicate, leading, and trailing back-slashes.
  257. $output = _pathauto_clean_separators($output, '/');
  258. // Trim duplicate, leading, and trailing separators.
  259. $output = _pathauto_clean_separators($output);
  260. // Shorten to a logical place based on word boundaries.
  261. $output = truncate_utf8($output, $cache['maxlength'], TRUE);
  262. return $output;
  263. }
  264. /**
  265. * Apply patterns to create an alias.
  266. *
  267. * @param $module
  268. * The name of your module (e.g., 'node').
  269. * @param $op
  270. * Operation being performed on the content being aliased
  271. * ('insert', 'update', 'return', or 'bulkupdate').
  272. * @param $source
  273. * An internal Drupal path to be aliased.
  274. * @param $data
  275. * An array of keyed objects to pass to token_replace(). For simple
  276. * replacement scenarios 'node', 'user', and others are common keys, with an
  277. * accompanying node or user object being the value. Some token types, like
  278. * 'site', do not require any explicit information from $data and can be
  279. * replaced even if it is empty.
  280. * @param $type
  281. * For modules which provided pattern items in hook_pathauto(),
  282. * the relevant identifier for the specific item to be aliased
  283. * (e.g., $node->type).
  284. * @param $language
  285. * A string specify the path's language.
  286. * @return
  287. * The alias that was created.
  288. *
  289. * @see _pathauto_set_alias()
  290. * @see token_replace()
  291. */
  292. function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $language = LANGUAGE_NONE) {
  293. // Retrieve and apply the pattern for this content type.
  294. $pattern = pathauto_pattern_load_by_entity($module, $type, $language);
  295. if (empty($pattern)) {
  296. // No pattern? Do nothing (otherwise we may blow away existing aliases...)
  297. return '';
  298. }
  299. // Special handling when updating an item which is already aliased.
  300. $existing_alias = NULL;
  301. if ($op == 'update' || $op == 'bulkupdate') {
  302. if ($existing_alias = _pathauto_existing_alias_data($source, $language)) {
  303. switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
  304. case PATHAUTO_UPDATE_ACTION_NO_NEW:
  305. // If an alias already exists, and the update action is set to do nothing,
  306. // then gosh-darn it, do nothing.
  307. return '';
  308. }
  309. }
  310. }
  311. // Replace any tokens in the pattern. Uses callback option to clean replacements. No sanitization.
  312. $alias = token_replace($pattern, $data, array(
  313. 'sanitize' => FALSE,
  314. 'clear' => TRUE,
  315. 'callback' => 'pathauto_clean_token_values',
  316. 'language' => (object) array('language' => $language),
  317. 'pathauto' => TRUE,
  318. ));
  319. // Check if the token replacement has not actually replaced any values. If
  320. // that is the case, then stop because we should not generate an alias.
  321. // @see token_scan()
  322. $pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern);
  323. if ($alias === $pattern_tokens_removed) {
  324. return '';
  325. }
  326. $alias = pathauto_clean_alias($alias);
  327. // Allow other modules to alter the alias.
  328. $context = array(
  329. 'module' => $module,
  330. 'op' => $op,
  331. 'source' => &$source,
  332. 'data' => $data,
  333. 'type' => $type,
  334. 'language' => &$language,
  335. 'pattern' => $pattern,
  336. );
  337. drupal_alter('pathauto_alias', $alias, $context);
  338. // If we have arrived at an empty string, discontinue.
  339. if (!drupal_strlen($alias)) {
  340. return '';
  341. }
  342. // If the alias already exists, generate a new, hopefully unique, variant.
  343. $original_alias = $alias;
  344. pathauto_alias_uniquify($alias, $source, $language);
  345. if ($original_alias != $alias) {
  346. // Alert the user why this happened.
  347. _pathauto_verbose(t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array(
  348. '%original_alias' => $original_alias,
  349. '%alias' => $alias,
  350. )), $op);
  351. }
  352. // Return the generated alias if requested.
  353. if ($op == 'return') {
  354. return $alias;
  355. }
  356. // Build the new path alias array and send it off to be created.
  357. $path = array(
  358. 'source' => $source,
  359. 'alias' => $alias,
  360. 'language' => $language,
  361. );
  362. $path = _pathauto_set_alias($path, $existing_alias, $op);
  363. return $path;
  364. }
  365. /**
  366. * Check to ensure a path alias is unique and add suffix variants if necessary.
  367. *
  368. * Given an alias 'content/test' if a path alias with the exact alias already
  369. * exists, the function will change the alias to 'content/test-0' and will
  370. * increase the number suffix until it finds a unique alias.
  371. *
  372. * @param $alias
  373. * A string with the alias. Can be altered by reference.
  374. * @param $source
  375. * A string with the path source.
  376. * @param $langcode
  377. * A string with a language code.
  378. */
  379. function pathauto_alias_uniquify(&$alias, $source, $langcode) {
  380. if (!_pathauto_alias_exists($alias, $source, $langcode)) {
  381. return;
  382. }
  383. // If the alias already exists, generate a new, hopefully unique, variant
  384. $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength());
  385. $separator = variable_get('pathauto_separator', '-');
  386. $original_alias = $alias;
  387. $i = 0;
  388. do {
  389. // Append an incrementing numeric suffix until we find a unique alias.
  390. $unique_suffix = $separator . $i;
  391. $alias = truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix, TRUE)) . $unique_suffix;
  392. $i++;
  393. } while (_pathauto_alias_exists($alias, $source, $langcode));
  394. }
  395. /**
  396. * Verify if the given path is a valid menu callback.
  397. *
  398. * Taken from menu_execute_active_handler().
  399. *
  400. * @param $path
  401. * A string containing a relative path.
  402. * @return
  403. * TRUE if the path already exists.
  404. */
  405. function _pathauto_path_is_callback($path) {
  406. // We need to use a try/catch here because of a core bug which will throw an
  407. // exception if $path is something like 'node/foo/bar'.
  408. // @todo Remove when http://drupal.org/node/1302158 is fixed in core.
  409. try {
  410. $menu = menu_get_item($path);
  411. }
  412. catch (Exception $e) {
  413. return FALSE;
  414. }
  415. if (isset($menu['path']) && $menu['path'] == $path) {
  416. return TRUE;
  417. }
  418. elseif (is_file(DRUPAL_ROOT . '/' . $path) || is_dir(DRUPAL_ROOT . '/' . $path)) {
  419. // Do not allow existing files or directories to get assigned an automatic
  420. // alias. Note that we do not need to use is_link() to check for symbolic
  421. // links since this returns TRUE for either is_file() or is_dir() already.
  422. return TRUE;
  423. }
  424. return FALSE;
  425. }
  426. /**
  427. * Private function for Pathauto to create an alias.
  428. *
  429. * @param $path
  430. * An associative array containing the following keys:
  431. * - source: The internal system path.
  432. * - alias: The URL alias.
  433. * - pid: (optional) Unique path alias identifier.
  434. * - language: (optional) The language of the alias.
  435. * @param $existing_alias
  436. * (optional) An associative array of the existing path alias.
  437. * @param $op
  438. * An optional string with the operation being performed.
  439. *
  440. * @return
  441. * The saved path from path_save() or NULL if the path was not saved.
  442. *
  443. * @see path_save()
  444. */
  445. function _pathauto_set_alias(array $path, $existing_alias = NULL, $op = NULL) {
  446. $verbose = _pathauto_verbose(NULL, $op);
  447. // Alert users that an existing callback cannot be overridden automatically
  448. if (_pathauto_path_is_callback($path['alias'])) {
  449. if ($verbose) {
  450. _pathauto_verbose(t('Ignoring alias %alias due to existing path conflict.', array('%alias' => $path['alias'])));
  451. }
  452. return;
  453. }
  454. // Alert users if they are trying to create an alias that is the same as the internal path
  455. if ($path['source'] == $path['alias']) {
  456. if ($verbose) {
  457. _pathauto_verbose(t('Ignoring alias %alias because it is the same as the internal path.', array('%alias' => $path['alias'])));
  458. }
  459. return;
  460. }
  461. // Skip replacing the current alias with an identical alias
  462. if (empty($existing_alias) || $existing_alias['alias'] != $path['alias']) {
  463. $path += array('pathauto' => TRUE, 'original' => $existing_alias);
  464. // If there is already an alias, respect some update actions.
  465. if (!empty($existing_alias)) {
  466. switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
  467. case PATHAUTO_UPDATE_ACTION_NO_NEW:
  468. // Do not create the alias.
  469. return;
  470. case PATHAUTO_UPDATE_ACTION_LEAVE:
  471. // Create a new alias instead of overwriting the existing by leaving
  472. // $path['pid'] empty.
  473. break;
  474. case PATHAUTO_UPDATE_ACTION_DELETE:
  475. // The delete actions should overwrite the existing alias.
  476. $path['pid'] = $existing_alias['pid'];
  477. break;
  478. }
  479. }
  480. // Save the path array.
  481. path_save($path);
  482. if ($verbose) {
  483. if (!empty($existing_alias['pid'])) {
  484. _pathauto_verbose(t('Created new alias %alias for %source, replacing %old_alias.', array('%alias' => $path['alias'], '%source' => $path['source'], '%old_alias' => $existing_alias['alias'])));
  485. }
  486. else {
  487. _pathauto_verbose(t('Created new alias %alias for %source.', array('%alias' => $path['alias'], '%source' => $path['source'])));
  488. }
  489. }
  490. return $path;
  491. }
  492. }
  493. /**
  494. * Output a helpful message if verbose output is enabled.
  495. *
  496. * Verbose output is only enabled when:
  497. * - The 'pathauto_verbose' setting is enabled.
  498. * - The current user has the 'notify of path changes' permission.
  499. * - The $op parameter is anything but 'bulkupdate' or 'return'.
  500. *
  501. * @param $message
  502. * An optional string of the verbose message to display. This string should
  503. * already be run through t().
  504. * @param $op
  505. * An optional string with the operation being performed.
  506. * @return
  507. * TRUE if verbose output is enabled, or FALSE otherwise.
  508. */
  509. function _pathauto_verbose($message = NULL, $op = NULL) {
  510. static $verbose;
  511. if (!isset($verbose)) {
  512. $verbose = variable_get('pathauto_verbose', FALSE) && user_access('notify of path changes');
  513. }
  514. if (!$verbose || (isset($op) && in_array($op, array('bulkupdate', 'return')))) {
  515. return FALSE;
  516. }
  517. if ($message) {
  518. drupal_set_message($message);
  519. }
  520. return $verbose;
  521. }
  522. /**
  523. * Clean tokens so they are URL friendly.
  524. *
  525. * @param $replacements
  526. * An array of token replacements that need to be "cleaned" for use in the URL.
  527. * @param $data
  528. * An array of objects used to generate the replacements.
  529. * @param $options
  530. * An array of options used to generate the replacements.
  531. */
  532. function pathauto_clean_token_values(&$replacements, $data = array(), $options = array()) {
  533. foreach ($replacements as $token => $value) {
  534. // Only clean non-path tokens.
  535. if (!preg_match('/(path|alias|url|url-brief)\]$/', $token)) {
  536. $replacements[$token] = pathauto_cleanstring($value);
  537. }
  538. }
  539. }
  540. /**
  541. * Return an array of arrays for punctuation values.
  542. *
  543. * Returns an array of arrays for punctuation values keyed by a name, including
  544. * the value and a textual description.
  545. * Can and should be expanded to include "all" non text punctuation values.
  546. *
  547. * @return
  548. * An array of arrays for punctuation values keyed by a name, including the
  549. * value and a textual description.
  550. */
  551. function pathauto_punctuation_chars() {
  552. $punctuation = &drupal_static(__FUNCTION__);
  553. if (!isset($punctuation)) {
  554. $cid = 'pathauto:punctuation:' . $GLOBALS['language']->language;
  555. if ($cache = cache_get($cid)) {
  556. $punctuation = $cache->data;
  557. }
  558. else {
  559. $punctuation = array();
  560. $punctuation['double_quotes'] = array('value' => '"', 'name' => t('Double quotation marks'));
  561. $punctuation['quotes'] = array('value' => '\'', 'name' => t("Single quotation marks (apostrophe)"));
  562. $punctuation['backtick'] = array('value' => '`', 'name' => t('Back tick'));
  563. $punctuation['comma'] = array('value' => ',', 'name' => t('Comma'));
  564. $punctuation['period'] = array('value' => '.', 'name' => t('Period'));
  565. $punctuation['hyphen'] = array('value' => '-', 'name' => t('Hyphen'));
  566. $punctuation['underscore'] = array('value' => '_', 'name' => t('Underscore'));
  567. $punctuation['colon'] = array('value' => ':', 'name' => t('Colon'));
  568. $punctuation['semicolon'] = array('value' => ';', 'name' => t('Semicolon'));
  569. $punctuation['pipe'] = array('value' => '|', 'name' => t('Vertical bar (pipe)'));
  570. $punctuation['left_curly'] = array('value' => '{', 'name' => t('Left curly bracket'));
  571. $punctuation['left_square'] = array('value' => '[', 'name' => t('Left square bracket'));
  572. $punctuation['right_curly'] = array('value' => '}', 'name' => t('Right curly bracket'));
  573. $punctuation['right_square'] = array('value' => ']', 'name' => t('Right square bracket'));
  574. $punctuation['plus'] = array('value' => '+', 'name' => t('Plus sign'));
  575. $punctuation['equal'] = array('value' => '=', 'name' => t('Equal sign'));
  576. $punctuation['asterisk'] = array('value' => '*', 'name' => t('Asterisk'));
  577. $punctuation['ampersand'] = array('value' => '&', 'name' => t('Ampersand'));
  578. $punctuation['percent'] = array('value' => '%', 'name' => t('Percent sign'));
  579. $punctuation['caret'] = array('value' => '^', 'name' => t('Caret'));
  580. $punctuation['dollar'] = array('value' => '$', 'name' => t('Dollar sign'));
  581. $punctuation['hash'] = array('value' => '#', 'name' => t('Number sign (pound sign, hash)'));
  582. $punctuation['at'] = array('value' => '@', 'name' => t('At sign'));
  583. $punctuation['exclamation'] = array('value' => '!', 'name' => t('Exclamation mark'));
  584. $punctuation['tilde'] = array('value' => '~', 'name' => t('Tilde'));
  585. $punctuation['left_parenthesis'] = array('value' => '(', 'name' => t('Left parenthesis'));
  586. $punctuation['right_parenthesis'] = array('value' => ')', 'name' => t('Right parenthesis'));
  587. $punctuation['question_mark'] = array('value' => '?', 'name' => t('Question mark'));
  588. $punctuation['less_than'] = array('value' => '<', 'name' => t('Less-than sign'));
  589. $punctuation['greater_than'] = array('value' => '>', 'name' => t('Greater-than sign'));
  590. $punctuation['slash'] = array('value' => '/', 'name' => t('Slash'));
  591. $punctuation['back_slash'] = array('value' => '\\', 'name' => t('Backslash'));
  592. // Allow modules to alter the punctuation list and cache the result.
  593. drupal_alter('pathauto_punctuation_chars', $punctuation);
  594. cache_set($cid, $punctuation);
  595. }
  596. }
  597. return $punctuation;
  598. }
  599. /**
  600. * Fetch the maximum length of the {url_alias}.alias field from the schema.
  601. *
  602. * @return
  603. * An integer of the maximum URL alias length allowed by the database.
  604. */
  605. function _pathauto_get_schema_alias_maxlength() {
  606. $maxlength = &drupal_static(__FUNCTION__);
  607. if (!isset($maxlength)) {
  608. $schema = drupal_get_schema('url_alias');
  609. $maxlength = $schema['fields']['alias']['length'];
  610. }
  611. return $maxlength;
  612. }