Project

General

Profile

Paste
Download (50.7 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / token / token.tokens.inc @ 4cfd8be6

1
<?php
2

    
3
/**
4
 * @file
5
 * Token callbacks for the token module.
6
 */
7

    
8
/**
9
 * Implements hook_token_info_alter().
10
 */
11
function token_token_info_alter(&$info) {
12
  // Force 'date' type tokens to require input and add a 'current-date' type.
13
  // @todo Remove when http://drupal.org/node/943028 is fixed.
14
  $info['types']['date']['needs-data'] = 'date';
15
  $info['types']['current-date'] = array(
16
    'name' => t('Current date'),
17
    'description' => t('Tokens related to the current date and time.'),
18
    'type' => 'date',
19
  );
20

    
21
  // Add a 'dynamic' key to any tokens that have chained but dynamic tokens.
22
  $info['tokens']['date']['custom']['dynamic'] = TRUE;
23

    
24
  // The [file:size] may not always return in kilobytes.
25
  // @todo Remove when http://drupal.org/node/1193044 is fixed.
26
  $info['tokens']['file']['size']['description'] = t('The size of the file.');
27

    
28
  // Remove deprecated tokens from being listed.
29
  unset($info['tokens']['node']['tnid']);
30
  unset($info['tokens']['node']['type']);
31
  unset($info['tokens']['node']['type-name']);
32

    
33
  // Support 'url' type tokens for core tokens.
34
  if (isset($info['tokens']['comment']['url']) && module_exists('comment')) {
35
    $info['tokens']['comment']['url']['type'] = 'url';
36
  }
37
  $info['tokens']['node']['url']['type'] = 'url';
38
  if (isset($info['tokens']['term']['url']) && module_exists('taxonomy')) {
39
    $info['tokens']['term']['url']['type'] = 'url';
40
  }
41
  $info['tokens']['user']['url']['type'] = 'url';
42

    
43
  // Add [token:url] tokens for any URI-able entities.
44
  $entities = entity_get_info();
45
  foreach ($entities as $entity => $entity_info) {
46
    if (!isset($entity_info['token type'])) {
47
      continue;
48
    }
49

    
50
    $token_type = $entity_info['token type'];
51
    if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
52
      continue;
53
    }
54

    
55
    // Add [entity:url] tokens if they do not already exist.
56
    // @todo Support entity:label
57
    if (!isset($info['tokens'][$token_type]['url']) && !empty($entity_info['uri callback'])) {
58
      $info['tokens'][$token_type]['url'] = array(
59
        'name' => t('URL'),
60
        'description' => t('The URL of the @entity.', array('@entity' => drupal_strtolower($entity_info['label']))),
61
        'module' => 'token',
62
        'type' => 'url',
63
      );
64
    }
65

    
66
    // Add [entity:original] tokens if they do not already exist.
67
    if (!isset($info['tokens'][$token_type]['original'])) {
68
      $info['tokens'][$token_type]['original'] = array(
69
        'name' => t('Original @entity', array('@entity' => drupal_strtolower($entity_info['label']))),
70
        'description' => t('The original @entity data if the @entity is being updated or saved.', array('@entity' => drupal_strtolower($entity_info['label']))),
71
        'module' => 'token',
72
        'type' => $token_type,
73
      );
74
    }
75
  }
76

    
77
  // Add support for custom date formats.
78
  // @todo Remove when http://drupal.org/node/1173706 is fixed.
79
  $date_format_types = system_get_date_types();
80
  foreach ($date_format_types as $date_format_type => $date_format_type_info) {
81
    if (!isset($info['tokens']['date'][$date_format_type])) {
82
      $info['tokens']['date'][$date_format_type] = array(
83
        'name' => check_plain($date_format_type_info['title']),
84
        'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => format_date(REQUEST_TIME, $date_format_type))),
85
        'module' => 'token',
86
      );
87
    }
88
  }
89
}
90

    
91
/**
92
 * Implements hook_token_info().
93
 */
94
function token_token_info() {
95
  // Node tokens.
96
  $info['tokens']['node']['source'] = array(
97
    'name' => t('Translation source node'),
98
    'description' => t("The source node for this current node's translation set."),
99
    'type' => 'node',
100
  );
101
  $info['tokens']['node']['log'] = array(
102
    'name' => t('Revision log message'),
103
    'description' => t('The explanation of the most recent changes made to the node.'),
104
  );
105
  $info['tokens']['node']['content-type'] = array(
106
    'name' => t('Content type'),
107
    'description' => t('The content type of the node.'),
108
    'type' => 'content-type',
109
  );
110

    
111
  // Content type tokens.
112
  $info['types']['content-type'] = array(
113
    'name' => t('Content types'),
114
    'description' => t('Tokens related to content types.'),
115
    'needs-data' => 'node_type',
116
  );
117
  $info['tokens']['content-type']['name'] = array(
118
    'name' => t('Name'),
119
    'description' => t('The name of the content type.'),
120
  );
121
  $info['tokens']['content-type']['machine-name'] = array(
122
    'name' => t('Machine-readable name'),
123
    'description' => t('The unique machine-readable name of the content type.'),
124
  );
125
  $info['tokens']['content-type']['description'] = array(
126
    'name' => t('Description'),
127
    'description' => t('The optional description of the content type.'),
128
  );
129
  $info['tokens']['content-type']['node-count'] = array(
130
    'name' => t('Node count'),
131
    'description' => t('The number of nodes belonging to the content type.'),
132
  );
133
  $info['tokens']['content-type']['edit-url'] = array(
134
    'name' => t('Edit URL'),
135
    'description' => t("The URL of the content type's edit page."),
136
    // 'type' => 'url',
137
  );
138

    
139
  // Taxonomy term and vocabulary tokens.
140
  if (module_exists('taxonomy')) {
141
    $info['tokens']['term']['edit-url'] = array(
142
      'name' => t('Edit URL'),
143
      'description' => t("The URL of the taxonomy term's edit page."),
144
      // 'type' => 'url',
145
    );
146
    $info['tokens']['term']['parents'] = array(
147
      'name' => t('Parents'),
148
      'description' => t("An array of all the term's parents, starting with the root."),
149
      'type' => 'array',
150
    );
151
    $info['tokens']['term']['root'] = array(
152
      'name' => t('Root term'),
153
      'description' => t("The root term of the taxonomy term."),
154
      'type' => 'term',
155
    );
156

    
157
    $info['tokens']['vocabulary']['machine-name'] = array(
158
      'name' => t('Machine-readable name'),
159
      'description' => t('The unique machine-readable name of the vocabulary.'),
160
    );
161
    $info['tokens']['vocabulary']['edit-url'] = array(
162
      'name' => t('Edit URL'),
163
      'description' => t("The URL of the vocabulary's edit page."),
164
      // 'type' => 'url',
165
    );
166
  }
167

    
168
  // File tokens.
169
  $info['tokens']['file']['basename'] = array(
170
    'name' => t('Base name'),
171
    'description' => t('The base name of the file.'),
172
  );
173
  $info['tokens']['file']['extension'] = array(
174
    'name' => t('Extension'),
175
    'description' => t('The extension of the file.'),
176
  );
177
  $info['tokens']['file']['size-raw'] = array(
178
    'name' => t('File byte size'),
179
    'description' => t('The size of the file, in bytes.'),
180
  );
181

    
182
  // User tokens.
183
  // Add information on the restricted user tokens.
184
  $info['tokens']['user']['cancel-url'] = array(
185
    'name' => t('Account cancellation URL'),
186
    'description' => t('The URL of the confirm delete page for the user account.'),
187
    'restricted' => TRUE,
188
    // 'type' => 'url',
189
  );
190
  $info['tokens']['user']['one-time-login-url'] = array(
191
    'name' => t('One-time login URL'),
192
    'description' => t('The URL of the one-time login page for the user account.'),
193
    'restricted' => TRUE,
194
    // 'type' => 'url',
195
  );
196
  if (variable_get('user_pictures', 0)) {
197
    $info['tokens']['user']['picture'] = array(
198
      'name' => t('Picture'),
199
      'description' => t('The picture of the user.'),
200
      'type' => 'file',
201
    );
202
  }
203
  $info['tokens']['user']['roles'] = array(
204
    'name' => t('Roles'),
205
    'description' => t('The user roles associated with the user account.'),
206
    'type' => 'array',
207
  );
208

    
209
  // Current user tokens.
210
  $info['tokens']['current-user']['ip-address'] = array(
211
    'name' => t('IP address'),
212
    'description' => 'The IP address of the current user.',
213
  );
214

    
215
  // Menu link tokens (work regardless if menu module is enabled or not).
216
  $info['types']['menu-link'] = array(
217
    'name' => t('Menu links'),
218
    'description' => t('Tokens related to menu links.'),
219
    'needs-data' => 'menu-link',
220
  );
221
  $info['tokens']['menu-link']['mlid'] = array(
222
    'name' => t('Link ID'),
223
    'description' => t('The unique ID of the menu link.'),
224
  );
225
  $info['tokens']['menu-link']['title'] = array(
226
    'name' => t('Title'),
227
    'description' => t('The title of the menu link.'),
228
  );
229
  $info['tokens']['menu-link']['url'] = array(
230
    'name' => t('URL'),
231
    'description' => t('The URL of the menu link.'),
232
    'type' => 'url',
233
  );
234
  $info['tokens']['menu-link']['parent'] = array(
235
    'name' => t('Parent'),
236
    'description' => t("The menu link's parent."),
237
    'type' => 'menu-link',
238
  );
239
  $info['tokens']['menu-link']['parents'] = array(
240
    'name' => t('Parents'),
241
    'description' => t("An array of all the menu link's parents, starting with the root."),
242
    'type' => 'array',
243
  );
244
  $info['tokens']['menu-link']['root'] = array(
245
    'name' => t('Root'),
246
    'description' => t("The menu link's root."),
247
    'type' => 'menu-link',
248
  );
249

    
250
  // Current page tokens.
251
  $info['types']['current-page'] = array(
252
    'name' => t('Current page'),
253
    'description' => t('Tokens related to the current page request.'),
254
  );
255
  $info['tokens']['current-page']['title'] = array(
256
    'name' => t('Title'),
257
    'description' => t('The title of the current page.'),
258
  );
259
  $info['tokens']['current-page']['url'] = array(
260
    'name' => t('URL'),
261
    'description' => t('The URL of the current page.'),
262
    'type' => 'url',
263
  );
264
  $info['tokens']['current-page']['page-number'] = array(
265
    'name' => t('Page number'),
266
    'description' => t('The page number of the current page when viewing paged lists.'),
267
  );
268
  $info['tokens']['current-page']['query'] = array(
269
    'name' => t('Query string value'),
270
    'description' => t('The value of a specific query string field of the current page.'),
271
    'dynamic' => TRUE,
272
  );
273

    
274
  // URL tokens.
275
  $info['types']['url'] = array(
276
    'name' => t('URL'),
277
    'description' => t('Tokens related to URLs.'),
278
    'needs-data' => 'path',
279
  );
280
  $info['tokens']['url']['path'] = array(
281
    'name' => t('Path'),
282
    'description' => t('The path component of the URL.'),
283
  );
284
  $info['tokens']['url']['relative'] = array(
285
    'name' => t('Relative URL'),
286
    'description' => t('The relative URL.'),
287
  );
288
  $info['tokens']['url']['absolute'] = array(
289
    'name' => t('Absolute URL'),
290
    'description' => t('The absolute URL.'),
291
  );
292
  $info['tokens']['url']['brief'] = array(
293
    'name' => t('Brief URL'),
294
    'description' => t('The URL without the protocol and trailing backslash.'),
295
  );
296
  $info['tokens']['url']['unaliased'] = array(
297
    'name' => t('Unaliased URL'),
298
    'description' => t('The unaliased URL.'),
299
    'type' => 'url',
300
  );
301
  $info['tokens']['url']['args'] = array(
302
    'name' => t('Arguments'),
303
    'description' => t("The specific argument of the current page (e.g. 'arg:1' on the page 'node/1' returns '1')."),
304
    'type' => 'array',
305
  );
306

    
307
  // Array tokens.
308
  $info['types']['array'] = array(
309
    'name' => t('Array'),
310
    'description' => t('Tokens related to arrays of strings.'),
311
    'needs-data' => 'array',
312
  );
313
  $info['tokens']['array']['first'] = array(
314
    'name' => t('First'),
315
    'description' => t('The first element of the array.'),
316
  );
317
  $info['tokens']['array']['last'] = array(
318
    'name' => t('Last'),
319
    'description' => t('The last element of the array.'),
320
  );
321
  $info['tokens']['array']['count'] = array(
322
    'name' => t('Count'),
323
    'description' => t('The number of elements in the array.'),
324
  );
325
  $info['tokens']['array']['reversed'] = array(
326
    'name' => t('Reversed'),
327
    'description' => t('The array reversed.'),
328
    'type' => 'array',
329
  );
330
  $info['tokens']['array']['keys'] = array(
331
    'name' => t('Keys'),
332
    'description' => t('The array of keys of the array.'),
333
    'type' => 'array',
334
  );
335
  $info['tokens']['array']['join'] = array(
336
    'name' => t('Imploded'),
337
    'description' => t('The values of the array joined together with a custom string in-between each value.'),
338
    'dynamic' => TRUE,
339
  );
340
  $info['tokens']['array']['value'] = array(
341
    'name' => t('Value'),
342
    'description' => t('The specific value of the array.'),
343
    'dynamic' => TRUE,
344
  );
345

    
346
  // Random tokens.
347
  $info['types']['random'] = array(
348
    'name' => t('Random'),
349
    'description' => ('Tokens related to random data.'),
350
  );
351
  $info['tokens']['random']['number'] = array(
352
    'name' => t('Number'),
353
    'description' => t('A random number from 0 to @max.', array('@max' => mt_getrandmax())),
354
  );
355
  $info['tokens']['random']['hash'] = array(
356
    'name' => t('Hash'),
357
    'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', array('@hash-algos' => implode(', ', hash_algos()))),
358
    'dynamic' => TRUE,
359
  );
360

    
361
  return $info;
362
}
363

    
364
/**
365
 * Implements hook_tokens().
366
 */
367
function token_tokens($type, $tokens, array $data = array(), array $options = array()) {
368
  $replacements = array();
369

    
370
  $url_options = array('absolute' => TRUE);
371
  if (isset($options['language'])) {
372
    $url_options['language'] = $options['language'];
373
    $language_code = $options['language']->language;
374
  }
375
  else {
376
    $language_code = NULL;
377
  }
378

    
379
  $sanitize = !empty($options['sanitize']);
380

    
381
  // Date tokens.
382
  if ($type == 'date') {
383
    $date = !empty($data['date']) ? $data['date'] : REQUEST_TIME;
384

    
385
    // @todo Remove when http://drupal.org/node/1173706 is fixed.
386
    $date_format_types = system_get_date_types();
387
    foreach ($tokens as $name => $original) {
388
      if (isset($date_format_types[$name]) && _token_module('date', $name) == 'token') {
389
        $replacements[$original] = format_date($date, $name, '', NULL, $language_code);
390
      }
391
    }
392
  }
393

    
394
  // Current date tokens.
395
  // @todo Remove when http://drupal.org/node/943028 is fixed.
396
  if ($type == 'current-date') {
397
    $replacements += token_generate('date', $tokens, array('date' => REQUEST_TIME), $options);
398
  }
399

    
400
  // Comment tokens.
401
  if ($type == 'comment' && !empty($data['comment'])) {
402
    $comment = $data['comment'];
403

    
404
    // Chained token relationships.
405
    if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
406
      $replacements += token_generate('url', $url_tokens, entity_uri('comment', $comment), $options);
407
    }
408
  }
409

    
410
  // Node tokens.
411
  if ($type == 'node' && !empty($data['node'])) {
412
    $node = $data['node'];
413

    
414
    foreach ($tokens as $name => $original) {
415
      switch ($name) {
416
        case 'source':
417
          if (!empty($node->tnid) && $source_node = node_load($node->tnid)) {
418
            $title = $source_node->title;
419
            $replacements[$original] = $sanitize ? filter_xss($title) : $title;
420
          }
421
          break;
422
        case 'log':
423
          $replacements[$original] = $sanitize ? filter_xss($node->log) : $node->log;
424
          break;
425
        case 'content-type':
426
          $type_name = node_type_get_name($node);
427
          $replacements[$original] = $sanitize ? check_plain($type_name) : $type_name;
428
          break;
429
      }
430
    }
431

    
432
    // Chained token relationships.
433
    if (!empty($node->tnid) && ($source_tokens = token_find_with_prefix($tokens, 'source')) && $source_node = node_load($node->tnid)) {
434
      $replacements += token_generate('node', $source_tokens, array('node' => $source_node), $options);
435
    }
436
    if (($node_type_tokens = token_find_with_prefix($tokens, 'content-type')) && $node_type = node_type_load($node->type)) {
437
      $replacements += token_generate('content-type', $node_type_tokens, array('node_type' => $node_type), $options);
438
    }
439
    if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
440
      $replacements += token_generate('url', $url_tokens, entity_uri('node', $node), $options);
441
    }
442
  }
443

    
444
  // Content type tokens.
445
  if ($type == 'content-type' && !empty($data['node_type'])) {
446
    $node_type = $data['node_type'];
447

    
448
    foreach ($tokens as $name => $original) {
449
      switch ($name) {
450
        case 'name':
451
          $replacements[$original] = $sanitize ? check_plain($node_type->name) : $node_type->name;
452
          break;
453
        case 'machine-name':
454
          // This is a machine name so does not ever need to be sanitized.
455
          $replacements[$original] = $node_type->type;
456
          break;
457
        case 'description':
458
          $replacements[$original] = $sanitize ? filter_xss($node_type->description) : $node_type->description;
459
          break;
460
        case 'node-count':
461
          $query = db_select('node');
462
          $query->condition('type', $node_type->type);
463
          $query->addTag('node_type_node_count');
464
          $count = $query->countQuery()->execute()->fetchField();
465
          $replacements[$original] = (int) $count;
466
          break;
467
        case 'edit-url':
468
          $replacements[$original] = url("admin/structure/types/manage/{$node_type->type}", $url_options);
469
          break;
470
      }
471
    }
472
  }
473

    
474
  // Taxonomy term tokens.
475
  if ($type == 'term' && !empty($data['term'])) {
476
    $term = $data['term'];
477

    
478
    foreach ($tokens as $name => $original) {
479
      switch ($name) {
480
        case 'edit-url':
481
          $replacements[$original] = url("taxonomy/term/{$term->tid}/edit", $url_options);
482
          break;
483

    
484
        case 'parents':
485
          if ($parents = token_taxonomy_term_load_all_parents($term->tid)) {
486
            $replacements[$original] = token_render_array($parents, $options);
487
          }
488
          break;
489

    
490
        case 'root':
491
          $parents = taxonomy_get_parents_all($term->tid);
492
          $root_term = end($parents);
493
          if ($root_term->tid != $term->tid) {
494
            $replacements[$original] = $sanitize ? check_plain($root_term->name) : $root_term->name;
495
          }
496
          break;
497
      }
498
    }
499

    
500
    // Chained token relationships.
501
    if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
502
      $replacements += token_generate('url', $url_tokens, entity_uri('taxonomy_term', $term), $options);
503
    }
504
    // [term:parents:*] chained tokens.
505
    if ($parents_tokens = token_find_with_prefix($tokens, 'parents')) {
506
      if ($parents = token_taxonomy_term_load_all_parents($term->tid)) {
507
        $replacements += token_generate('array', $parents_tokens, array('array' => $parents), $options);
508
      }
509
    }
510
    if ($root_tokens = token_find_with_prefix($tokens, 'root')) {
511
      $parents = taxonomy_get_parents_all($term->tid);
512
      $root_term = end($parents);
513
      if ($root_term->tid != $term->tid) {
514
        $replacements += token_generate('term', $root_tokens, array('term' => $root_term), $options);
515
      }
516
    }
517
  }
518

    
519
  // Vocabulary tokens.
520
  if ($type == 'vocabulary' && !empty($data['vocabulary'])) {
521
    $vocabulary = $data['vocabulary'];
522

    
523
    foreach ($tokens as $name => $original) {
524
      switch ($name) {
525
        case 'machine-name':
526
          // This is a machine name so does not ever need to be sanitized.
527
          $replacements[$original] = $vocabulary->machine_name;
528
          break;
529
        case 'edit-url':
530
          $replacements[$original] = url("admin/structure/taxonomy/{$vocabulary->machine_name}/edit", $url_options);
531
          break;
532
      }
533
    }
534
  }
535

    
536
  // File tokens.
537
  if ($type == 'file' && !empty($data['file'])) {
538
    $file = $data['file'];
539

    
540
    foreach ($tokens as $name => $original) {
541
      switch ($name) {
542
        case 'basename':
543
          $basename = pathinfo($file->uri, PATHINFO_BASENAME);
544
          $replacements[$original] = $sanitize ? check_plain($basename) : $basename;
545
          break;
546
        case 'extension':
547
          $extension = pathinfo($file->uri, PATHINFO_EXTENSION);
548
          $replacements[$original] = $sanitize ? check_plain($extension) : $extension;
549
          break;
550
        case 'size-raw':
551
          $replacements[$original] = (int) $file->filesize;
552
          break;
553
      }
554
    }
555
  }
556

    
557
  // User tokens.
558
  if ($type == 'user' && !empty($data['user'])) {
559
    $account = $data['user'];
560

    
561
    foreach ($tokens as $name => $original) {
562
      switch ($name) {
563
        case 'picture':
564
          if (variable_get('user_pictures', 0)) {
565
            $replacements[$original] = theme('user_picture', array('account' => $account));
566
          }
567
          break;
568
        case 'roles':
569
          // The roles array may be set from checkbox values so ensure it always
570
          // has 'proper' data with the role names.
571
          $roles = array_intersect_key(user_roles(), $account->roles);
572
          $replacements[$original] = token_render_array($roles, $options);
573
          break;
574
      }
575
    }
576

    
577
    // Chained token relationships.
578
    if (variable_get('user_pictures', 0) && !empty($account->picture) && ($picture_tokens = token_find_with_prefix($tokens, 'picture'))) {
579
      // @todo Remove when core bug http://drupal.org/node/978028 is fixed.
580
      $account->picture->description = '';
581
      $replacements += token_generate('file', $picture_tokens, array('file' => $account->picture), $options);
582
    }
583
    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
584
      $replacements += token_generate('url', $url_tokens, entity_uri('user', $account), $options);
585
    }
586
    if ($role_tokens = token_find_with_prefix($tokens, 'roles')) {
587
      // The roles array may be set from checkbox values so ensure it always
588
      // has 'proper' data with the role names.
589
      $roles = array_intersect_key(user_roles(), $account->roles);
590
      $replacements += token_generate('array', $role_tokens, array('array' => $roles), $options);
591
    }
592
  }
593

    
594
  // Current user tokens.
595
  if ($type == 'current-user') {
596
    foreach ($tokens as $name => $original) {
597
      switch ($name) {
598
        case 'ip-address':
599
          $ip = ip_address();
600
          $replacements[$original] = $sanitize ? check_plain($ip) : $ip;
601
          break;
602
      }
603
    }
604
  }
605

    
606
  // Menu link tokens.
607
  if ($type == 'menu-link' && !empty($data['menu-link'])) {
608
    $link = (array) $data['menu-link'];
609

    
610
    if (!isset($link['title'])) {
611
      // Re-load the link if it was not loaded via token_menu_link_load().
612
      $link = token_menu_link_load($link['mlid']);
613
    }
614

    
615
    foreach ($tokens as $name => $original) {
616
      switch ($name) {
617
        case 'mlid':
618
          $replacements[$original] = $link['mlid'];
619
          break;
620
        case 'title':
621
          $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
622
          break;
623
        case 'url':
624
          $replacements[$original] = url($link['href'], $url_options);
625
          break;
626
        case 'parent':
627
          if (!empty($link['plid']) && $parent = token_menu_link_load($link['plid'])) {
628
            $replacements[$original] = $sanitize ? check_plain($parent['title']) : $parent['title'];
629
          }
630
          break;
631
        case 'parents':
632
          if ($parents = token_menu_link_load_all_parents($link['mlid'])) {
633
            $replacements[$original] = token_render_array($parents, $options);
634
          }
635
          break;
636
        case 'root';
637
          if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && $root = token_menu_link_load($link['p1'])) {
638
            $replacements[$original] = $sanitize ? check_plain($root['title']) : $root['title'];
639
          }
640
          break;
641
      }
642
    }
643

    
644
    // Chained token relationships.
645
    if (!empty($link['plid']) && ($source_tokens = token_find_with_prefix($tokens, 'parent')) && $parent = token_menu_link_load($link['plid'])) {
646
      $replacements += token_generate('menu-link', $source_tokens, array('menu-link' => $parent), $options);
647
    }
648
    // [menu-link:parents:*] chained tokens.
649
    if ($parents_tokens = token_find_with_prefix($tokens, 'parents')) {
650
      if ($parents = token_menu_link_load_all_parents($link['mlid'])) {
651
        $replacements += token_generate('array', $parents_tokens, array('array' => $parents), $options);
652
      }
653
    }
654
    if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && ($root_tokens = token_find_with_prefix($tokens, 'root')) && $root = token_menu_link_load($link['p1'])) {
655
      $replacements += token_generate('menu-link', $root_tokens, array('menu-link' => $root), $options);
656
    }
657
    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
658
      $replacements += token_generate('url', $url_tokens, array('path' => $link['href']), $options);
659
    }
660
  }
661

    
662
  // Current page tokens.
663
  if ($type == 'current-page') {
664
    $current_path = current_path();
665

    
666
    foreach ($tokens as $name => $original) {
667
      switch ($name) {
668
        case 'title':
669
          $title = drupal_get_title();
670
          $replacements[$original] = $sanitize ? $title : decode_entities($title);
671
          break;
672
        case 'url':
673
          $replacements[$original] = url($current_path, $url_options);
674
          break;
675
        case 'page-number':
676
          if ($page = filter_input(INPUT_GET, 'page')) {
677
            // @see PagerDefault::execute()
678
            $pager_page_array = explode(',', $page);
679
            $page = $pager_page_array[0];
680
          }
681
          $replacements[$original] = (int) $page + 1;
682
          break;
683
      }
684
    }
685

    
686
    // @deprecated
687
    // [current-page:arg] dynamic tokens.
688
    if ($arg_tokens = token_find_with_prefix($tokens, 'arg')) {
689
      foreach ($arg_tokens as $name => $original) {
690
        if (is_numeric($name) && ($arg = arg($name)) && isset($arg)) {
691
          $replacements[$original] = $sanitize ? check_plain($arg) : $arg;
692
        }
693
      }
694
    }
695

    
696
    // [current-page:query] dynamic tokens.
697
    if ($query_tokens = token_find_with_prefix($tokens, 'query')) {
698
      foreach ($query_tokens as $name => $original) {
699
        // @todo Should this use filter_input()?
700
        if (isset($_GET[$name])) {
701
          $replacements[$original] = $sanitize ? check_plain($_GET[$name]) : $_GET[$name];
702
        }
703
      }
704
    }
705

    
706
    // Chained token relationships.
707
    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
708
      $replacements += token_generate('url', $url_tokens, array('path' => $current_path), $options);
709
    }
710
  }
711

    
712
  // URL tokens.
713
  if ($type == 'url' && !empty($data['path'])) {
714
    $path = $data['path'];
715

    
716
    if (isset($data['options'])) {
717
      // Merge in the URL options if available.
718
      $url_options = $data['options'] + $url_options;
719
    }
720

    
721
    foreach ($tokens as $name => $original) {
722
      switch ($name) {
723
        case 'path':
724
          $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
725
          $replacements[$original] = $sanitize ? check_plain($value) : $value;
726
          break;
727
        case 'alias':
728
          // @deprecated
729
          $alias = drupal_get_path_alias($path, $language_code);
730
          $replacements[$original] = $sanitize ? check_plain($alias) : $alias;
731
          break;
732
        case 'absolute':
733
          $replacements[$original] = url($path, $url_options);
734
          break;
735
        case 'relative':
736
          $replacements[$original] = url($path, array('absolute' => FALSE) + $url_options);
737
          break;
738
        case 'brief':
739
          $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', url($path, $url_options));
740
          break;
741
        case 'unaliased':
742
          $replacements[$original] = url($path, array('alias' => TRUE) + $url_options);
743
          break;
744
        case 'args':
745
          $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
746
          $replacements[$original] = token_render_array(arg(NULL, $value), $options);
747
          break;
748
      }
749
    }
750

    
751
    // [url:arg:*] chained tokens.
752
    if ($arg_tokens = token_find_with_prefix($tokens, 'args')) {
753
      $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
754
      $replacements += token_generate('array', $arg_tokens, array('array' => arg(NULL, $value)), $options);
755
    }
756

    
757
    // [url:unaliased:*] chained tokens.
758
    if ($unaliased_tokens = token_find_with_prefix($tokens, 'unaliased')) {
759
      $unaliased_token_data['path'] = $path;
760
      $unaliased_token_data['options'] = isset($data['options']) ? $data['options'] : array();
761
      $unaliased_token_data['options']['alias'] = TRUE;
762
      $replacements += token_generate('url', $unaliased_tokens, $unaliased_token_data, $options);
763
    }
764
  }
765

    
766
  // Entity tokens.
767
  if (!empty($data[$type]) && $entity_type = token_get_entity_mapping('token', $type)) {
768
    $entity = $data[$type];
769

    
770
    // Sometimes taxonomy terms are not properly loaded.
771
    // @see http://drupal.org/node/870528
772
    if ($entity_type == 'taxonomy_term' && !isset($entity->vocabulary_machine_name)) {
773
      $entity->vocabulary_machine_name = db_query("SELECT machine_name FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $entity->vid))->fetchField();
774
    }
775

    
776
    foreach ($tokens as $name => $original) {
777
      switch ($name) {
778
        case 'url':
779
          if (_token_module($type, 'url') == 'token' && $uri = entity_uri($entity_type, $entity)) {
780
            $replacements[$original] = url($uri['path'], $uri['options']);
781
          }
782
          break;
783

    
784
        case 'original':
785
          if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
786
            $label = entity_label($entity_type, $entity->original);
787
            $replacements[$original] = $sanitize ? check_plain($label) : $label;
788
          }
789
          break;
790
      }
791
    }
792

    
793
    // [entity:url:*] chained tokens.
794
    if (($url_tokens = token_find_with_prefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
795
      $replacements += token_generate('url', $url_tokens, entity_uri($entity_type, $entity), $options);
796
    }
797

    
798
    // [entity:original:*] chained tokens.
799
    if (($original_tokens = token_find_with_prefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
800
      $replacements += token_generate($type, $original_tokens, array($type => $entity->original), $options);
801
    }
802

    
803
    // Pass through to an generic 'entity' token type generation.
804
    $entity_data = array(
805
      'entity_type' => $entity_type,
806
      'entity' => $entity,
807
      'token_type' => $type,
808
    );
809
    // @todo Investigate passing through more data like everything from entity_extract_ids().
810
    $replacements += token_generate('entity', $tokens, $entity_data, $options);
811
  }
812

    
813
  // Array tokens.
814
  if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
815
    $array = $data['array'];
816

    
817
    $sort = isset($options['array sort']) ? $options['array sort'] : TRUE;
818
    $keys = element_children($array, $sort);
819

    
820
    foreach ($tokens as $name => $original) {
821
      switch ($name) {
822
        case 'first':
823
          $value = $array[$keys[0]];
824
          $value = is_array($value) ? render($value) : (string) $value;
825
          $replacements[$original] = $sanitize ? check_plain($value) : $value;
826
          break;
827
        case 'last':
828
          $value = $array[$keys[count($keys) - 1]];
829
          $value = is_array($value) ? render($value) : (string) $value;
830
          $replacements[$original] = $sanitize ? check_plain($value) : $value;
831
          break;
832
        case 'count':
833
          $replacements[$original] = count($keys);
834
          break;
835
        case 'keys':
836
          $replacements[$original] = token_render_array($keys, $options);
837
          break;
838
        case 'reversed':
839
          $reversed = array_reverse($array, TRUE);
840
          $replacements[$original] = token_render_array($reversed, $options);
841
          break;
842
        case 'join':
843
          $replacements[$original] = token_render_array($array, array('join' => '') + $options);
844
          break;
845
      }
846
    }
847

    
848
    // [array:value:*] dynamic tokens.
849
    if ($value_tokens = token_find_with_prefix($tokens, 'value')) {
850
      foreach ($value_tokens as $key => $original) {
851
        if ($key[0] !== '#' && isset($array[$key])) {
852
          $replacements[$original] = token_render_array_value($array[$key], $options);
853
        }
854
      }
855
    }
856

    
857
    // [array:join:*] dynamic tokens.
858
    if ($join_tokens = token_find_with_prefix($tokens, 'join')) {
859
      foreach ($join_tokens as $join => $original) {
860
        $replacements[$original] = token_render_array($array, array('join' => $join) + $options);
861
      }
862
    }
863

    
864
    // [array:keys:*] chained tokens.
865
    if ($key_tokens = token_find_with_prefix($tokens, 'keys')) {
866
      $replacements += token_generate('array', $key_tokens, array('array' => $keys), $options);
867
    }
868

    
869
    // [array:reversed:*] chained tokens.
870
    if ($reversed_tokens = token_find_with_prefix($tokens, 'reversed')) {
871
      $replacements += token_generate('array', $reversed_tokens, array('array' => array_reverse($array, TRUE)), array('array sort' => FALSE) + $options);
872
    }
873

    
874
    // @todo Handle if the array values are not strings and could be chained.
875
  }
876

    
877
  // Random tokens.
878
  if ($type == 'random') {
879
    foreach ($tokens as $name => $original) {
880
      switch ($name) {
881
        case 'number':
882
          $replacements[$original] = mt_rand();
883
          break;
884
      }
885
    }
886

    
887
    // [custom:hash:*] dynamic token.
888
    if ($hash_tokens = token_find_with_prefix($tokens, 'hash')) {
889
      $algos = hash_algos();
890
      foreach ($hash_tokens as $name => $original) {
891
        if (in_array($name, $algos)) {
892
          $replacements[$original] = hash($name, drupal_random_bytes(55));
893
        }
894
      }
895
    }
896
  }
897

    
898
  // If $type is a token type, $data[$type] is empty but $data[$entity_type] is
899
  // not, re-run token replacements.
900
  if (empty($data[$type]) && ($entity_type = token_get_entity_mapping('token', $type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
901
    $data[$type] = $data[$entity_type];
902
    $options['recursive'] = TRUE;
903
    $replacements += module_invoke_all('tokens', $type, $tokens, $data, $options);
904
  }
905

    
906
  // If the token type specifics a 'needs-data' value, and the value is not
907
  // present in $data, then throw an error.
908
  if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
909
    // Only check when tests are running.
910
    $type_info = token_get_info($type);
911
    if (!empty($type_info['needs-data']) && !isset($data[$type_info['needs-data']])) {
912
      trigger_error(t('Attempting to perform token replacement for token type %type without required data', array('%type' => $type)), E_USER_WARNING);
913
    }
914
  }
915

    
916
  return $replacements;
917
}
918

    
919
/**
920
 * Implements hook_tokens_alter().
921
 *
922
 * Fix existing core tokens that do not work correctly.
923
 */
924
function token_tokens_alter(array &$replacements, array $context) {
925
  $options = $context['options'];
926

    
927
  $sanitize = !empty($options['sanitize']);
928
  $langcode = !empty($options['language']->language) ? $options['language']->language : NULL;
929

    
930
  // Comment token fixes.
931
  if ($context['type'] == 'comment' && !empty($context['data']['comment'])) {
932
    $comment = $context['data']['comment'];
933

    
934
    foreach ($context['tokens'] as $name => $original) {
935
      switch ($name) {
936
        case 'name':
937
        case 'author':
938
          // @todo Remove when http://drupal.org/node/920056 is fixed.
939
          if (!empty($comment->uid)) {
940
            $account = user_load($comment->uid);
941
          }
942
          else {
943
            $account = drupal_anonymous_user();
944
            $account->name = $comment->name;
945
          }
946
          $name = format_username($account);
947
          $replacements[$original] = $sanitize ? check_plain($name) : $name;
948
          break;
949
      }
950
    }
951
  }
952

    
953
  // Node token fixes.
954
  if ($context['type'] == 'node' && !empty($context['data']['node'])) {
955
    $node = $context['data']['node'];
956

    
957
    foreach ($context['tokens'] as $name => $original) {
958
      switch ($name) {
959
        case 'author':
960
          // http://drupal.org/node/1185842 was fixed in core release 7.9.
961
          if (version_compare(VERSION, '7.9', '<')) {
962
            $account = user_load($node->uid);
963
            $name = format_username($account);
964
            $replacements[$original] = $sanitize ? check_plain($name) : $name;
965
          }
966
          break;
967
      }
968
    }
969
  }
970

    
971
  // File token fixes.
972
  if ($context['type'] == 'file' && !empty($context['data']['file'])) {
973
    $file = $context['data']['file'];
974

    
975
    foreach ($context['tokens'] as $name => $original) {
976
      switch ($name) {
977
        case 'owner':
978
          // http://drupal.org/node/978028 was fixed in core release 7.7.
979
          if (version_compare(VERSION, '7.7', '<')) {
980
            $account = user_load($file->uid);
981
            $name = format_username($account);
982
            $replacements[$original] = $sanitize ? check_plain($name) : $name;
983
          }
984
          break;
985
      }
986
    }
987
  }
988
}
989

    
990
/**
991
 * Implements hook_token_info() on behalf of book.module.
992
 */
993
function book_token_info() {
994
  $info['tokens']['node']['book'] = array(
995
    'name' => t('Book'),
996
    'description' => t('The book page associated with the node.'),
997
    'type' => 'menu-link',
998
  );
999
  return $info;
1000
}
1001

    
1002
/**
1003
 * Implements hook_tokens() on behalf of book.module.
1004
 */
1005
function book_tokens($type, $tokens, array $data = array(), array $options = array()) {
1006
  $replacements = array();
1007
  $sanitize = !empty($options['sanitize']);
1008

    
1009
  // Node tokens.
1010
  if ($type == 'node' && !empty($data['node'])) {
1011
    $node = $data['node'];
1012

    
1013
    if (!empty($node->book['mlid'])) {
1014
      $link = token_book_link_load($node->book['mlid']);
1015

    
1016
      foreach ($tokens as $name => $original) {
1017
        switch ($name) {
1018
          case 'book':
1019
            $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
1020
            break;
1021
        }
1022
      }
1023

    
1024
      // Chained token relationships.
1025
      if ($book_tokens = token_find_with_prefix($tokens, 'book')) {
1026
        $replacements += token_generate('menu-link', $book_tokens, array('menu-link' => $link), $options);
1027
      }
1028
    }
1029
  }
1030

    
1031
  return $replacements;
1032
}
1033

    
1034
/**
1035
 * Implements hook_token_info() on behalf of menu.module.
1036
 */
1037
function menu_token_info() {
1038
  // Menu tokens.
1039
  $info['types']['menu'] = array(
1040
    'name' => t('Menus'),
1041
    'description' => t('Tokens related to menus.'),
1042
    'needs-data' => 'menu',
1043
  );
1044
  $info['tokens']['menu']['name'] = array(
1045
    'name' => t('Name'),
1046
    'description' => t("The name of the menu."),
1047
  );
1048
  $info['tokens']['menu']['machine-name'] = array(
1049
    'name' => t('Machine-readable name'),
1050
    'description' => t("The unique machine-readable name of the menu."),
1051
  );
1052
  $info['tokens']['menu']['description'] = array(
1053
    'name' => t('Description'),
1054
    'description' => t('The optional description of the menu.'),
1055
  );
1056
  $info['tokens']['menu']['menu-link-count'] = array(
1057
    'name' => t('Menu link count'),
1058
    'description' => t('The number of menu links belonging to the menu.'),
1059
  );
1060
  $info['tokens']['menu']['edit-url'] = array(
1061
    'name' => t('Edit URL'),
1062
    'description' => t("The URL of the menu's edit page."),
1063
  );
1064

    
1065
  $info['tokens']['menu-link']['menu'] = array(
1066
    'name' => t('Menu'),
1067
    'description' => t('The menu of the menu link.'),
1068
    'type' => 'menu',
1069
  );
1070
  $info['tokens']['menu-link']['edit-url'] = array(
1071
    'name' => t('Edit URL'),
1072
    'description' => t("The URL of the menu link's edit page."),
1073
  );
1074
  $info['tokens']['node']['menu-link'] = array(
1075
    'name' => t('Menu link'),
1076
    'description' => t("The menu link for this node."),
1077
    'type' => 'menu-link',
1078
  );
1079

    
1080
  return $info;
1081
}
1082

    
1083
/**
1084
 * Implements hook_tokens() on behalf of menu.module.
1085
 */
1086
function menu_tokens($type, $tokens, array $data = array(), array $options = array()) {
1087
  $replacements = array();
1088

    
1089
  $url_options = array('absolute' => TRUE);
1090
  if (isset($options['language'])) {
1091
    $url_options['language'] = $options['language'];
1092
    $language_code = $options['language']->language;
1093
  }
1094
  else {
1095
    $language_code = NULL;
1096
  }
1097

    
1098
  $sanitize = !empty($options['sanitize']);
1099

    
1100
  // Node tokens.
1101
  if ($type == 'node' && !empty($data['node'])) {
1102
    $node = $data['node'];
1103

    
1104
    foreach ($tokens as $name => $original) {
1105
      switch ($name) {
1106
        case 'menu-link':
1107
          if ($link = token_node_menu_link_load($node)) {
1108
            $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
1109
          }
1110
          break;
1111
      }
1112

    
1113
      // Chained token relationships.
1114
      if ($menu_tokens = token_find_with_prefix($tokens, 'menu-link')) {
1115
        if ($link = token_node_menu_link_load($node)) {
1116
          $replacements += token_generate('menu-link', $menu_tokens, array('menu-link' => $link), $options);
1117
        }
1118
      }
1119
    }
1120
  }
1121

    
1122
  // Menu link tokens.
1123
  if ($type == 'menu-link' && !empty($data['menu-link'])) {
1124
    $link = (array) $data['menu-link'];
1125

    
1126
    foreach ($tokens as $name => $original) {
1127
      switch ($name) {
1128
        case 'menu':
1129
          if ($menu = menu_load($link['menu_name'])) {
1130
            $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title'];
1131
          }
1132
          break;
1133
        case 'edit-url':
1134
          $replacements[$original] = url("admin/structure/menu/item/{$link['mlid']}/edit", $url_options);
1135
          break;
1136
      }
1137
    }
1138

    
1139
    // Chained token relationships.
1140
    if (($menu_tokens = token_find_with_prefix($tokens, 'menu')) && $menu = menu_load($link['menu_name'])) {
1141
      $replacements += token_generate('menu', $menu_tokens, array('menu' => $menu), $options);
1142
    }
1143
  }
1144

    
1145
  // Menu tokens.
1146
  if ($type == 'menu' && !empty($data['menu'])) {
1147
    $menu = (array) $data['menu'];
1148

    
1149
    foreach ($tokens as $name => $original) {
1150
      switch ($name) {
1151
        case 'name':
1152
          $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title'];
1153
          break;
1154
        case 'machine-name':
1155
          // This is a machine name so does not ever need to be sanitized.
1156
          $replacements[$original] = $menu['menu_name'];
1157
          break;
1158
        case 'description':
1159
          $replacements[$original] = $sanitize ? filter_xss($menu['description']) : $menu['description'];
1160
          break;
1161
        case 'menu-link-count':
1162
          $query = db_select('menu_links');
1163
          $query->condition('menu_name', $menu['menu_name']);
1164
          $query->addTag('menu_menu_link_count');
1165
          $count = $query->countQuery()->execute()->fetchField();
1166
          $replacements[$original] = (int) $count;
1167
          break;
1168
        case 'edit-url':
1169
          $replacements[$original] = url("admin/structure/menu/manage/" . $menu['menu_name'], $url_options);
1170
          break;
1171
      }
1172
    }
1173
  }
1174

    
1175
  return $replacements;
1176
}
1177

    
1178
/**
1179
 * Implements hook_token_info() on behalf of profile.module.
1180
 */
1181
function profile_token_info() {
1182
  $info = array();
1183

    
1184
  foreach (_token_profile_fields() as $field) {
1185
    $info['tokens']['user'][$field->token_name] = array(
1186
      'name' => check_plain($field->title),
1187
      'description' => t('@category @type field.', array('@category' => drupal_ucfirst($field->category), '@type' => $field->type)),
1188
    );
1189

    
1190
    switch ($field->type) {
1191
      case 'date':
1192
        $info['tokens']['user'][$field->token_name]['type'] = 'date';
1193
        break;
1194
    }
1195
  }
1196

    
1197
  return $info;
1198
}
1199

    
1200
/**
1201
 * Implements hook_tokens() on behalf of profile.module.
1202
 */
1203
function profile_tokens($type, $tokens, array $data = array(), array $options = array()) {
1204
  $replacements = array();
1205
  $sanitize = !empty($options['sanitize']);
1206
  $language_code = isset($options['language']) ? $options['language']->language : NULL;
1207

    
1208
  if ($type == 'user' && !empty($data['user'])) {
1209
    $account = $data['user'];
1210

    
1211
    // Load profile fields if this is the global user account.
1212
    // @see http://drupal.org/node/361471
1213
    // @see http://drupal.org/node/967330
1214
    if ($account->uid == $GLOBALS['user']->uid && isset($account->timestamp)) {
1215
      $profile_users = array($account->uid => $account);
1216
      profile_user_load($profile_users);
1217
      $account = $profile_users[$account->uid];
1218
    }
1219

    
1220
    $profile_fields = _token_profile_fields();
1221
    foreach ($tokens as $name => $original) {
1222
      if (isset($profile_fields[$name]) && !empty($account->{$profile_fields[$name]->name})) {
1223
        $value = $account->{$profile_fields[$name]->name};
1224
        switch ($profile_fields[$name]->type) {
1225
          case 'textarea':
1226
            $replacements[$original] = $sanitize ? check_markup($value, filter_default_format($account), '', TRUE) : $value;
1227
            break;
1228
          case 'date':
1229
            $timestamp = gmmktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
1230
            $replacements[$original] = format_date($timestamp, 'medium', '', NULL, $language_code);
1231
            break;
1232
          case 'url':
1233
            $replacements[$original] = $sanitize ? check_url($value) : $value;
1234
            break;
1235
          case 'checkbox':
1236
            // Checkbox field if checked should return the text.
1237
            $replacements[$original] = $sanitize ? check_plain($profile_fields[$name]->title) : $profile_fields[$name]->title;
1238
            break;
1239
          case 'list':
1240
            $value = preg_split("/[,\n\r]/", $value);
1241
            $value = array_map('trim', $value);
1242
            $value = implode(', ', $value);
1243
            // Intentionally fall through to the default condition.
1244
          default:
1245
            $replacements[$original] = $sanitize ? check_plain($value) : $value;
1246
            break;
1247
        }
1248
      }
1249
    }
1250

    
1251
    // Chained token relationships.
1252
    foreach ($profile_fields as $field) {
1253
      if ($field->type == 'date' && isset($account->{$field->name}) && $field_tokens = token_find_with_prefix($tokens, $field->token_name)) {
1254
        $date = $account->{$field->name};
1255
        $replacements += token_generate('date', $field_tokens, array('date' => gmmktime(0, 0, 0, $date['month'], $date['day'], $date['year'])), $options);
1256
      }
1257
    }
1258
  }
1259

    
1260
  return $replacements;
1261
}
1262

    
1263
/**
1264
 * Fetch an array of profile field objects, keyed by token name.
1265
 */
1266
function _token_profile_fields() {
1267
  $fields = &drupal_static(__FUNCTION__);
1268

    
1269
  if (!isset($fields)) {
1270
    $fields = array();
1271
    $results = db_query("SELECT name, title, category, type FROM {profile_field}");
1272
    foreach ($results as $field) {
1273
      $field->token_name = token_clean_token_name($field->name);
1274
      $fields[$field->token_name] = $field;
1275
    }
1276
  }
1277

    
1278
  return $fields;
1279
}
1280

    
1281
/**
1282
 * Fetch an array of field data used for tokens.
1283
 */
1284
function _token_field_info($field_name = NULL) {
1285
  $info = &drupal_static(__FUNCTION__);
1286

    
1287
  if (!isset($fields)) {
1288
    if ($cached = cache_get('field:info', 'cache_token')) {
1289
      $info = $cached->data;
1290
    }
1291
    else {
1292
      $info = array();
1293

    
1294
      $fields = field_info_fields();
1295
      $instances = field_info_instances();
1296
      $type_info = field_info_field_types();
1297
      $entity_info = entity_get_info();
1298

    
1299
      foreach ($fields as $field) {
1300
        $key = $field['field_name'];
1301
        if (!empty($field['bundles'])) {
1302
          foreach (array_keys($field['bundles']) as $entity) {
1303
            // Make sure a token type exists for this entity.
1304
            $token_type = token_get_entity_mapping('entity', $entity);
1305
            if (empty($token_type)) {
1306
              continue;
1307
            }
1308

    
1309
            $info[$key]['token types'][] = $token_type;
1310
            $info[$key] += array('labels' => array(), 'bundles' => array());
1311

    
1312
            // Find which label is most commonly used.
1313
            foreach ($field['bundles'][$entity] as $bundle) {
1314
              // Field information will included fields attached to disabled
1315
              // bundles, so check that the bundle exists before provided a
1316
              // token for it.
1317
              // @see http://drupal.org/node/1252566
1318
              if (!isset($entity_info[$entity]['bundles'][$bundle])) {
1319
                continue;
1320
              }
1321

    
1322
              $info[$key]['labels'][] = $instances[$entity][$bundle][$key]['label'];
1323
              $info[$key]['bundles'][$token_type][$bundle] = $entity_info[$entity]['bundles'][$bundle]['label'];
1324
            }
1325
          }
1326
        }
1327

    
1328
        if (isset($info[$key])) {
1329
          $labels = array_count_values($info[$key]['labels']);
1330
          arsort($labels);
1331
          $info[$key]['label'] = check_plain(key($labels));
1332

    
1333
          // Generate a description for the token.
1334
          $info[$key]['description'] = t('@type field.', array('@type' => $type_info[$field['type']]['label']));
1335
          if ($also_known_as = array_unique(array_diff($info[$key]['labels'], array($info[$key]['label'])))) {
1336
            $info[$key]['description'] .= ' ' . t('Also known as %labels.', array('%labels' => implode(', ', $also_known_as)));
1337
          }
1338
        }
1339
      }
1340

    
1341
      drupal_alter('token_field_info', $info);
1342
      cache_set('field:info', $info, 'cache_token');
1343
    }
1344
  }
1345

    
1346
  if (isset($field_name)) {
1347
    return isset($info[$field_name]) ? $info[$field_name] : FALSE;
1348
  }
1349

    
1350
  return $info;
1351
}
1352

    
1353
/**
1354
 * Implements hook_token_info_alter() on behalf of field.module.
1355
 *
1356
 * We use hook_token_info_alter() rather than hook_token_info() as other
1357
 * modules may already have defined some field tokens.
1358
 */
1359
function field_token_info_alter(&$info) {
1360
  $fields = _token_field_info();
1361

    
1362
  // Attach field tokens to their respecitve entity tokens.
1363
  foreach ($fields as $field_name => $field) {
1364
    foreach (array_keys($field['bundles']) as $token_type) {
1365
      // If a token already exists for this field, then don't add it.
1366
      if (isset($info['tokens'][$token_type][$field_name])) {
1367
        continue;
1368
      }
1369

    
1370
      // Ensure the tokens exist.
1371
      if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
1372
        continue;
1373
      }
1374

    
1375
      if ($token_type == 'comment' && $field_name == 'comment_body') {
1376
        // Core provides the comment field as [comment:body].
1377
        continue;
1378
      }
1379

    
1380
      $info['tokens'][$token_type][$field_name] = array(
1381
        // Note that label and description have already been sanitized by _token_field_info().
1382
        'name' => $field['label'],
1383
        'description' => $field['description'],
1384
        'module' => 'token',
1385
      );
1386
    }
1387
  }
1388
}
1389

    
1390
/**
1391
 * Implements hook_tokens() on behalf of field.module.
1392
 */
1393
function field_tokens($type, $tokens, array $data = array(), array $options = array()) {
1394
  $replacements = array();
1395
  $sanitize = !empty($options['sanitize']);
1396
  $langcode = isset($options['language']) ? $options['language']->language : NULL;
1397

    
1398
  // Entity tokens.
1399
  if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {
1400
    $entity_type = $data['entity_type'];
1401

    
1402
    // The field API does weird stuff to the entity, so let's clone it.
1403
    $entity = clone $data['entity'];
1404

    
1405
    // Reset the prepared view flag in case token generation is called from
1406
    // inside field_attach_view().
1407
    unset($entity->_field_view_prepared);
1408

    
1409
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
1410
    $fields = field_info_instances($entity_type, $bundle);
1411

    
1412
    foreach (array_keys($fields) as $field_name) {
1413
      // Do not continue if the field is empty.
1414
      if (empty($entity->{$field_name})) {
1415
        continue;
1416
      }
1417

    
1418
      // Replace the [entity:field-name] token only if token module added this
1419
      // token.
1420
      if (isset($tokens[$field_name]) && _token_module($data['token_type'], $field_name) == 'token') {
1421
        $original = $tokens[$field_name];
1422

    
1423
        $field_output = field_view_field($entity_type, $entity, $field_name, 'token', $langcode);
1424
        $field_output['#token_options'] = $options;
1425
        $field_output['#pre_render'][] = 'token_pre_render_field_token';
1426
        $replacements[$original] = drupal_render($field_output);
1427
      }
1428
    }
1429

    
1430
    // Remove the cloned object from memory.
1431
    unset($entity);
1432
  }
1433

    
1434
  return $replacements;
1435
}
1436

    
1437
/**
1438
 * Pre-render callback for field output used with tokens.
1439
 */
1440
function token_pre_render_field_token(&$elements) {
1441
  // Remove the field theme hook, attachments, and JavaScript states.
1442
  unset($elements['#theme']);
1443
  unset($elements['#states']);
1444
  unset($elements['#attached']);
1445

    
1446
  // Prevent multi-value fields from appearing smooshed together by appending
1447
  // a join suffix to all but the last value.
1448
  $deltas = element_get_visible_children($elements);
1449
  $count = count($deltas);
1450
  if ($count > 1) {
1451
    $join = isset($elements['#token_options']['join']) ? $elements['#token_options']['join'] : ", ";
1452
    foreach ($deltas as $index => $delta) {
1453
      // Do not add a suffix to the last item.
1454
      if ($index < ($count - 1)) {
1455
        $elements[$delta] += array('#suffix' => $join);
1456
      }
1457
    }
1458
  }
1459
  return $elements;
1460
}