1 | <?php |
---|
2 | |
---|
3 | /** |
---|
4 | * @file |
---|
5 | * The Token API module. |
---|
6 | * |
---|
7 | * The Token module provides an API for providing tokens to other modules. |
---|
8 | * Tokens are small bits of text that can be placed into larger documents |
---|
9 | * via simple placeholders, like %site-name or [user]. |
---|
10 | * |
---|
11 | * @ingroup token |
---|
12 | */ |
---|
13 | |
---|
14 | /** |
---|
15 | * The default token prefix string. |
---|
16 | */ |
---|
17 | define('TOKEN_PREFIX', '['); |
---|
18 | |
---|
19 | /** |
---|
20 | * The default token suffix string. |
---|
21 | */ |
---|
22 | define('TOKEN_SUFFIX', ']'); |
---|
23 | |
---|
24 | /** |
---|
25 | * Implements hook_help(). |
---|
26 | */ |
---|
27 | function token_help($path, $arg) { |
---|
28 | if ($path == 'admin/help#token') { |
---|
29 | $output = '<dl>'; |
---|
30 | $output .= '<dt>' . t('List of the currently available tokens on this site') . '</dt>'; |
---|
31 | $output .= '<dd>' . theme('token_tree', 'all', TRUE, FALSE) . '</dd>'; |
---|
32 | $output .= '</dl>'; |
---|
33 | return $output; |
---|
34 | } |
---|
35 | } |
---|
36 | |
---|
37 | /** |
---|
38 | * Return an array of the core modules supported by token.module. |
---|
39 | */ |
---|
40 | function _token_core_supported_modules() { |
---|
41 | return array('node', 'user', 'taxonomy', 'comment', 'menu', 'book'); |
---|
42 | } |
---|
43 | |
---|
44 | /** |
---|
45 | * Implements hook_menu(). |
---|
46 | */ |
---|
47 | function token_menu() { |
---|
48 | $items = array(); |
---|
49 | |
---|
50 | // Devel token pages. |
---|
51 | if (module_exists('devel')) { |
---|
52 | $items['node/%node/devel/token'] = array( |
---|
53 | 'title' => 'Tokens', |
---|
54 | 'page callback' => 'token_devel_token_object', |
---|
55 | 'page arguments' => array('node', 1), |
---|
56 | 'access arguments' => array('access devel information'), |
---|
57 | 'type' => MENU_LOCAL_TASK, |
---|
58 | 'file' => 'token.pages.inc', |
---|
59 | 'weight' => 5, |
---|
60 | ); |
---|
61 | $items['user/%user/devel/token'] = array( |
---|
62 | 'title' => 'Tokens', |
---|
63 | 'page callback' => 'token_devel_token_object', |
---|
64 | 'page arguments' => array('user', 1), |
---|
65 | 'access arguments' => array('access devel information'), |
---|
66 | 'type' => MENU_LOCAL_TASK, |
---|
67 | 'file' => 'token.pages.inc', |
---|
68 | 'weight' => 5, |
---|
69 | ); |
---|
70 | } |
---|
71 | |
---|
72 | return $items; |
---|
73 | } |
---|
74 | |
---|
75 | /** |
---|
76 | * Implements hook_theme(). |
---|
77 | */ |
---|
78 | function token_theme() { |
---|
79 | return array( |
---|
80 | 'token_help' => array( |
---|
81 | 'arguments' => array('type' => 'all', 'prefix' => TOKEN_PREFIX, 'suffix' => TOKEN_SUFFIX), |
---|
82 | 'file' => 'token.pages.inc', |
---|
83 | ), |
---|
84 | 'token_tree' => array( |
---|
85 | 'arguments' => array('token_types' => array(), 'global_types' => TRUE , 'click_insert' => TRUE), |
---|
86 | 'file' => 'token.pages.inc', |
---|
87 | ), |
---|
88 | ); |
---|
89 | } |
---|
90 | |
---|
91 | /** |
---|
92 | * Implements hook_token_values(). |
---|
93 | */ |
---|
94 | function token_token_values($type, $object = NULL) { |
---|
95 | global $user; |
---|
96 | $values = array(); |
---|
97 | |
---|
98 | switch ($type) { |
---|
99 | case 'global': |
---|
100 | // Current user tokens. |
---|
101 | $values['user-name'] = $user->uid ? $user->name : variable_get('anonymous', t('Anonymous')); |
---|
102 | $values['user-id'] = $user->uid ? $user->uid : 0; |
---|
103 | $values['user-mail'] = $user->uid ? $user->mail : ''; |
---|
104 | |
---|
105 | // Site information tokens. |
---|
106 | $values['site-url'] = url('<front>', array('absolute' => TRUE)); |
---|
107 | $values['site-name'] = check_plain(variable_get('site_name', t('Drupal'))); |
---|
108 | $values['site-slogan'] = check_plain(variable_get('site_slogan', '')); |
---|
109 | $values['site-mission'] = filter_xss_admin(variable_get('site_mission', '')); |
---|
110 | $values['site-mail'] = variable_get('site_mail', ''); |
---|
111 | $values += token_get_date_token_values(NULL, 'site-date-'); |
---|
112 | |
---|
113 | // Current page tokens. |
---|
114 | $values['current-page-title'] = drupal_get_title(); |
---|
115 | $alias = drupal_get_path_alias($_GET['q']); |
---|
116 | $values['current-page-path-raw'] = $alias; |
---|
117 | $values['current-page-path'] = check_plain($alias); |
---|
118 | $values['current-page-url'] = url($_GET['q'], array('absolute' => TRUE)); |
---|
119 | |
---|
120 | $page = isset($_GET['page']) ? $_GET['page'] : ''; |
---|
121 | $pager_page_array = explode(',', $page); |
---|
122 | $page = $pager_page_array[0]; |
---|
123 | $values['current-page-number'] = (int) $page + 1; |
---|
124 | |
---|
125 | // Backwards compatability for renamed tokens. |
---|
126 | $values['site-date'] = $values['site-date-small']; |
---|
127 | $values['page-number'] = $values['current-page-number']; |
---|
128 | |
---|
129 | break; |
---|
130 | } |
---|
131 | return $values; |
---|
132 | } |
---|
133 | |
---|
134 | /** |
---|
135 | * Implements hook_token_list(). |
---|
136 | */ |
---|
137 | function token_token_list($type = 'all') { |
---|
138 | $tokens = array(); |
---|
139 | |
---|
140 | if ($type == 'global' || $type == 'all') { |
---|
141 | // Current user tokens. |
---|
142 | $tokens['global']['user-name'] = t('The name of the currently logged in user.'); |
---|
143 | $tokens['global']['user-id'] = t('The user ID of the currently logged in user.'); |
---|
144 | $tokens['global']['user-mail'] = t('The email address of the currently logged in user.'); |
---|
145 | |
---|
146 | // Site information tokens. |
---|
147 | $tokens['global']['site-url'] = t("The URL of the site's front page."); |
---|
148 | $tokens['global']['site-name'] = t('The name of the site.'); |
---|
149 | $tokens['global']['site-slogan'] = t('The slogan of the site.'); |
---|
150 | $tokens['global']['site-mission'] = t("The optional 'mission' of the site."); |
---|
151 | $tokens['global']['site-mail'] = t('The administrative email address for the site.'); |
---|
152 | $tokens['global'] += token_get_date_token_info(t('The current'), 'site-date-'); |
---|
153 | |
---|
154 | // Current page tokens. |
---|
155 | $tokens['global']['current-page-title'] = t('The title of the current page.'); |
---|
156 | $tokens['global']['current-page-path'] = t('The URL alias of the current page.'); |
---|
157 | $tokens['global']['current-page-path-raw'] = t('The URL alias of the current page.'); |
---|
158 | $tokens['global']['current-page-url'] = t('The URL of the current page.'); |
---|
159 | $tokens['global']['current-page-number'] = t('The page number of the current page when viewing paged lists.'); |
---|
160 | } |
---|
161 | |
---|
162 | return $tokens; |
---|
163 | } |
---|
164 | |
---|
165 | /** |
---|
166 | * General function to include the files that token relies on for the real work. |
---|
167 | */ |
---|
168 | function token_include() { |
---|
169 | static $run = FALSE; |
---|
170 | |
---|
171 | if (!$run) { |
---|
172 | $run = TRUE; |
---|
173 | $modules_enabled = array_keys(module_list()); |
---|
174 | $modules = array_intersect(_token_core_supported_modules(), $modules_enabled); |
---|
175 | foreach ($modules as $module) { |
---|
176 | module_load_include('inc', 'token', "token_$module"); |
---|
177 | } |
---|
178 | } |
---|
179 | } |
---|
180 | |
---|
181 | /** |
---|
182 | * Replace all tokens in a given string with appropriate values. |
---|
183 | * |
---|
184 | * @param $text |
---|
185 | * A string potentially containing replaceable tokens. |
---|
186 | * @param $type |
---|
187 | * (optional) A flag indicating the class of substitution tokens to use. If |
---|
188 | * an object is passed in the second param, 'type' should contain the |
---|
189 | * object's type. For example, 'node', 'comment', or 'user'. If no type is |
---|
190 | * specified, only 'global' site-wide substitution tokens are built. |
---|
191 | * @param $object |
---|
192 | * (optional) An object to use for building substitution values (e.g. a node |
---|
193 | * comment, or user object). |
---|
194 | * @param $leading |
---|
195 | * (optional) Character(s) to prepend to the token key before searching for |
---|
196 | * matches. Defaults to TOKEN_PREFIX. |
---|
197 | * @param $trailing |
---|
198 | * (optional) Character(s) to append to the token key before searching for |
---|
199 | * matches. Defaults to TOKEN_SUFFIX. |
---|
200 | * @param $options |
---|
201 | * (optional) A keyed array of settings and flags to control the token |
---|
202 | * generation and replacement process. Supported options are: |
---|
203 | * - clear: A boolean flag indicating that tokens should be removed from the |
---|
204 | * final text if no replacement value can be generated. |
---|
205 | * @param $flush |
---|
206 | * (optional) A flag indicating whether or not to flush the token cache. |
---|
207 | * Useful for processes that need to slog through huge numbers of tokens |
---|
208 | * in a single execution cycle. Flushing it will keep them from burning |
---|
209 | * through memory. Defaults to FALSE. |
---|
210 | * |
---|
211 | * @return |
---|
212 | * Text with tokens replaced. |
---|
213 | */ |
---|
214 | function token_replace($text, $type = 'global', $object = NULL, $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX, $options = array(), $flush = FALSE) { |
---|
215 | return token_replace_multiple($text, array($type => $object), $leading, $trailing, $options, $flush); |
---|
216 | } |
---|
217 | |
---|
218 | /** |
---|
219 | * Replace all tokens in a given string with appropriate values. |
---|
220 | * |
---|
221 | * Contrary to token_replace() this function supports replacing multiple types. |
---|
222 | * |
---|
223 | * @param $text |
---|
224 | * A string potentially containing replaceable tokens. |
---|
225 | * @param $types |
---|
226 | * (optional) An array of substitution classes and optional objects. The key |
---|
227 | * is a flag indicating the class of substitution tokens to use. If an object |
---|
228 | * is passed as value, the key should contain the object's type. For example, |
---|
229 | * 'node', 'comment', or 'user'. The object will be used for building |
---|
230 | * substitution values. If no type is specified, only 'global' site-wide |
---|
231 | * substitution tokens are built. |
---|
232 | * @param $leading |
---|
233 | * (optional) Character(s) to prepend to the token key before searching for |
---|
234 | * matches. Defaults to TOKEN_PREFIX. |
---|
235 | * @param $trailing |
---|
236 | * (optional) Character(s) to append to the token key before searching for |
---|
237 | * matches. Defaults to TOKEN_SUFFIX. |
---|
238 | * @param $options |
---|
239 | * (optional) A keyed array of settings and flags to control the token |
---|
240 | * generation and replacement process. Supported options are: |
---|
241 | * - clear: A boolean flag indicating that tokens should be removed from the |
---|
242 | * final text if no replacement value can be generated. |
---|
243 | * @param $flush |
---|
244 | * (optional) A flag indicating whether or not to flush the token cache. |
---|
245 | * Useful for processes that need to slog through huge numbers of tokens |
---|
246 | * in a single execution cycle. Flushing it will keep them from burning |
---|
247 | * through memory. Defaults to FALSE. |
---|
248 | * |
---|
249 | * @return |
---|
250 | * Text with tokens replaced. |
---|
251 | */ |
---|
252 | function token_replace_multiple($text, $types = array('global' => NULL), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX, $options = array(), $flush = FALSE) { |
---|
253 | // Ensure that the $text parameter is a string and not an array which is an |
---|
254 | // invalid input. |
---|
255 | if (is_array($text)) { |
---|
256 | foreach ($text as $key => $value) { |
---|
257 | $text[$key] = token_replace_multiple($value, $types, $leading, $trailing, $options, $flush); |
---|
258 | } |
---|
259 | return $text; |
---|
260 | } |
---|
261 | |
---|
262 | // If there are no tokens to replace, just return the text. |
---|
263 | $text_tokens = token_scan($text, $leading, $trailing); |
---|
264 | if (empty($text_tokens)) { |
---|
265 | return $text; |
---|
266 | } |
---|
267 | |
---|
268 | $full = new stdClass(); |
---|
269 | $full->tokens = $full->values = array(); |
---|
270 | |
---|
271 | // Allow global token replacement by default. |
---|
272 | if (empty($types) || !is_array($types)) { |
---|
273 | $types = array('global' => NULL); |
---|
274 | } |
---|
275 | |
---|
276 | foreach ($types as $type => $object) { |
---|
277 | $temp = token_get_values($type, $object, $flush, $options); |
---|
278 | $full->tokens = array_merge($full->tokens, $temp->tokens); |
---|
279 | $full->values = array_merge($full->values, $temp->values); |
---|
280 | } |
---|
281 | |
---|
282 | // Support clearing out tokens that would not be replaced. |
---|
283 | if (!empty($options['clear'])) { |
---|
284 | foreach ($text_tokens as $token) { |
---|
285 | if (!in_array($token, $full->tokens)) { |
---|
286 | $full->tokens[] = $token; |
---|
287 | $full->values[] = ''; |
---|
288 | } |
---|
289 | } |
---|
290 | } |
---|
291 | |
---|
292 | $tokens = token_prepare_tokens($full->tokens, $leading, $trailing); |
---|
293 | return str_replace($tokens, $full->values, $text); |
---|
294 | } |
---|
295 | |
---|
296 | /** |
---|
297 | * Return a list of valid substitution tokens and their values for |
---|
298 | * the specified type. |
---|
299 | * |
---|
300 | * @param $type |
---|
301 | * (optional) A flag indicating the class of substitution tokens to use. If an |
---|
302 | * object is passed in the second param, 'type' should contain the |
---|
303 | * object's type. For example, 'node', 'comment', or 'user'. If no |
---|
304 | * type is specified, only 'global' site-wide substitution tokens are |
---|
305 | * built. |
---|
306 | * @param $object |
---|
307 | * (optional) An object to use for building substitution values (e.g. a node |
---|
308 | * comment, or user object). |
---|
309 | * @param $flush |
---|
310 | * (optional) A flag indicating whether or not to flush the token cache. |
---|
311 | * Useful for processes that need to slog through huge numbers of tokens |
---|
312 | * in a single execution cycle. Flushing it will keep them from burning |
---|
313 | * through memory. Defaults to FALSE. |
---|
314 | * @param $options |
---|
315 | * (optional) A keyed array of settings and flags to control the token |
---|
316 | * generation process. |
---|
317 | * |
---|
318 | * @return |
---|
319 | * An object with two properties: |
---|
320 | * - tokens: All the possible tokens names generated. |
---|
321 | * - values: The corresponding values for the tokens. |
---|
322 | * |
---|
323 | * Note that before performing actual token replacement that the token names |
---|
324 | * should be run through token_prepare_tokens(). |
---|
325 | */ |
---|
326 | function token_get_values($type = 'global', $object = NULL, $flush = FALSE, $options = array()) { |
---|
327 | static $tokens = array(); |
---|
328 | static $running = FALSE; |
---|
329 | |
---|
330 | // Simple recursion check. This is to avoid content_view()'s potential |
---|
331 | // for endless looping when a filter uses tokens, which load the content |
---|
332 | // view, which calls the filter, which uses tokens, which... |
---|
333 | if ($running) { |
---|
334 | // We'll allow things to get two levels deep, but bail out after that |
---|
335 | // without performing any substitutions. |
---|
336 | $result = new stdClass(); |
---|
337 | $result->tokens = array(); |
---|
338 | $result->values = array(); |
---|
339 | return $result; |
---|
340 | } |
---|
341 | else { |
---|
342 | $running = TRUE; |
---|
343 | } |
---|
344 | |
---|
345 | // Flush the static token cache. Useful for processes that need to slog |
---|
346 | // through huge numbers of tokens in a single execution cycle. Flushing it |
---|
347 | // will keep them from burning through memory. |
---|
348 | if ($flush || !empty($options['reset'])) { |
---|
349 | $tokens = array(); |
---|
350 | } |
---|
351 | |
---|
352 | // Allow simple resets of the static values. |
---|
353 | if ($type === 'reset') { |
---|
354 | $tokens = array(); |
---|
355 | $running = FALSE; |
---|
356 | return; |
---|
357 | } |
---|
358 | |
---|
359 | // Neutralize options that do not affect token replacement. |
---|
360 | $serialized_options = $options; |
---|
361 | unset($serialized_options['clear']); |
---|
362 | |
---|
363 | // Store the token cache by object ID and serialized options. |
---|
364 | $cid = _token_get_id($type, $object) . ':' . md5(serialize($serialized_options)); |
---|
365 | if ($type != 'global' && !isset($tokens[$type][$cid])) { |
---|
366 | token_include(); |
---|
367 | $tokens[$type][$cid] = module_invoke_all('token_values', $type, $object, $options); |
---|
368 | } |
---|
369 | |
---|
370 | // Special-case global tokens, as we always want to be able to process |
---|
371 | // those substitutions. |
---|
372 | if (!isset($tokens['global'][$cid])) { |
---|
373 | token_include(); |
---|
374 | $tokens['global'][$cid] = module_invoke_all('token_values', 'global', NULL, $options); |
---|
375 | } |
---|
376 | |
---|
377 | $all = $tokens['global'][$cid]; |
---|
378 | if ($type != 'global') { |
---|
379 | // Avoid using array_merge() if only global tokens were requested. |
---|
380 | $all = array_merge($all, $tokens[$type][$cid]); |
---|
381 | } |
---|
382 | |
---|
383 | // Allow other modules to alter the replacements. |
---|
384 | $context = array( |
---|
385 | 'type' => $type, |
---|
386 | 'object' => $object, |
---|
387 | 'options' => $options, |
---|
388 | ); |
---|
389 | drupal_alter('token_values', $all, $context); |
---|
390 | |
---|
391 | $result = new stdClass(); |
---|
392 | $result->tokens = array_keys($all); |
---|
393 | $result->values = array_values($all); |
---|
394 | |
---|
395 | $running = FALSE; |
---|
396 | |
---|
397 | return $result; |
---|
398 | } |
---|
399 | |
---|
400 | /** |
---|
401 | * A helper function that retrieves all currently exposed tokens, |
---|
402 | * and merges them recursively. This is only necessary when building |
---|
403 | * the token listing -- during actual value replacement, only tokens |
---|
404 | * in a particular domain are requested and a normal array_marge() is |
---|
405 | * sufficient. |
---|
406 | * |
---|
407 | * @param $types |
---|
408 | * A flag indicating the class of substitution tokens to use. If an |
---|
409 | * object is passed in the second param, 'types' should contain the |
---|
410 | * object's type. For example, 'node', 'comment', or 'user'. 'types' |
---|
411 | * may also be an array of types of the form array('node','user'). If no |
---|
412 | * type is specified, only 'global' site-wide substitution tokens are |
---|
413 | * built. |
---|
414 | * |
---|
415 | * @return |
---|
416 | * The array of usable tokens and their descriptions, organized by |
---|
417 | * token type. |
---|
418 | */ |
---|
419 | function token_get_list($types = 'all') { |
---|
420 | token_include(); |
---|
421 | $return = array(); |
---|
422 | settype($types, 'array'); |
---|
423 | foreach (module_implements('token_list') as $module) { |
---|
424 | foreach ($types as $type) { |
---|
425 | $module_token_list = module_invoke($module, 'token_list', $type); |
---|
426 | if (isset($module_token_list) && is_array($module_token_list)) { |
---|
427 | foreach ($module_token_list as $category => $tokens) { |
---|
428 | foreach ($tokens as $token => $title) { |
---|
429 | // Automatically append a raw token warning. |
---|
430 | if (substr($token, -4) === '-raw' && strpos($title, t('raw user input')) === FALSE && strpos($title, t('UNIX timestamp format')) === FALSE) { |
---|
431 | $title .= ' <em>' . t('Warning: Token value contains raw user input.') . '</em>'; |
---|
432 | } |
---|
433 | $return[$category][$token] = $title; |
---|
434 | } |
---|
435 | } |
---|
436 | } |
---|
437 | } |
---|
438 | } |
---|
439 | // Sort the tokens by name. |
---|
440 | foreach (array_keys($return) as $category) { |
---|
441 | ksort($return[$category]); |
---|
442 | } |
---|
443 | return $return; |
---|
444 | } |
---|
445 | |
---|
446 | /** |
---|
447 | * A helper function to prepare raw tokens for replacement. |
---|
448 | * |
---|
449 | * @param $tokens |
---|
450 | * The array of tokens names with no delimiting characters. |
---|
451 | * @param $leading |
---|
452 | * String to prepend to the token. Default is TOKEN_PREFIX. |
---|
453 | * @param $trailing |
---|
454 | * String to append to the token. Default is TOKEN_SUFFIX. |
---|
455 | * |
---|
456 | * @return |
---|
457 | * An array of the formatted tokens. |
---|
458 | */ |
---|
459 | function token_prepare_tokens($tokens = array(), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) { |
---|
460 | foreach ($tokens as $key => $value) { |
---|
461 | $tokens[$key] = $leading . $value . $trailing; |
---|
462 | } |
---|
463 | return $tokens; |
---|
464 | } |
---|
465 | |
---|
466 | /** |
---|
467 | * A helper function to return an object's ID for use in static caching. |
---|
468 | */ |
---|
469 | function _token_get_id($type = 'global', $object = NULL) { |
---|
470 | if (!isset($object)) { |
---|
471 | return "default"; |
---|
472 | } |
---|
473 | switch ($type) { |
---|
474 | case 'node': |
---|
475 | return isset($object->vid) ? $object->vid : (isset($object->nid) ? $object->nid : 0); |
---|
476 | case 'comment': |
---|
477 | return isset($object->cid) ? $object->cid : 0; |
---|
478 | case 'user': |
---|
479 | return isset($object->uid) ? $object->uid : 0; |
---|
480 | case 'taxonomy': |
---|
481 | return isset($object->tid) ? $object->tid : 0; |
---|
482 | default: |
---|
483 | return crc32(serialize($object)); |
---|
484 | } |
---|
485 | } |
---|
486 | |
---|
487 | /** |
---|
488 | * Build a list of common date tokens for use in hook_token_list(). |
---|
489 | * |
---|
490 | * @param $description |
---|
491 | */ |
---|
492 | function token_get_date_token_info($description, $token_prefix = '') { |
---|
493 | $time = time(); |
---|
494 | $tokens[$token_prefix . 'small'] = t("!description date in 'small' format. (%date)", array('!description' => $description, '%date' => format_date($time, 'small'))); |
---|
495 | $tokens[$token_prefix . 'yyyy'] = t("!description year (four digit)", array('!description' => $description)); |
---|
496 | $tokens[$token_prefix . 'yy'] = t("!description year (two digit)", array('!description' => $description)); |
---|
497 | $tokens[$token_prefix . 'month'] = t("!description month (full word)", array('!description' => $description)); |
---|
498 | $tokens[$token_prefix . 'mon'] = t("!description month (abbreviated)", array('!description' => $description)); |
---|
499 | $tokens[$token_prefix . 'mm'] = t("!description month (two digits with leading zeros)", array('!description' => $description)); |
---|
500 | $tokens[$token_prefix . 'm'] = t("!description month (one or two digits without leading zeros)", array('!description' => $description)); |
---|
501 | $tokens[$token_prefix . 'ww'] = t("!description week (two digits with leading zeros)", array('!description' => $description)); |
---|
502 | if (version_compare(PHP_VERSION, '5.1.0', '>=')) { |
---|
503 | $tokens[$token_prefix . 'date'] = t("!description date (numeric representation of the day of the week)", array('!description' => $description)); |
---|
504 | } |
---|
505 | $tokens[$token_prefix . 'day'] = t("!description day (full word)", array('!description' => $description)); |
---|
506 | $tokens[$token_prefix . 'ddd'] = t("!description day (abbreviation)", array('!description' => $description)); |
---|
507 | $tokens[$token_prefix . 'dd'] = t("!description day (two digits with leading zeros)", array('!description' => $description)); |
---|
508 | $tokens[$token_prefix . 'd'] = t("!description day (one or two digits without leading zeros)", array('!description' => $description)); |
---|
509 | $tokens[$token_prefix . 'raw'] = t("!description in UNIX timestamp format (%date)", array('!description' => $description, '%date' => $time)); |
---|
510 | $tokens[$token_prefix . 'since'] = t("!description in 'time-since' format. (%date)", array('!description' => $description, '%date' => format_interval($time - 360, 2))); |
---|
511 | return $tokens; |
---|
512 | } |
---|
513 | |
---|
514 | /** |
---|
515 | * Build a list of common date tokens for use in hook_token_values(). |
---|
516 | */ |
---|
517 | function token_get_date_token_values($timestamp = NULL, $token_prefix = '', $langcode = NULL) { |
---|
518 | static $formats; |
---|
519 | |
---|
520 | if (!isset($formats)) { |
---|
521 | $formats = array(); |
---|
522 | $formats['small'] = variable_get('date_format_short', 'm/d/Y - H:i'); |
---|
523 | $formats['yyyy'] = 'Y'; |
---|
524 | $formats['yy'] = 'y'; |
---|
525 | $formats['month'] = 'F'; |
---|
526 | $formats['mon'] = 'M'; |
---|
527 | $formats['mm'] = 'm'; |
---|
528 | $formats['m'] = 'n'; |
---|
529 | $formats['ww'] = 'W'; |
---|
530 | if (version_compare(PHP_VERSION, '5.1.0', '>=')) { |
---|
531 | $formats['date'] = 'N'; |
---|
532 | } |
---|
533 | $formats['day'] = 'l'; |
---|
534 | $formats['ddd'] = 'D'; |
---|
535 | $formats['dd'] = 'd'; |
---|
536 | $formats['d'] = 'j'; |
---|
537 | } |
---|
538 | |
---|
539 | $time = time(); |
---|
540 | if (!isset($timestamp)) { |
---|
541 | $timestamp = $time; |
---|
542 | } |
---|
543 | |
---|
544 | $tokens = array(); |
---|
545 | foreach ($formats as $token => $format) { |
---|
546 | $tokens[$token_prefix . $token] = token_format_date($timestamp, 'custom', $format, NULL, $langcode); |
---|
547 | } |
---|
548 | $tokens[$token_prefix . 'raw'] = $timestamp; |
---|
549 | $tokens[$token_prefix . 'since'] = format_interval($time - $timestamp, 2, $langcode); |
---|
550 | |
---|
551 | return $tokens; |
---|
552 | } |
---|
553 | |
---|
554 | /** |
---|
555 | * A copy of format_date() that supports the 'N' date format character. |
---|
556 | * |
---|
557 | * @see format_date() |
---|
558 | */ |
---|
559 | function token_format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { |
---|
560 | global $user; |
---|
561 | static $timezones = array(); |
---|
562 | |
---|
563 | // Statically cache each user's timezone so it doesn't need to be re-fetched |
---|
564 | // ever call. |
---|
565 | if (!isset($timezones[$user->uid])) { |
---|
566 | if (!empty($user->uid) && variable_get('configurable_timezones', 1) && strlen($user->timezone)) { |
---|
567 | $timezones[$user->uid] = $user->timezone; |
---|
568 | } |
---|
569 | else { |
---|
570 | $timezones[$user->uid] = variable_get('date_default_timezone', 0); |
---|
571 | } |
---|
572 | } |
---|
573 | |
---|
574 | $timestamp += $timezones[$user->uid]; |
---|
575 | |
---|
576 | switch ($type) { |
---|
577 | case 'custom': |
---|
578 | // No change to format. |
---|
579 | break; |
---|
580 | case 'small': |
---|
581 | $format = variable_get('date_format_short', 'm/d/Y - H:i'); |
---|
582 | break; |
---|
583 | case 'large': |
---|
584 | $format = variable_get('date_format_long', 'l, F j, Y - H:i'); |
---|
585 | break; |
---|
586 | case 'medium': |
---|
587 | default: |
---|
588 | $format = variable_get('date_format_medium', 'D, m/d/Y - H:i'); |
---|
589 | } |
---|
590 | |
---|
591 | $max = strlen($format); |
---|
592 | $date = ''; |
---|
593 | for ($i = 0; $i < $max; $i++) { |
---|
594 | $c = $format[$i]; |
---|
595 | if (strpos('AaDlM', $c) !== FALSE) { |
---|
596 | $date .= t(gmdate($c, $timestamp), array(), $langcode); |
---|
597 | } |
---|
598 | elseif ($c == 'F') { |
---|
599 | // Special treatment for long month names: May is both an abbreviation |
---|
600 | // and a full month name in English, but other languages have |
---|
601 | // different abbreviations. |
---|
602 | $date .= trim(t('!long-month-name ' . gmdate($c, $timestamp), array('!long-month-name' => ''), $langcode)); |
---|
603 | } |
---|
604 | elseif (strpos('BdgGhHiIjLmnNsStTUwWYyz', $c) !== FALSE) { |
---|
605 | // This condition was modified to allow the 'N' date format character. |
---|
606 | $date .= gmdate($c, $timestamp); |
---|
607 | } |
---|
608 | elseif ($c == 'r') { |
---|
609 | $date .= token_format_date($timestamp - $timezone, 'custom', 'D, d M Y H:i:s O', $timezone, $langcode); |
---|
610 | } |
---|
611 | elseif ($c == 'O') { |
---|
612 | $date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'), abs($timezone / 3600), abs($timezone % 3600) / 60); |
---|
613 | } |
---|
614 | elseif ($c == 'Z') { |
---|
615 | $date .= $timezone; |
---|
616 | } |
---|
617 | elseif ($c == '\\') { |
---|
618 | $date .= $format[++$i]; |
---|
619 | } |
---|
620 | else { |
---|
621 | $date .= $c; |
---|
622 | } |
---|
623 | } |
---|
624 | |
---|
625 | return $date; |
---|
626 | } |
---|
627 | |
---|
628 | /** |
---|
629 | * Validate an tokens in raw text based on possible contexts. |
---|
630 | * |
---|
631 | * @param $value |
---|
632 | * A string with the raw text containing the raw tokens, or an array of |
---|
633 | * tokens from token_scan(). |
---|
634 | * @param $valid_types |
---|
635 | * An array of token types to validage against. |
---|
636 | * @param $leading |
---|
637 | * Character(s) to prepend to the token key before searching for |
---|
638 | * matches. Defaults to TOKEN_PREFIX. |
---|
639 | * @param $trailing |
---|
640 | * Character(s) to append to the token key before searching for |
---|
641 | * matches. Defaults to TOKEN_SUFFIX. |
---|
642 | * |
---|
643 | * @return |
---|
644 | * An array with the invalid tokens in their original raw forms. |
---|
645 | */ |
---|
646 | function token_get_invalid_tokens_by_context($value, $valid_types = array(), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) { |
---|
647 | if (in_array('all', $valid_types)) { |
---|
648 | $valid_types = array('all'); |
---|
649 | } |
---|
650 | else { |
---|
651 | // Add the token types that are always valid in global context. |
---|
652 | $valid_types[] = 'global'; |
---|
653 | } |
---|
654 | |
---|
655 | $invalid_tokens = array(); |
---|
656 | $valid_tokens = array(); |
---|
657 | $value_tokens = is_string($value) ? token_scan($value, $leading, $trailing) : $value; |
---|
658 | |
---|
659 | foreach (token_get_list($valid_types) as $category => $tokens) { |
---|
660 | $valid_tokens += $tokens; |
---|
661 | } |
---|
662 | |
---|
663 | foreach ($value_tokens as $token) { |
---|
664 | if (isset($valid_tokens[$token])) { |
---|
665 | continue; |
---|
666 | } |
---|
667 | elseif (preg_match('/^(.*[_-])([^-_])+$/', $token, $matches)) { |
---|
668 | // Allow tokens that do not have a direct match to tokens listed in |
---|
669 | // hook_token_info() to be matched against a 'wildcard' token name. |
---|
670 | if (isset($valid_tokens[$matches[1] . '?'])) { |
---|
671 | // [token-name-?] wildcards. |
---|
672 | continue; |
---|
673 | } |
---|
674 | elseif (isset($valid_tokens[$matches[1] . '????'])) { |
---|
675 | // [token-name-????] wildcards. |
---|
676 | continue; |
---|
677 | } |
---|
678 | elseif (is_numeric($matches[2]) && isset($valid_tokens[$matches[1] . 'N'])) { |
---|
679 | // [token-name-N] wildcards if N is a numeric value. |
---|
680 | continue; |
---|
681 | } |
---|
682 | } |
---|
683 | $invalid_tokens[] = $token; |
---|
684 | } |
---|
685 | |
---|
686 | array_unique($invalid_tokens); |
---|
687 | $invalid_tokens = token_prepare_tokens($invalid_tokens, $leading, $trailing); |
---|
688 | return $invalid_tokens; |
---|
689 | } |
---|
690 | |
---|
691 | /** |
---|
692 | * Build a list of all token-like patterns that appear in the text. |
---|
693 | * |
---|
694 | * @param $text |
---|
695 | * The text to be scanned for possible tokens. |
---|
696 | * @param $leading |
---|
697 | * Character(s) to prepend to the token key before searching for |
---|
698 | * matches. Defaults to TOKEN_PREFIX. |
---|
699 | * @param $trailing |
---|
700 | * Character(s) to append to the token key before searching for |
---|
701 | * matches. Defaults to TOKEN_SUFFIX. |
---|
702 | * |
---|
703 | * @return |
---|
704 | * An array of discovered tokens. |
---|
705 | */ |
---|
706 | function token_scan($text, $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) { |
---|
707 | $leadingregex = preg_quote($leading, '/'); |
---|
708 | $trailingregex = preg_quote($trailing, '/'); |
---|
709 | |
---|
710 | $regex = '/' . $leadingregex; |
---|
711 | $regex .= '([^\s'; |
---|
712 | if (drupal_strlen($leading) == 1) { |
---|
713 | // Only add the leading string as a non-match if it is a single character. |
---|
714 | $regex .= $leadingregex; |
---|
715 | } |
---|
716 | if (drupal_strlen($trailing) == 1) { |
---|
717 | // Only add the trailing string as a non-match if it is a single character. |
---|
718 | $regex .= $trailingregex; |
---|
719 | } |
---|
720 | $regex .= ']+)' . $trailingregex . '/x'; |
---|
721 | |
---|
722 | preg_match_all($regex, $text, $matches); |
---|
723 | return $matches[1]; |
---|
724 | } |
---|
725 | |
---|
726 | /** |
---|
727 | * Validate a form element that should have tokens in it. |
---|
728 | * |
---|
729 | * Form elements that want to add this validation should have the #token_types |
---|
730 | * parameter defined. |
---|
731 | * |
---|
732 | * For example: |
---|
733 | * @code |
---|
734 | * $form['my_node_text_element'] = array( |
---|
735 | * '#type' => 'textfield', |
---|
736 | * '#title' => t('Some text to token-ize that has a node context.'), |
---|
737 | * '#default_value' => 'The title of this node is [title].', |
---|
738 | * '#element_validate' => array('token_element_validate'), |
---|
739 | * '#token_types' => array('node'), |
---|
740 | * '#min_tokens' => 1, |
---|
741 | * '#max_tokens' => 10, |
---|
742 | * ); |
---|
743 | * @endcode |
---|
744 | */ |
---|
745 | function token_element_validate(&$element, &$form_state) { |
---|
746 | $value = isset($element['#value']) ? $element['#value'] : $element['#default_value']; |
---|
747 | |
---|
748 | if (!drupal_strlen($value)) { |
---|
749 | // Empty value needs no further validation since the element should depend |
---|
750 | // on using the '#required' FAPI property. |
---|
751 | return $element; |
---|
752 | } |
---|
753 | |
---|
754 | $tokens = token_scan($value); |
---|
755 | $title = empty($element['#title']) ? $element['#parents'][0] : $element['#title']; |
---|
756 | |
---|
757 | // Validate if an element must have a minimum number of tokens. |
---|
758 | if (isset($element['#min_tokens']) && count($tokens) < $element['#min_tokens']) { |
---|
759 | // @todo Change this error message to include the minimum number. |
---|
760 | $error = format_plural($element['#min_tokens'], 'The %element-title cannot contain fewer than one token.', 'The %element-title must contain at least @count tokens.', array('%element-title' => $title)); |
---|
761 | form_error($element, $error); |
---|
762 | } |
---|
763 | |
---|
764 | // Validate if an element must have a maximum number of tokens. |
---|
765 | if (isset($element['#max_tokens']) && count($tokens) > $element['#max_tokens']) { |
---|
766 | // @todo Change this error message to include the maximum number. |
---|
767 | $error = format_plural($element['#max_tokens'], 'The %element-title must contain as most one token.', 'The %element-title must contain at most @count tokens.', array('%element-title' => $title)); |
---|
768 | form_error($element, $error); |
---|
769 | } |
---|
770 | |
---|
771 | // Check if the field defines specific token types. |
---|
772 | if (!empty($element['#token_types'])) { |
---|
773 | $invalid_tokens = token_get_invalid_tokens_by_context($tokens, $element['#token_types']); |
---|
774 | if ($invalid_tokens) { |
---|
775 | form_error($element, t('The %element-title is using the following invalid tokens: @invalid-tokens.', array('%element-title' => $title, '@invalid-tokens' => implode(', ', $invalid_tokens)))); |
---|
776 | } |
---|
777 | } |
---|
778 | |
---|
779 | return $element; |
---|
780 | } |
---|
781 | |
---|
782 | /** |
---|
783 | * Deprecated. Use token_element_validate() instead. |
---|
784 | */ |
---|
785 | function token_element_validate_token_context(&$element, &$form_state) { |
---|
786 | return token_element_validate($element, $form_state); |
---|
787 | } |
---|
788 | |
---|
789 | /** |
---|
790 | * Find tokens that have been declared twice by different modules. |
---|
791 | */ |
---|
792 | function token_find_duplicate_tokens() { |
---|
793 | token_include(); |
---|
794 | $all_tokens = array(); |
---|
795 | |
---|
796 | foreach (module_implements('token_list') as $module) { |
---|
797 | $module_token_list = module_invoke($module, 'token_list', 'all'); |
---|
798 | if (!isset($module_token_list) || !is_array($module_token_list)) { |
---|
799 | // Skip modules that do not return an array as that is a valid return |
---|
800 | // value. |
---|
801 | continue; |
---|
802 | } |
---|
803 | if (in_array($module, _token_core_supported_modules())) { |
---|
804 | $module = 'token'; |
---|
805 | } |
---|
806 | foreach ($module_token_list as $type => $tokens) { |
---|
807 | foreach (array_keys($tokens) as $token) { |
---|
808 | $all_tokens[$type . ':' . $token][] = $module; |
---|
809 | } |
---|
810 | } |
---|
811 | } |
---|
812 | |
---|
813 | foreach ($all_tokens as $token => $modules) { |
---|
814 | if (count($modules) < 2) { |
---|
815 | unset($all_tokens[$token]); |
---|
816 | } |
---|
817 | } |
---|
818 | |
---|
819 | return $all_tokens; |
---|
820 | } |
---|
821 | |
---|
822 | /** |
---|
823 | * Get a translated menu link by its mlid, without access checking. |
---|
824 | * |
---|
825 | * This function is a copy of menu_link_load() but with its own cache and a |
---|
826 | * simpler query to load the link. This also skips normal menu link access |
---|
827 | * checking by using _token_menu_link_translate(). |
---|
828 | * |
---|
829 | * @param $mlid |
---|
830 | * The mlid of the menu item. |
---|
831 | * |
---|
832 | * @return |
---|
833 | * A menu link translated for rendering. |
---|
834 | * |
---|
835 | * @see menu_link_load() |
---|
836 | * @see _token_menu_link_translate() |
---|
837 | */ |
---|
838 | function token_menu_link_load($mlid) { |
---|
839 | static $cache = array(); |
---|
840 | |
---|
841 | if (!is_numeric($mlid)) { |
---|
842 | return FALSE; |
---|
843 | } |
---|
844 | |
---|
845 | if (!isset($cache[$mlid])) { |
---|
846 | $item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid)); |
---|
847 | if (!empty($item)) { |
---|
848 | _token_menu_link_translate($item); |
---|
849 | } |
---|
850 | $cache[$mlid] = $item; |
---|
851 | } |
---|
852 | |
---|
853 | return $cache[$mlid]; |
---|
854 | } |
---|
855 | |
---|
856 | /** |
---|
857 | * Get a translated book menu link by its mlid, without access checking. |
---|
858 | * |
---|
859 | * This function is a copy of book_link_load() but with its own cache and a |
---|
860 | * simpler query to load the link. This also skips normal menu link access |
---|
861 | * checking by using _token_menu_link_translate(). |
---|
862 | * |
---|
863 | * @param $mlid |
---|
864 | * The mlid of the book menu item. |
---|
865 | * |
---|
866 | * @return |
---|
867 | * A book menu link translated for rendering. |
---|
868 | * |
---|
869 | * @see book_link_load() |
---|
870 | * @see _token_menu_link_translate() |
---|
871 | */ |
---|
872 | function token_book_link_load($mlid) { |
---|
873 | static $cache = array(); |
---|
874 | |
---|
875 | if (!is_numeric($mlid)) { |
---|
876 | return FALSE; |
---|
877 | } |
---|
878 | |
---|
879 | if (!isset($cache[$mlid])) { |
---|
880 | $item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid)); |
---|
881 | if (!empty($item)) { |
---|
882 | _token_menu_link_translate($item); |
---|
883 | } |
---|
884 | $cache[$mlid] = $item; |
---|
885 | } |
---|
886 | |
---|
887 | return $cache[$mlid]; |
---|
888 | } |
---|
889 | |
---|
890 | function _token_menu_link_translate(&$item) { |
---|
891 | $map = array(); |
---|
892 | |
---|
893 | if (!is_array($item['options'])) { |
---|
894 | $item['options'] = unserialize($item['options']); |
---|
895 | } |
---|
896 | |
---|
897 | if ($item['external']) { |
---|
898 | $item['access'] = 1; |
---|
899 | $item['href'] = $item['link_path']; |
---|
900 | $item['title'] = $item['link_title']; |
---|
901 | $item['localized_options'] = $item['options']; |
---|
902 | } |
---|
903 | else { |
---|
904 | $map = explode('/', $item['link_path']); |
---|
905 | _menu_link_map_translate($map, $item['to_arg_functions']); |
---|
906 | $item['href'] = implode('/', $map); |
---|
907 | |
---|
908 | // Note - skip callbacks without real values for their arguments. |
---|
909 | if (strpos($item['href'], '%') !== FALSE) { |
---|
910 | $item['access'] = FALSE; |
---|
911 | return FALSE; |
---|
912 | } |
---|
913 | |
---|
914 | $item['access'] = TRUE; |
---|
915 | _menu_item_localize($item, $map, TRUE); |
---|
916 | } |
---|
917 | |
---|
918 | // Allow other customizations - e.g. adding a page-specific query string to the |
---|
919 | // options array. For performance reasons we only invoke this hook if the link |
---|
920 | // has the 'alter' flag set in the options array. |
---|
921 | if (!empty($item['options']['alter'])) { |
---|
922 | drupal_alter('translated_menu_link', $item, $map); |
---|
923 | } |
---|
924 | |
---|
925 | return $map; |
---|
926 | } |
---|
927 | |
---|
928 | /** |
---|
929 | * Find all ancestors of a given menu link ID. |
---|
930 | * |
---|
931 | * @param $mlid |
---|
932 | * A menu link ID. |
---|
933 | * |
---|
934 | * @return |
---|
935 | * An array of menu links from token_menu_link_load() with the root link |
---|
936 | * first, and the menu link with ID $mlid last. |
---|
937 | */ |
---|
938 | function token_menu_link_get_parents_all($mlid) { |
---|
939 | $parents = array(); |
---|
940 | |
---|
941 | while (!empty($mlid)) { |
---|
942 | $link = token_menu_link_load($mlid); |
---|
943 | array_unshift($parents, $link); |
---|
944 | $mlid = $link['plid']; |
---|
945 | } |
---|
946 | |
---|
947 | return $parents; |
---|
948 | } |
---|
949 | |
---|
950 | /** |
---|
951 | * Deprecated. Use the raw return value of token_menu_link_get_parents_all() instead. |
---|
952 | */ |
---|
953 | function _menu_titles($menu_link, $nid) { |
---|
954 | $titles = array(); |
---|
955 | $parents = token_menu_link_get_parents_all($menu_link['mlid']); |
---|
956 | foreach ($parents as $mlid => $parent) { |
---|
957 | $titles[] = $parent['title']; |
---|
958 | } |
---|
959 | return $titles; |
---|
960 | } |
---|