source: sipes/modules_contrib/pathauto/pathauto.inc @ 49072ea

stableversion-3.0
Last change on this file since 49072ea was 49072ea, checked in by José Gregorio Puentes <jpuentes@…>, 8 años ago

se actualizo el modulo

  • Propiedad mode establecida a 100755
File size: 35.4 KB
Línea 
1<?php
2
3/**
4 * @file
5 * Miscellaneous functions for Pathauto.
6 *
7 * This also contains some constants giving human readable names to some numeric
8 * settings; they're included here as they're only rarely used outside this file
9 * anyway. Use module_load_include('inc', 'pathauto') if the constants need to
10 * be available.
11 *
12 * @ingroup pathauto
13 */
14
15/**
16 * Case should be left as is in the generated path.
17 */
18define('PATHAUTO_CASE_LEAVE_ASIS', 0);
19
20/**
21 * Case should be lowercased in the generated path.
22 */
23define('PATHAUTO_CASE_LOWER', 1);
24
25/**
26 * "Do nothing. Leave the old alias intact."
27 */
28define('PATHAUTO_UPDATE_ACTION_NO_NEW', 0);
29
30/**
31 * "Create a new alias. Leave the existing alias functioning."
32 */
33define('PATHAUTO_UPDATE_ACTION_LEAVE', 1);
34
35/**
36 * "Create a new alias. Delete the old alias."
37 */
38define('PATHAUTO_UPDATE_ACTION_DELETE', 2);
39
40/**
41 * "Create a new alias. Redirect from old alias."
42 *
43 * This is only available when the Path Redirect module is.
44 */
45define('PATHAUTO_UPDATE_ACTION_REDIRECT', 3);
46
47/**
48 * Remove the punctuation from the alias.
49 */
50define('PATHAUTO_PUNCTUATION_REMOVE', 0);
51
52/**
53 * Replace the punctuation with the separator in the alias.
54 */
55define('PATHAUTO_PUNCTUATION_REPLACE', 1);
56
57/**
58 * Leave the punctuation as it is in the alias.
59 */
60define('PATHAUTO_PUNCTUATION_DO_NOTHING', 2);
61
62/**
63 * Matches Unicode characters that are word boundaries.
64 *
65 * Characters with the following General_category (gc) property values are used
66 * as word boundaries. While this does not fully conform to the Word Boundaries
67 * algorithm described in http://unicode.org/reports/tr29, as PCRE does not
68 * contain the Word_Break property table, this simpler algorithm has to do.
69 * - Cc, Cf, Cn, Co, Cs: Other.
70 * - Pc, Pd, Pe, Pf, Pi, Po, Ps: Punctuation.
71 * - Sc, Sk, Sm, So: Symbols.
72 * - Zl, Zp, Zs: Separators.
73 *
74 * Non-boundary characters include the following General_category (gc) property
75 * values:
76 * - Ll, Lm, Lo, Lt, Lu: Letters.
77 * - Mc, Me, Mn: Combining Marks.
78 * - Nd, Nl, No: Numbers.
79 *
80 * Note that the PCRE property matcher is not used because we wanted to be
81 * compatible with Unicode 5.2.0 regardless of the PCRE version used (and any
82 * bugs in PCRE property tables).
83 *
84 * @see http://unicode.org/glossary
85 */
86define('PATHAUTO_PREG_CLASS_UNICODE_WORD_BOUNDARY',
87  '\x{0}-\x{2F}\x{3A}-\x{40}\x{5B}-\x{60}\x{7B}-\x{A9}\x{AB}-\x{B1}\x{B4}' .
88  '\x{B6}-\x{B8}\x{BB}\x{BF}\x{D7}\x{F7}\x{2C2}-\x{2C5}\x{2D2}-\x{2DF}' .
89  '\x{2E5}-\x{2EB}\x{2ED}\x{2EF}-\x{2FF}\x{375}\x{37E}-\x{385}\x{387}\x{3F6}' .
90  '\x{482}\x{55A}-\x{55F}\x{589}-\x{58A}\x{5BE}\x{5C0}\x{5C3}\x{5C6}' .
91  '\x{5F3}-\x{60F}\x{61B}-\x{61F}\x{66A}-\x{66D}\x{6D4}\x{6DD}\x{6E9}' .
92  '\x{6FD}-\x{6FE}\x{700}-\x{70F}\x{7F6}-\x{7F9}\x{830}-\x{83E}' .
93  '\x{964}-\x{965}\x{970}\x{9F2}-\x{9F3}\x{9FA}-\x{9FB}\x{AF1}\x{B70}' .
94  '\x{BF3}-\x{BFA}\x{C7F}\x{CF1}-\x{CF2}\x{D79}\x{DF4}\x{E3F}\x{E4F}' .
95  '\x{E5A}-\x{E5B}\x{F01}-\x{F17}\x{F1A}-\x{F1F}\x{F34}\x{F36}\x{F38}' .
96  '\x{F3A}-\x{F3D}\x{F85}\x{FBE}-\x{FC5}\x{FC7}-\x{FD8}\x{104A}-\x{104F}' .
97  '\x{109E}-\x{109F}\x{10FB}\x{1360}-\x{1368}\x{1390}-\x{1399}\x{1400}' .
98  '\x{166D}-\x{166E}\x{1680}\x{169B}-\x{169C}\x{16EB}-\x{16ED}' .
99  '\x{1735}-\x{1736}\x{17B4}-\x{17B5}\x{17D4}-\x{17D6}\x{17D8}-\x{17DB}' .
100  '\x{1800}-\x{180A}\x{180E}\x{1940}-\x{1945}\x{19DE}-\x{19FF}' .
101  '\x{1A1E}-\x{1A1F}\x{1AA0}-\x{1AA6}\x{1AA8}-\x{1AAD}\x{1B5A}-\x{1B6A}' .
102  '\x{1B74}-\x{1B7C}\x{1C3B}-\x{1C3F}\x{1C7E}-\x{1C7F}\x{1CD3}\x{1FBD}' .
103  '\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}' .
104  '\x{1FFD}-\x{206F}\x{207A}-\x{207E}\x{208A}-\x{208E}\x{20A0}-\x{20B8}' .
105  '\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}' .
106  '\x{2116}-\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}' .
107  '\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}-\x{214D}\x{214F}' .
108  '\x{2190}-\x{244A}\x{249C}-\x{24E9}\x{2500}-\x{2775}\x{2794}-\x{2B59}' .
109  '\x{2CE5}-\x{2CEA}\x{2CF9}-\x{2CFC}\x{2CFE}-\x{2CFF}\x{2E00}-\x{2E2E}' .
110  '\x{2E30}-\x{3004}\x{3008}-\x{3020}\x{3030}\x{3036}-\x{3037}' .
111  '\x{303D}-\x{303F}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{3190}-\x{3191}' .
112  '\x{3196}-\x{319F}\x{31C0}-\x{31E3}\x{3200}-\x{321E}\x{322A}-\x{3250}' .
113  '\x{3260}-\x{327F}\x{328A}-\x{32B0}\x{32C0}-\x{33FF}\x{4DC0}-\x{4DFF}' .
114  '\x{A490}-\x{A4C6}\x{A4FE}-\x{A4FF}\x{A60D}-\x{A60F}\x{A673}\x{A67E}' .
115  '\x{A6F2}-\x{A716}\x{A720}-\x{A721}\x{A789}-\x{A78A}\x{A828}-\x{A82B}' .
116  '\x{A836}-\x{A839}\x{A874}-\x{A877}\x{A8CE}-\x{A8CF}\x{A8F8}-\x{A8FA}' .
117  '\x{A92E}-\x{A92F}\x{A95F}\x{A9C1}-\x{A9CD}\x{A9DE}-\x{A9DF}' .
118  '\x{AA5C}-\x{AA5F}\x{AA77}-\x{AA79}\x{AADE}-\x{AADF}\x{ABEB}' .
119  '\x{E000}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' .
120  '\x{FE10}-\x{FE19}\x{FE30}-\x{FE6B}\x{FEFF}-\x{FF0F}\x{FF1A}-\x{FF20}' .
121  '\x{FF3B}-\x{FF40}\x{FF5B}-\x{FF65}\x{FFE0}-\x{FFFD}');
122
123/**
124 * Check to see if there is already an alias pointing to a different item.
125 *
126 * @param $alias
127 *   A string alias.
128 * @param $source
129 *   A string that is the internal path.
130 * @param $language
131 *   A string indicating the path's language.
132 * @return
133 *   TRUE if an alias exists, FALSE if not.
134 */
135function _pathauto_alias_exists($alias, $source, $language = '') {
136  $pid = db_result(db_query_range("SELECT pid FROM {url_alias} WHERE src <> '%s' AND dst = '%s' AND language IN ('%s', '') ORDER BY language DESC, pid DESC", $source, $alias, $language, 0, 1));
137
138  if (module_exists('path_redirect') && function_exists('path_redirect_delete_multiple')) {
139    // Delete from path_redirect the exact same alias to the same node.
140    path_redirect_delete_multiple(NULL, array('source' => $alias, 'redirect' => $source));
141
142    // If there still is this alias used in path_redirect, then create a different alias.
143    $redirects = path_redirect_load_multiple(NULL, array('source' => $alias));
144  }
145
146  if ($pid || !empty($redirects)) {
147    return TRUE;
148  }
149  else {
150    return FALSE;
151  }
152}
153
154/**
155 * Fetches an existing URL alias given a path and optional language.
156 *
157 * @param $source
158 *   An internal Drupal path.
159 * @param $language
160 *   An optional language code to look up the path in.
161 * @return
162 *   FALSE if no alias was found or an associative array containing the
163 *   following keys:
164 *   - pid: Unique path alias identifier.
165 *   - alias: The URL alias.
166 */
167function _pathauto_existing_alias_data($source, $language = '') {
168  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));
169}
170
171/**
172 * Clean up a string segment to be used in an URL alias.
173 *
174 * Performs the following possible alterations:
175 * - Remove all HTML tags.
176 * - Process the string through the transliteration module.
177 * - Replace or remove punctuation with the separator character.
178 * - Remove back-slashes.
179 * - Replace non-ascii and non-numeric characters with the separator.
180 * - Remove common words.
181 * - Replace whitespace with the separator character.
182 * - Trim duplicate, leading, and trailing separators.
183 * - Convert to lower-case.
184 * - Shorten to a desired length and logical position based on word boundaries.
185 *
186 * This function should *not* be called on URL alias or path strings because it
187 * is assumed that they are already clean.
188 *
189 * @param $string
190 *   A string to clean.
191 * @param array $options
192 *   (optional) A keyed array of settings and flags to control the Pathauto
193 *   clean string replacement process. Supported options are:
194 *   - langcode: A language code to be used when translating strings.
195 *
196 * @return
197 *   The cleaned string.
198 */
199function pathauto_cleanstring($string) {
200  // Use the advanced drupal_static() pattern, since this is called very often.
201  static $drupal_static_fast;
202  if (!isset($drupal_static_fast)) {
203    $drupal_static_fast['cache'] = &pathauto_static(__FUNCTION__);
204  }
205  $cache = &$drupal_static_fast['cache'];
206
207  // Generate and cache variables used in this function so that on the second
208  // call to pathauto_cleanstring() we focus on processing.
209  if (!isset($cache)) {
210    $cache = array(
211      'separator' => variable_get('pathauto_separator', '-'),
212      'strings' => array(),
213      'transliterate' => variable_get('pathauto_transliterate', FALSE) && module_exists('transliteration'),
214      'punctuation' => array(),
215      'reduce_ascii' => (bool) variable_get('pathauto_reduce_ascii', FALSE),
216      'ignore_words_regex' => FALSE,
217      'lowercase' => (bool) variable_get('pathauto_case', PATHAUTO_CASE_LOWER),
218      'maxlength' => min(variable_get('pathauto_max_component_length', 100), _pathauto_get_schema_alias_maxlength()),
219    );
220
221    // Generate and cache the punctuation replacements for strtr().
222    $punctuation = pathauto_punctuation_chars();
223    foreach ($punctuation as $name => $details) {
224      $action = variable_get('pathauto_punctuation_' . $name, PATHAUTO_PUNCTUATION_REMOVE);
225      switch ($action) {
226        case PATHAUTO_PUNCTUATION_REMOVE:
227          $cache['punctuation'][$details['value']] = '';
228          break;
229        case PATHAUTO_PUNCTUATION_REPLACE:
230          $cache['punctuation'][$details['value']] = $cache['separator'];
231          break;
232        case PATHAUTO_PUNCTUATION_DO_NOTHING:
233          // Literally do nothing.
234          break;
235      }
236    }
237
238    // Generate and cache the ignored words regular expression.
239    $ignore_words = variable_get('pathauto_ignore_words', PATHAUTO_IGNORE_WORDS);
240    $ignore_words_regex = preg_replace(array('/^[,\s]+|[,\s]+$/', '/[,\s]+/'), array('', '\b|\b'), $ignore_words);
241    if ($ignore_words_regex) {
242      $cache['ignore_words_regex'] = '\b' . $ignore_words_regex . '\b';
243      if (function_exists('mb_eregi_replace')) {
244        $cache['ignore_words_callback'] = 'mb_eregi_replace';
245      }
246      else {
247        $cache['ignore_words_callback'] = 'preg_replace';
248        $cache['ignore_words_regex'] = '/' . $cache['ignore_words_regex'] . '/i';
249      }
250    }
251  }
252
253  // Empty strings do not need any proccessing.
254  if ($string === '' || $string === NULL) {
255    return '';
256  }
257
258  $langcode = NULL;
259  if (!empty($options['language']->language)) {
260    $langcode = $options['language']->language;
261  }
262  elseif (!empty($options['langcode'])) {
263    $langcode = $options['langcode'];
264  }
265
266  // Check if the string has already been processed, and if so return the
267  // cached result.
268  if (isset($cache['strings'][$langcode][$string])) {
269    return $cache['strings'][$langcode][$string];
270  }
271
272  // Remove all HTML tags from the string.
273  $output = strip_tags(decode_entities($string));
274
275  // Optionally transliterate (by running through the Transliteration module)
276  if ($cache['transliterate']) {
277    $output = transliteration_get($output, $cache['reduce_ascii'] ? '' : '?', $langcode);
278  }
279
280  // Replace or drop punctuation based on user settings
281  $output = strtr($output, $cache['punctuation']);
282
283  // Reduce strings to letters and numbers
284  if ($cache['reduce_ascii']) {
285    $output = preg_replace('/[^a-zA-Z0-9\/]+/', $cache['separator'], $output);
286  }
287
288  // Get rid of words that are on the ignore list
289  if ($cache['ignore_words_regex']) {
290    $words_removed = $cache['ignore_words_callback']($cache['ignore_words_regex'], '', $output);
291    if (drupal_strlen(trim($words_removed)) > 0) {
292      $output = $words_removed;
293    }
294  }
295
296  // Always replace whitespace with the separator.
297  $output = preg_replace('/\s+/', $cache['separator'], $output);
298
299  // Trim duplicates and remove trailing and leading separators.
300  $output = _pathauto_clean_separators($output, $cache['separator']);
301
302  // Optionally convert to lower case.
303  if ($cache['lowercase']) {
304    $output = drupal_strtolower($output);
305  }
306
307  // Shorten to a logical place based on word boundaries.
308  $output = pathauto_truncate_utf8($output, $cache['maxlength'], TRUE);
309
310  // Cache this result in the static array.
311  $cache['strings'][$langcode][$string] = $output;
312
313  return $output;
314}
315
316/**
317 * Trims duplicate, leading, and trailing separators from a string.
318 *
319 * @param $string
320 *   The string to clean path separators from.
321 * @param $separator
322 *   The path separator to use when cleaning.
323 * @return
324 *   The cleaned version of the string.
325 *
326 * @see pathauto_cleanstring()
327 * @see pathauto_clean_alias()
328 */
329function _pathauto_clean_separators($string, $separator = NULL) {
330  static $default_separator;
331
332  if (!isset($separator)) {
333    if (!isset($default_separator)) {
334      $default_separator = variable_get('pathauto_separator', '-');
335    }
336    $separator = $default_separator;
337  }
338
339  $output = $string;
340
341  if (strlen($separator)) {
342    // Trim any leading or trailing separators.
343    $output = trim($output, $separator);
344
345    // Escape the separator for use in regular expressions.
346    $seppattern = preg_quote($separator, '/');
347
348    // Replace multiple separators with a single one.
349    $output = preg_replace("/$seppattern+/", $separator, $output);
350
351    // Replace trailing separators around slashes.
352    if ($separator !== '/') {
353      $output = preg_replace("/\/+$seppattern\/+|$seppattern\/+|\/+$seppattern/", "/", $output);
354    }
355  }
356
357  return $output;
358}
359
360/**
361 * Clean up an URL alias.
362 *
363 * Performs the following alterations:
364 * - Trim duplicate, leading, and trailing back-slashes.
365 * - Trim duplicate, leading, and trailing separators.
366 * - Shorten to a desired length and logical position based on word boundaries.
367 *
368 * @param $alias
369 *   A string with the URL alias to clean up.
370 * @return
371 *   The cleaned URL alias.
372 */
373function pathauto_clean_alias($alias) {
374  $cache = &pathauto_static(__FUNCTION__);
375
376  if (!isset($cache)) {
377    $cache = array(
378      'maxlength' => min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength()),
379    );
380  }
381
382  $output = $alias;
383
384  // Trim duplicate, leading, and trailing separators. Do this before cleaning
385  // backslashes since a pattern like "[token1]/[token2]-[token3]/[token4]"
386  // could end up like "value1/-/value2" and if backslashes were cleaned first
387  // this would result in a duplicate blackslash.
388  $output = _pathauto_clean_separators($output);
389
390  // Trim duplicate, leading, and trailing backslashes.
391  $output = _pathauto_clean_separators($output, '/');
392
393  // Shorten to a logical place based on word boundaries.
394  $output = pathauto_truncate_utf8($output, $cache['maxlength'], TRUE);
395
396  return $output;
397}
398
399/**
400 * Apply patterns to create an alias.
401 *
402 * @param $module
403 *   The name of your module (e.g., 'node').
404 * @param $op
405 *   Operation being performed on the content being aliased
406 *   ('insert', 'update', 'return', or 'bulkupdate').
407 * @param $source
408 *   An internal Drupal path to be aliased.
409 * @param $data
410 *   An array of keyed objects to pass to token_replace(). For simple
411 *   replacement scenarios 'node', 'user', and others are common keys, with an
412 *   accompanying node or user object being the value.  Only one key/value pair
413 *   is supported, otherwise you may pass the results from
414 *   pathauto_get_placeholders() here.
415 * @param $entity_id
416 *   (deprecated) The entity ID (node ID, user ID, etc.). This parameter is
417 *   deprecated and is not actually used.
418 * @param $type
419 *   For modules which provided pattern items in hook_pathauto(),
420 *   the relevant identifier for the specific item to be aliased
421 *   (e.g., $node->type).
422 * @param $language
423 *   A string specify the path's language.
424 * @return
425 *   The alias that was created.
426 *
427 * @see _pathauto_set_alias()
428 * @see pathauto_get_placeholders()
429 */
430function pathauto_create_alias($module, $op, $source, $data, $entity_id = NULL, $type = NULL, $language = '') {
431  // Retrieve and apply the pattern for this content type.
432  $pattern = pathauto_pattern_load_by_entity($module, $type, $language);
433
434  // Allow other modules to alter the pattern.
435  $context = array(
436    'module' => $module,
437    'op' => $op,
438    'source' => $source,
439    'data' => $data,
440    'type' => $type,
441    'language' => &$language,
442  );
443  drupal_alter('pathauto_pattern', $pattern, $context);
444
445  if (empty($pattern)) {
446    // No pattern? Do nothing (otherwise we may blow away existing aliases...)
447    return '';
448  }
449
450  // Support for when $source and $placeholders were swapped.
451  if (is_array($source) && is_string($data)) {
452    $placeholders = $source;
453    $source = $data;
454  }
455  elseif (is_array($data) && !isset($data['tokens']) && !isset($data['values'])) {
456    $placeholders = pathauto_get_placeholders(key($data), current($data), $pattern, array('language' => (object) array('language' => $language)));
457  }
458  else {
459    $placeholders = $data;
460  }
461
462  // Special handling when updating an item which is already aliased.
463  $existing_alias = NULL;
464  if ($op == 'update' || $op == 'bulkupdate') {
465    if ($existing_alias = _pathauto_existing_alias_data($source, $language)) {
466      switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
467        case PATHAUTO_UPDATE_ACTION_NO_NEW:
468          // If an alias already exists, and the update action is set to do nothing,
469          // then gosh-darn it, do nothing.
470          return '';
471      }
472    }
473  }
474
475  // Replace the placeholders with the values provided by the module.
476  $alias = str_replace($placeholders['tokens'], $placeholders['values'], $pattern);
477
478  // Check if the token replacement has not actually replaced any values. If
479  // that is the case, then stop because we should not generate an alias.
480  // @see token_scan()
481  $pattern_tokens_removed = preg_replace('/\[([^\s]+?)\]/', '', $pattern);
482  if ($alias === $pattern_tokens_removed) {
483    return '';
484  }
485
486  $alias = pathauto_clean_alias($alias);
487
488  // Allow other modules to alter the alias.
489  $context['source'] = &$source;
490  $context['pattern'] = $pattern;
491  drupal_alter('pathauto_alias', $alias, $context);
492
493  // If we have arrived at an empty string, discontinue.
494  if (!drupal_strlen($alias)) {
495    return '';
496  }
497
498  // If the alias already exists, generate a new, hopefully unique, variant.
499  $original_alias = $alias;
500  pathauto_alias_uniquify($alias, $source, $language);
501  if ($original_alias != $alias) {
502    // Alert the user why this happened.
503    _pathauto_verbose(t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array(
504      '%original_alias' => $original_alias,
505      '%alias' => $alias,
506    )), $op);
507  }
508
509  // Return the generated alias if requested.
510  if ($op == 'return') {
511    return $alias;
512  }
513
514  // Build the new path alias array and send it off to be created.
515  $path = array(
516    'source' => $source,
517    'alias' => $alias,
518    'language' => $language,
519  );
520  $success = _pathauto_set_alias($path, $existing_alias, $op);
521  return $success ? $alias : NULL;
522}
523
524/**
525 * Check to ensure a path alias is unique and add suffix variants if necessary.
526 *
527 * Given an alias 'content/test' if a path alias with the exact alias already
528 * exists, the function will change the alias to 'content/test-0' and will
529 * increase the number suffix until it finds a unique alias.
530 *
531 * @param $alias
532 *   A string with the alias. Can be altered by reference.
533 * @param $source
534 *   A string with the path source.
535 * @param $langcode
536 *   A string with a language code.
537 */
538function pathauto_alias_uniquify(&$alias, $source, $langcode) {
539  if (!_pathauto_alias_exists($alias, $source, $langcode)) {
540    return;
541  }
542
543  // If the alias already exists, generate a new, hopefully unique, variant
544  $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength());
545  $separator = variable_get('pathauto_separator', '-');
546  $original_alias = $alias;
547
548  $i = 0;
549  do {
550    // Append an incrementing numeric suffix until we find a unique alias.
551    $unique_suffix = $separator . $i;
552    $alias = pathauto_truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix), TRUE) . $unique_suffix;
553    $i++;
554  } while (_pathauto_alias_exists($alias, $source, $langcode));
555}
556
557/**
558 * Verify if the given path is a valid menu callback.
559 *
560 * Taken from menu_execute_active_handler().
561 *
562 * @param $path
563 *   A string containing a relative path.
564 * @return
565 *   TRUE if the path already exists.
566 */
567function _pathauto_path_is_callback($path) {
568  $menu = menu_get_item($path);
569  if (isset($menu['path']) && $menu['path'] == $path) {
570    return TRUE;
571  }
572  elseif (is_file('./' . $path) || is_dir('./' . $path)) {
573    // Do not allow existing files or directories to get assigned an automatic
574    // alias. Note that we do not need to use is_link() to check for symbolic
575    // links since this returns TRUE for either is_file() or is_dir() already.
576    return TRUE;
577  }
578  return FALSE;
579}
580
581/**
582 * Private function for Pathauto to create an alias.
583 *
584 * @param $path
585 *   An associative array containing the following keys:
586 *   - source: The internal system path.
587 *   - alias: The URL alias.
588 *   - pid: (optional) Unique path alias identifier.
589 *   - language: (optional) The language of the alias.
590 * @param $existing_alias
591 *   (optional) An associative array of the existing path alias.
592 * @param $op
593 *   An optional string with the operation being performed.
594 *
595 * @return
596 *   The saved path from path_save() or NULL if the path was not saved.
597 *
598 * @see path_set_alias()
599 */
600function _pathauto_set_alias($path, $existing_alias = NULL, $op = NULL) {
601  $verbose = _pathauto_verbose(NULL, $op);
602
603  // Alert users that an existing callback cannot be overridden automatically
604  if (_pathauto_path_is_callback($path['alias'])) {
605    if ($verbose) {
606      _pathauto_verbose(t('Ignoring alias %alias due to existing path conflict.', array('%alias' => $path['alias'])));
607    }
608    return;
609  }
610  // Alert users if they are trying to create an alias that is the same as the internal path
611  if ($path['source'] == $path['alias']) {
612    if ($verbose) {
613      _pathauto_verbose(t('Ignoring alias %alias because it is the same as the internal path.', array('%alias' => $path['alias'])));
614    }
615    return;
616  }
617
618  // Skip replacing the current alias with an identical alias
619  if (empty($existing_alias) || $existing_alias['alias'] != $path['alias']) {
620    $path += array('pid' => NULL, 'language' => '');
621
622    // If there is already an alias, respect some update actions.
623    if (!empty($existing_alias)) {
624      switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
625        case PATHAUTO_UPDATE_ACTION_NO_NEW:
626          // Do not create the alias.
627          return;
628        case PATHAUTO_UPDATE_ACTION_LEAVE:
629          // Create a new alias instead of overwriting the existing by leaving
630          // $path['pid'] empty.
631          break;
632        case PATHAUTO_UPDATE_ACTION_REDIRECT:
633          // Create a redirect
634          if (module_exists('path_redirect') && function_exists('path_redirect_save')) {
635            $redirect = array(
636              'source' => $existing_alias['alias'],
637              'language' => $existing_alias['language'],
638              'redirect' => $path['source'],
639            );
640            path_redirect_save($redirect);
641          }
642          // Intentionally fall through to the next condition since we still
643          // want to replace the existing alias.
644        case PATHAUTO_UPDATE_ACTION_DELETE:
645          // Both the redirect and delete actions should overwrite the existing
646          // alias.
647          $path['pid'] = $existing_alias['pid'];
648          break;
649      }
650    }
651
652    // Save the path array.
653    path_set_alias($path['source'], $path['alias'], $path['pid'], $path['language']);
654
655    if ($verbose) {
656      if (!empty($redirect)) {
657        _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'])));
658      }
659      elseif (!empty($existing_alias['pid'])) {
660        _pathauto_verbose(t('Created new alias %alias for %source, replacing %old_alias.', array('%alias' => $path['alias'], '%source' => $path['source'], '%old_alias' => $existing_alias['alias'])));
661      }
662      else {
663        _pathauto_verbose(t('Created new alias %alias for %source.', array('%alias' => $path['alias'], '%source' => $path['source'])));
664      }
665    }
666
667    return $path;
668  }
669}
670
671/**
672 * Output a helpful message if verbose output is enabled.
673 *
674 * Verbose output is only enabled when:
675 * - The 'pathauto_verbose' setting is enabled.
676 * - The current user has the 'notify of path changes' permission.
677 * - The $op parameter is anything but 'bulkupdate' or 'return'.
678 *
679 * @param $message
680 *   An optional string of the verbose message to display. This string should
681 *   already be run through t().
682 * @param $op
683 *   An optional string with the operation being performed.
684 * @return
685 *   TRUE if verbose output is enabled, or FALSE otherwise.
686 */
687function _pathauto_verbose($message = NULL, $op = NULL) {
688  static $verbose;
689
690  if (!isset($verbose)) {
691    $verbose = variable_get('pathauto_verbose', FALSE) && user_access('notify of path changes');
692  }
693
694  if (!$verbose || (isset($op) && in_array($op, array('bulkupdate', 'return')))) {
695    return FALSE;
696  }
697
698  if ($message) {
699    drupal_set_message($message);
700  }
701
702  return $verbose;
703}
704
705/**
706 * Generalized function to get tokens across all Pathauto types.
707 *
708 * @param $type
709 *   The token type to pass to token_get_values().
710 * @param $object
711 *   The object to pass to token_get_values().
712 * @param $text
713 *   (optional) The string that will be replaced with tokens. If provided
714 *   and the token_scan() function is available it will be used to trim down
715 *   the number of tokens that need to be passed to
716 *   pathauto_clean_token_values().
717 *
718 * @return
719 *   Tokens for that object formatted in the way that
720 *   Pathauto expects to see them.
721 */
722function pathauto_get_placeholders($type, $object, $text = '', array $options = array()) {
723  $options += array('pathauto' => TRUE);
724
725  $full = token_get_values($type, $object, TRUE, $options);
726
727  // Attempt to reduce the tokens to only the ones that will actually be used
728  // before passing into pathauto_clean_token_values().
729  static $token_scan_exists;
730  if (!isset($token_scan_exists)) {
731    $token_scan_exists = function_exists('token_scan');
732  }
733  if ($token_scan_exists && !empty($text)) {
734    $used_tokens = token_scan($text);
735    foreach ($full->tokens as $index => $token) {
736      if (!in_array($token, $used_tokens)) {
737        unset($full->tokens[$index]);
738        unset($full->values[$index]);
739      }
740    }
741  }
742
743  $tokens = token_prepare_tokens($full->tokens);
744  $values = pathauto_clean_token_values($full, $options);
745  return array('tokens' => $tokens, 'values' => $values);
746}
747
748/**
749 * Clean tokens so they are URL friendly.
750 *
751 * @param $full
752 *   An array of token values from token_get_values() that need to be "cleaned"
753 *   for use in the URL.
754 *
755 * @return
756 *   An array of the cleaned tokens.
757 */
758function pathauto_clean_token_values($full, $options = array()) {
759  $replacements = array();
760  foreach ($full->values as $key => $value) {
761    $token = $full->tokens[$key];
762    if (strpos($token, 'path') !== FALSE && is_array($value) && !empty($options['pathauto'])) {
763      // If the token name contains 'path', the token value is an array, and
764      // the 'pathauto' option was passed to token_get_values(), then the token
765      // should have each segment cleaned, and then glued back together to
766      // construct a value resembling an URL.
767      $segments = array();
768      foreach ($value as $segment) {
769        $segments[] = pathauto_cleanstring($segment, $options);
770      }
771      $replacements[$token] = implode('/', $segments);
772    }
773    elseif (preg_match('/(path|alias|url|url-brief)(-raw)?$/', $token)) {
774      // Token name matches an URL-type name and should be left raw.
775      $replacements[$token] = $value;
776    }
777    else {
778      // Token is not an URL, so it should have its value cleaned.
779      $replacements[$token] = pathauto_cleanstring($value, $options);
780    }
781  }
782  return $replacements;
783}
784
785/**
786 * Return an array of arrays for punctuation values.
787 *
788 * Returns an array of arrays for punctuation values keyed by a name, including
789 * the value and a textual description.
790 * Can and should be expanded to include "all" non text punctuation values.
791 *
792 * @return
793 *   An array of arrays for punctuation values keyed by a name, including the
794 *   value and a textual description.
795 */
796function pathauto_punctuation_chars() {
797  $punctuation = &pathauto_static(__FUNCTION__);
798
799  if (!isset($punctuation)) {
800    $cid = 'pathauto:punctuation:' . $GLOBALS['language']->language;
801    if ($cache = cache_get($cid)) {
802      $punctuation = $cache->data;
803    }
804    else {
805      $punctuation = array();
806      $punctuation['double_quotes']      = array('value' => '"', 'name' => t('Double quotation marks'));
807      $punctuation['quotes']             = array('value' => '\'', 'name' => t("Single quotation marks (apostrophe)"));
808      $punctuation['backtick']           = array('value' => '`', 'name' => t('Back tick'));
809      $punctuation['comma']              = array('value' => ',', 'name' => t('Comma'));
810      $punctuation['period']             = array('value' => '.', 'name' => t('Period'));
811      $punctuation['hyphen']             = array('value' => '-', 'name' => t('Hyphen'));
812      $punctuation['underscore']         = array('value' => '_', 'name' => t('Underscore'));
813      $punctuation['colon']              = array('value' => ':', 'name' => t('Colon'));
814      $punctuation['semicolon']          = array('value' => ';', 'name' => t('Semicolon'));
815      $punctuation['pipe']               = array('value' => '|', 'name' => t('Vertical bar (pipe)'));
816      $punctuation['left_curly']         = array('value' => '{', 'name' => t('Left curly bracket'));
817      $punctuation['left_square']        = array('value' => '[', 'name' => t('Left square bracket'));
818      $punctuation['right_curly']        = array('value' => '}', 'name' => t('Right curly bracket'));
819      $punctuation['right_square']       = array('value' => ']', 'name' => t('Right square bracket'));
820      $punctuation['plus']               = array('value' => '+', 'name' => t('Plus sign'));
821      $punctuation['equal']              = array('value' => '=', 'name' => t('Equal sign'));
822      $punctuation['asterisk']           = array('value' => '*', 'name' => t('Asterisk'));
823      $punctuation['ampersand']          = array('value' => '&', 'name' => t('Ampersand'));
824      $punctuation['percent']            = array('value' => '%', 'name' => t('Percent sign'));
825      $punctuation['caret']              = array('value' => '^', 'name' => t('Caret'));
826      $punctuation['dollar']             = array('value' => '$', 'name' => t('Dollar sign'));
827      $punctuation['hash']               = array('value' => '#', 'name' => t('Number sign (pound sign, hash)'));
828      $punctuation['at']                 = array('value' => '@', 'name' => t('At sign'));
829      $punctuation['exclamation']        = array('value' => '!', 'name' => t('Exclamation mark'));
830      $punctuation['tilde']              = array('value' => '~', 'name' => t('Tilde'));
831      $punctuation['left_parenthesis']   = array('value' => '(', 'name' => t('Left parenthesis'));
832      $punctuation['right_parenthesis']  = array('value' => ')', 'name' => t('Right parenthesis'));
833      $punctuation['question_mark']      = array('value' => '?', 'name' => t('Question mark'));
834      $punctuation['less_than']          = array('value' => '<', 'name' => t('Less-than sign'));
835      $punctuation['greater_than']       = array('value' => '>', 'name' => t('Greater-than sign'));
836      $punctuation['slash']              = array('value' => '/', 'name' => t('Slash'));
837      $punctuation['back_slash']         = array('value' => '\\', 'name' => t('Backslash'));
838
839      // Allow modules to alter the punctuation list and cache the result.
840      drupal_alter('pathauto_punctuation_chars', $punctuation);
841      cache_set($cid, $punctuation);
842    }
843  }
844
845  return $punctuation;
846}
847
848/**
849 * Fetch the maximum length of the {url_alias}.dst field from the schema.
850 *
851 * @return
852 *   An integer of the maximum URL alias length allowed by the database.
853 */
854function _pathauto_get_schema_alias_maxlength() {
855  static $maxlength;
856  if (!isset($maxlength)) {
857    $schema = drupal_get_schema('url_alias');
858    $maxlength = $schema['fields']['dst']['length'];
859  }
860  return $maxlength;
861}
862
863/**
864 * Truncates a UTF-8-encoded string safely to a number of characters.
865 *
866 * This is a functional backport of Drupal 7's truncate_utf8().
867 *
868 * @param $string
869 *   The string to truncate.
870 * @param $max_length
871 *   An upper limit on the returned string length, including trailing ellipsis
872 *   if $add_ellipsis is TRUE.
873 * @param $wordsafe
874 *   If TRUE, attempt to truncate on a word boundary. Word boundaries are
875 *   spaces, punctuation, and Unicode characters used as word boundaries in
876 *   non-Latin languages; see PREG_CLASS_UNICODE_WORD_BOUNDARY for more
877 *   information.  If a word boundary cannot be found that would make the length
878 *   of the returned string fall within length guidelines (see parameters
879 *   $max_return_length and $min_wordsafe_length), word boundaries are ignored.
880 * @param $add_ellipsis
881 *   If TRUE, add t('...') to the end of the truncated string (defaults to
882 *   FALSE). The string length will still fall within $max_return_length.
883 * @param $min_wordsafe_length
884 *   If $wordsafe is TRUE, the minimum acceptable length for truncation (before
885 *   adding an ellipsis, if $add_ellipsis is TRUE). Has no effect if $wordsafe
886 *   is FALSE. This can be used to prevent having a very short resulting string
887 *   that will not be understandable. For instance, if you are truncating the
888 *   string "See myverylongurlexample.com for more information" to a word-safe
889 *   return length of 20, the only available word boundary within 20 characters
890 *   is after the word "See", which wouldn't leave a very informative string. If
891 *   you had set $min_wordsafe_length to 10, though, the function would realize
892 *   that "See" alone is too short, and would then just truncate ignoring word
893 *   boundaries, giving you "See myverylongurl..." (assuming you had set
894 *   $add_ellipses to TRUE).
895 *
896 * @return
897 *   The truncated string.
898 */
899function pathauto_truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) {
900  $ellipsis = '';
901  $max_length = max($max_length, 0);
902  $min_wordsafe_length = max($min_wordsafe_length, 0);
903
904  if (drupal_strlen($string) <= $max_length) {
905    // No truncation needed, so don't add ellipsis, just return.
906    return $string;
907  }
908
909  if ($add_ellipsis) {
910    // Truncate ellipsis in case $max_length is small.
911    $ellipsis = drupal_substr(t('...'), 0, $max_length);
912    $max_length -= drupal_strlen($ellipsis);
913    $max_length = max($max_length, 0);
914  }
915
916  if ($max_length <= $min_wordsafe_length) {
917    // Do not attempt word-safe if lengths are bad.
918    $wordsafe = FALSE;
919  }
920
921  if ($wordsafe) {
922    $matches = array();
923    // Find the last word boundary, if there is one within $min_wordsafe_length
924    // to $max_length characters. preg_match() is always greedy, so it will
925    // find the longest string possible.
926    $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . PATHAUTO_PREG_CLASS_UNICODE_WORD_BOUNDARY . ']/u', $string, $matches);
927    if ($found) {
928      $string = $matches[1];
929    }
930    else {
931      $string = drupal_substr($string, 0, $max_length);
932    }
933  }
934  else {
935    $string = drupal_substr($string, 0, $max_length);
936  }
937
938  if ($add_ellipsis) {
939    $string .= $ellipsis;
940  }
941
942  return $string;
943}
944
945/**
946 * Fetch an array of non-raw tokens that have matching raw tokens.
947 *
948 * @return
949 *   An array of tokens.
950 */
951function _pathauto_get_raw_tokens() {
952  static $raw_tokens;
953
954  if (!isset($raw_tokens)) {
955    $raw_tokens = array();
956
957    // Build one big list of tokens.
958    foreach (token_get_list('all') as $tokens) {
959      $raw_tokens = array_merge($raw_tokens, array_keys($tokens));
960    }
961
962    // Filter out any tokens without -raw as a suffix.
963    foreach ($raw_tokens as $index => $token) {
964      if (substr($token, -4) !== '-raw') {
965        unset($raw_tokens[$index]);
966      }
967    }
968
969    array_unique($raw_tokens);
970  }
971
972  return $raw_tokens;
973}
Nota: Vea TracBrowser para ayuda de uso del navegador del repositorio.