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 | */ |
---|
18 | define('PATHAUTO_CASE_LEAVE_ASIS', 0); |
---|
19 | |
---|
20 | /** |
---|
21 | * Case should be lowercased in the generated path. |
---|
22 | */ |
---|
23 | define('PATHAUTO_CASE_LOWER', 1); |
---|
24 | |
---|
25 | /** |
---|
26 | * "Do nothing. Leave the old alias intact." |
---|
27 | */ |
---|
28 | define('PATHAUTO_UPDATE_ACTION_NO_NEW', 0); |
---|
29 | |
---|
30 | /** |
---|
31 | * "Create a new alias. Leave the existing alias functioning." |
---|
32 | */ |
---|
33 | define('PATHAUTO_UPDATE_ACTION_LEAVE', 1); |
---|
34 | |
---|
35 | /** |
---|
36 | * "Create a new alias. Delete the old alias." |
---|
37 | */ |
---|
38 | define('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 | */ |
---|
45 | define('PATHAUTO_UPDATE_ACTION_REDIRECT', 3); |
---|
46 | |
---|
47 | /** |
---|
48 | * Remove the punctuation from the alias. |
---|
49 | */ |
---|
50 | define('PATHAUTO_PUNCTUATION_REMOVE', 0); |
---|
51 | |
---|
52 | /** |
---|
53 | * Replace the punctuation with the separator in the alias. |
---|
54 | */ |
---|
55 | define('PATHAUTO_PUNCTUATION_REPLACE', 1); |
---|
56 | |
---|
57 | /** |
---|
58 | * Leave the punctuation as it is in the alias. |
---|
59 | */ |
---|
60 | define('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 | */ |
---|
86 | define('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 | */ |
---|
135 | function _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 | */ |
---|
167 | function _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 | */ |
---|
199 | function 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 | */ |
---|
329 | function _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 | */ |
---|
373 | function 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 | */ |
---|
430 | function 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 | */ |
---|
538 | function 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 | */ |
---|
567 | function _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 | */ |
---|
600 | function _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 | */ |
---|
687 | function _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 | */ |
---|
722 | function 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 | */ |
---|
758 | function 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 | */ |
---|
796 | function 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 | */ |
---|
854 | function _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 | */ |
---|
899 | function 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 | */ |
---|
951 | function _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 | } |
---|