f6ad619109dfa0c8f13b7e8e9eb5484b44324796
[yaffs-website] / web / modules / contrib / token / token.tokens.inc
1 <?php
2
3 /**
4  * @file
5  * Token callbacks for the token module.
6  */
7 use Drupal\Core\Entity\ContentEntityInterface;
8 use Drupal\Core\Entity\FieldableEntityInterface;
9 use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
10 use Drupal\Core\Render\BubbleableMetadata;
11 use Drupal\Core\Render\Element;
12 use Drupal\Component\Utility\Crypt;
13 use Drupal\Component\Utility\Unicode;
14 use Drupal\Component\Utility\Html;
15 use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
16 use Drupal\Core\Url;
17 use Drupal\field\FieldStorageConfigInterface;
18 use Drupal\menu_link_content\MenuLinkContentInterface;
19 use Drupal\node\Entity\Node;
20 use Drupal\node\NodeInterface;
21 use Drupal\system\Entity\Menu;
22 use Drupal\user\UserInterface;
23 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
24 use Drupal\Core\TypedData\PrimitiveInterface;
25 use Drupal\Core\Field\FieldStorageDefinitionInterface;
26 use Drupal\Core\Entity\ContentEntityTypeInterface;
27 use Drupal\image\Entity\ImageStyle;
28
29 /**
30  * Implements hook_token_info_alter().
31  */
32 function token_token_info_alter(&$info) {
33   // Force 'date' type tokens to require input and add a 'current-date' type.
34   // @todo Remove when http://drupal.org/node/943028 is fixed.
35   $info['types']['date']['needs-data'] = 'date';
36   $info['types']['current-date'] = array(
37     'name' => t('Current date'),
38     'description' => t('Tokens related to the current date and time.'),
39     'type' => 'date',
40   );
41
42   // Add a 'dynamic' key to any tokens that have chained but dynamic tokens.
43   $info['tokens']['date']['custom']['dynamic'] = TRUE;
44
45   // The [file:size] may not always return in kilobytes.
46   // @todo Remove when http://drupal.org/node/1193044 is fixed.
47   if (!empty($info['tokens']['file']['size'])) {
48     $info['tokens']['file']['size']['description'] = t('The size of the file.');
49   }
50
51   // Remove deprecated tokens from being listed.
52   unset($info['tokens']['node']['tnid']);
53   unset($info['tokens']['node']['type']);
54   unset($info['tokens']['node']['type-name']);
55
56   // Support 'url' type tokens for core tokens.
57   if (isset($info['tokens']['comment']['url']) && \Drupal::moduleHandler()->moduleExists('comment')) {
58     $info['tokens']['comment']['url']['type'] = 'url';
59   }
60   if (isset($info['tokens']['node']['url']) && \Drupal::moduleHandler()->moduleExists('node')) {
61     $info['tokens']['node']['url']['type'] = 'url';
62   }
63   if (isset($info['tokens']['term']['url']) && \Drupal::moduleHandler()->moduleExists('taxonomy')) {
64     $info['tokens']['term']['url']['type'] = 'url';
65   }
66   $info['tokens']['user']['url']['type'] = 'url';
67
68   // Add [token:url] tokens for any URI-able entities.
69   $entities = \Drupal::entityTypeManager()->getDefinitions();
70   foreach ($entities as $entity => $entity_info) {
71     // Do not generate tokens if the entity doesn't define a token type or is
72     // not a content entity.
73     if (!$entity_info->get('token_type') || (!$entity_info instanceof ContentEntityTypeInterface)) {
74       continue;
75     }
76
77     $token_type = $entity_info->get('token_type');
78     if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
79       // Define tokens for entity type's without their own integration.
80       $info['types'][$entity_info->id()] = [
81         'name' => $entity_info->getLabel(),
82         'needs-data' => $entity_info->id(),
83         'module' => 'token',
84       ];
85     }
86
87     // Add [entity:url] tokens if they do not already exist.
88     // @todo Support entity:label
89     if (!isset($info['tokens'][$token_type]['url'])) {
90       $info['tokens'][$token_type]['url'] = array(
91         'name' => t('URL'),
92         'description' => t('The URL of the @entity.', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
93         'module' => 'token',
94         'type' => 'url',
95       );
96     }
97
98     // Add [entity:original] tokens if they do not already exist.
99     if (!isset($info['tokens'][$token_type]['original'])) {
100       $info['tokens'][$token_type]['original'] = array(
101         'name' => t('Original @entity', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
102         'description' => t('The original @entity data if the @entity is being updated or saved.', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
103         'module' => 'token',
104         'type' => $token_type,
105       );
106     }
107   }
108
109   // Add support for custom date formats.
110   // @todo Remove when http://drupal.org/node/1173706 is fixed.
111   $date_format_types = \Drupal::entityTypeManager()->getStorage('date_format')->loadMultiple();
112   foreach ($date_format_types as $date_format_type => $date_format_type_info) {
113     /* @var \Drupal\system\Entity\DateFormat $date_format_type_info */
114     if (!isset($info['tokens']['date'][$date_format_type])) {
115       $info['tokens']['date'][$date_format_type] = array(
116         'name' => Html::escape($date_format_type_info->label()),
117         'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => format_date(REQUEST_TIME, $date_format_type))),
118         'module' => 'token',
119       );
120     }
121   }
122 }
123
124 /**
125  * Implements hook_token_info().
126  */
127 function token_token_info() {
128   // Node tokens.
129   $info['tokens']['node']['source'] = array(
130     'name' => t('Translation source node'),
131     'description' => t("The source node for this current node's translation set."),
132     'type' => 'node',
133   );
134   $info['tokens']['node']['log'] = array(
135     'name' => t('Revision log message'),
136     'description' => t('The explanation of the most recent changes made to the node.'),
137   );
138   $info['tokens']['node']['content-type'] = array(
139     'name' => t('Content type'),
140     'description' => t('The content type of the node.'),
141     'type' => 'content-type',
142   );
143
144   // Content type tokens.
145   $info['types']['content-type'] = array(
146     'name' => t('Content types'),
147     'description' => t('Tokens related to content types.'),
148     'needs-data' => 'node_type',
149   );
150   $info['tokens']['content-type']['name'] = array(
151     'name' => t('Name'),
152     'description' => t('The name of the content type.'),
153   );
154   $info['tokens']['content-type']['machine-name'] = array(
155     'name' => t('Machine-readable name'),
156     'description' => t('The unique machine-readable name of the content type.'),
157   );
158   $info['tokens']['content-type']['description'] = array(
159     'name' => t('Description'),
160     'description' => t('The optional description of the content type.'),
161   );
162   $info['tokens']['content-type']['node-count'] = array(
163     'name' => t('Node count'),
164     'description' => t('The number of nodes belonging to the content type.'),
165   );
166   $info['tokens']['content-type']['edit-url'] = array(
167     'name' => t('Edit URL'),
168     'description' => t("The URL of the content type's edit page."),
169     // 'type' => 'url',
170   );
171
172   // Taxonomy term and vocabulary tokens.
173   if (\Drupal::moduleHandler()->moduleExists('taxonomy')) {
174     $info['tokens']['term']['edit-url'] = array(
175       'name' => t('Edit URL'),
176       'description' => t("The URL of the taxonomy term's edit page."),
177       // 'type' => 'url',
178     );
179     $info['tokens']['term']['parents'] = array(
180       'name' => t('Parents'),
181       'description' => t("An array of all the term's parents, starting with the root."),
182       'type' => 'array',
183     );
184     $info['tokens']['term']['root'] = array(
185       'name' => t('Root term'),
186       'description' => t("The root term of the taxonomy term."),
187       'type' => 'term',
188     );
189
190     $info['tokens']['vocabulary']['machine-name'] = array(
191       'name' => t('Machine-readable name'),
192       'description' => t('The unique machine-readable name of the vocabulary.'),
193     );
194     $info['tokens']['vocabulary']['edit-url'] = array(
195       'name' => t('Edit URL'),
196       'description' => t("The URL of the vocabulary's edit page."),
197       // 'type' => 'url',
198     );
199   }
200
201   // File tokens.
202   $info['tokens']['file']['basename'] = array(
203     'name' => t('Base name'),
204     'description' => t('The base name of the file.'),
205   );
206   $info['tokens']['file']['extension'] = array(
207     'name' => t('Extension'),
208     'description' => t('The extension of the file.'),
209   );
210   $info['tokens']['file']['size-raw'] = array(
211     'name' => t('File byte size'),
212     'description' => t('The size of the file, in bytes.'),
213   );
214
215   // User tokens.
216   // Add information on the restricted user tokens.
217   $info['tokens']['user']['cancel-url'] = array(
218     'name' => t('Account cancellation URL'),
219     'description' => t('The URL of the confirm delete page for the user account.'),
220     'restricted' => TRUE,
221     // 'type' => 'url',
222   );
223   $info['tokens']['user']['one-time-login-url'] = array(
224     'name' => t('One-time login URL'),
225     'description' => t('The URL of the one-time login page for the user account.'),
226     'restricted' => TRUE,
227     // 'type' => 'url',
228   );
229   $info['tokens']['user']['roles'] = array(
230     'name' => t('Roles'),
231     'description' => t('The user roles associated with the user account.'),
232     'type' => 'array',
233   );
234
235   // Current user tokens.
236   $info['tokens']['current-user']['ip-address'] = array(
237     'name' => t('IP address'),
238     'description' => 'The IP address of the current user.',
239   );
240
241   // Menu link tokens (work regardless if menu module is enabled or not).
242   $info['types']['menu-link'] = array(
243     'name' => t('Menu links'),
244     'description' => t('Tokens related to menu links.'),
245     'needs-data' => 'menu-link',
246   );
247   $info['tokens']['menu-link']['mlid'] = array(
248     'name' => t('Link ID'),
249     'description' => t('The unique ID of the menu link.'),
250   );
251   $info['tokens']['menu-link']['title'] = array(
252     'name' => t('Title'),
253     'description' => t('The title of the menu link.'),
254   );
255   $info['tokens']['menu-link']['url'] = array(
256     'name' => t('URL'),
257     'description' => t('The URL of the menu link.'),
258     'type' => 'url',
259   );
260   $info['tokens']['menu-link']['parent'] = array(
261     'name' => t('Parent'),
262     'description' => t("The menu link's parent."),
263     'type' => 'menu-link',
264   );
265   $info['tokens']['menu-link']['parents'] = array(
266     'name' => t('Parents'),
267     'description' => t("An array of all the menu link's parents, starting with the root."),
268     'type' => 'array',
269   );
270   $info['tokens']['menu-link']['root'] = array(
271     'name' => t('Root'),
272     'description' => t("The menu link's root."),
273     'type' => 'menu-link',
274   );
275
276   // Current page tokens.
277   $info['types']['current-page'] = array(
278     'name' => t('Current page'),
279     'description' => t('Tokens related to the current page request.'),
280   );
281   $info['tokens']['current-page']['title'] = array(
282     'name' => t('Title'),
283     'description' => t('The title of the current page.'),
284   );
285   $info['tokens']['current-page']['url'] = array(
286     'name' => t('URL'),
287     'description' => t('The URL of the current page.'),
288     'type' => 'url',
289   );
290   $info['tokens']['current-page']['page-number'] = array(
291     'name' => t('Page number'),
292     'description' => t('The page number of the current page when viewing paged lists.'),
293   );
294   $info['tokens']['current-page']['query'] = array(
295     'name' => t('Query string value'),
296     'description' => t('The value of a specific query string field of the current page.'),
297     'dynamic' => TRUE,
298   );
299
300   // URL tokens.
301   $info['types']['url'] = array(
302     'name' => t('URL'),
303     'description' => t('Tokens related to URLs.'),
304     'needs-data' => 'path',
305   );
306   $info['tokens']['url']['path'] = array(
307     'name' => t('Path'),
308     'description' => t('The path component of the URL.'),
309   );
310   $info['tokens']['url']['relative'] = array(
311     'name' => t('Relative URL'),
312     'description' => t('The relative URL.'),
313   );
314   $info['tokens']['url']['absolute'] = array(
315     'name' => t('Absolute URL'),
316     'description' => t('The absolute URL.'),
317   );
318   $info['tokens']['url']['brief'] = array(
319     'name' => t('Brief URL'),
320     'description' => t('The URL without the protocol and trailing backslash.'),
321   );
322   $info['tokens']['url']['unaliased'] = array(
323     'name' => t('Unaliased URL'),
324     'description' => t('The unaliased URL.'),
325     'type' => 'url',
326   );
327   $info['tokens']['url']['args'] = array(
328     'name' => t('Arguments'),
329     'description' => t("The specific argument of the current page (e.g. 'arg:1' on the page 'node/1' returns '1')."),
330     'type' => 'array',
331   );
332
333   // Array tokens.
334   $info['types']['array'] = array(
335     'name' => t('Array'),
336     'description' => t('Tokens related to arrays of strings.'),
337     'needs-data' => 'array',
338     'nested' => TRUE,
339   );
340   $info['tokens']['array']['first'] = array(
341     'name' => t('First'),
342     'description' => t('The first element of the array.'),
343   );
344   $info['tokens']['array']['last'] = array(
345     'name' => t('Last'),
346     'description' => t('The last element of the array.'),
347   );
348   $info['tokens']['array']['count'] = array(
349     'name' => t('Count'),
350     'description' => t('The number of elements in the array.'),
351   );
352   $info['tokens']['array']['reversed'] = array(
353     'name' => t('Reversed'),
354     'description' => t('The array reversed.'),
355     'type' => 'array',
356   );
357   $info['tokens']['array']['keys'] = array(
358     'name' => t('Keys'),
359     'description' => t('The array of keys of the array.'),
360     'type' => 'array',
361   );
362   $info['tokens']['array']['join'] = array(
363     'name' => t('Imploded'),
364     'description' => t('The values of the array joined together with a custom string in-between each value.'),
365     'dynamic' => TRUE,
366   );
367   $info['tokens']['array']['value'] = array(
368     'name' => t('Value'),
369     'description' => t('The specific value of the array.'),
370     'dynamic' => TRUE,
371   );
372
373   // Random tokens.
374   $info['types']['random'] = array(
375     'name' => t('Random'),
376     'description' => t('Tokens related to random data.'),
377   );
378   $info['tokens']['random']['number'] = array(
379     'name' => t('Number'),
380     'description' => t('A random number from 0 to @max.', array('@max' => mt_getrandmax())),
381   );
382   $info['tokens']['random']['hash'] = array(
383     'name' => t('Hash'),
384     'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', array('@hash-algos' => implode(', ', hash_algos()))),
385     'dynamic' => TRUE,
386   );
387
388   // Define image_with_image_style token type.
389   if (\Drupal::moduleHandler()->moduleExists('image')) {
390     $info['types']['image_with_image_style'] = [
391       'name' => t('Image with image style'),
392       'needs-data' => 'image_with_image_style',
393       'module' => 'token',
394       'nested' => TRUE,
395     ];
396
397     // Provide tokens for the ImageStyle attributes.
398     $info['tokens']['image_with_image_style']['mimetype'] = [
399       'name' => t('MIME type'),
400       'description' => t('The MIME type (image/png, image/bmp, etc.) of the image.'),
401     ];
402     $info['tokens']['image_with_image_style']['filesize'] = [
403       'name' => t('File size'),
404       'description' => t('The file size of the image.'),
405     ];
406     $info['tokens']['image_with_image_style']['height'] = [
407       'name' => t('Height'),
408       'description' => t('The height the image, in pixels.'),
409     ];
410     $info['tokens']['image_with_image_style']['width'] = [
411       'name' => t('Width'),
412       'description' => t('The width of the image, in pixels.'),
413     ];
414     $info['tokens']['image_with_image_style']['uri'] = [
415       'name' => t('URI'),
416       'description' => t('The URI to the image.'),
417     ];
418     $info['tokens']['image_with_image_style']['url'] = [
419       'name' => t('URL'),
420       'description' => t('The URL to the image.'),
421     ];
422   }
423
424   return $info;
425 }
426
427 /**
428  * Implements hook_tokens().
429  */
430 function token_tokens($type, array $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
431   $replacements = array();
432   $language_manager = \Drupal::languageManager();
433   $url_options = array('absolute' => TRUE);
434   if (isset($options['langcode'])) {
435     $url_options['language'] = $language_manager->getLanguage($options['langcode']);
436     $langcode = $options['langcode'];
437   }
438   else {
439     $langcode = $language_manager->getCurrentLanguage()->getId();
440   }
441
442   // Date tokens.
443   if ($type == 'date') {
444     $date = !empty($data['date']) ? $data['date'] : REQUEST_TIME;
445
446     // @todo Remove when http://drupal.org/node/1173706 is fixed.
447     $date_format_types = \Drupal::entityTypeManager()->getStorage('date_format')->loadMultiple();
448     foreach ($tokens as $name => $original) {
449       if (isset($date_format_types[$name]) && _token_module('date', $name) == 'token') {
450         $replacements[$original] = format_date($date, $name, '', NULL, $langcode);
451       }
452     }
453   }
454
455   // Current date tokens.
456   // @todo Remove when http://drupal.org/node/943028 is fixed.
457   if ($type == 'current-date') {
458     $replacements += \Drupal::token()->generate('date', $tokens, array('date' => REQUEST_TIME), $options, $bubbleable_metadata);
459   }
460
461   // Comment tokens.
462   if ($type == 'comment' && !empty($data['comment'])) {
463     /* @var \Drupal\comment\CommentInterface $comment */
464     $comment = $data['comment'];
465
466     // Chained token relationships.
467     if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
468       // Add fragment to url options.
469       $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $comment->urlInfo('canonical', ['fragment' => "comment-{$comment->id()}"])), $options, $bubbleable_metadata);
470     }
471   }
472
473   // Node tokens.
474   if ($type == 'node' && !empty($data['node'])) {
475     /* @var \Drupal\node\NodeInterface $node */
476     $node = $data['node'];
477
478     foreach ($tokens as $name => $original) {
479       switch ($name) {
480         case 'log':
481           $replacements[$original] = (string) $node->revision_log->value;
482           break;
483         case 'content-type':
484           $type_name = \Drupal::entityTypeManager()->getStorage('node_type')->load($node->getType())->label();
485           $replacements[$original] = $type_name;
486           break;
487       }
488     }
489
490     // Chained token relationships.
491     if (($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'source')) && $source_node = $node->getUntranslated()) {
492       $replacements += \Drupal::token()->generate('node', $parent_tokens, array('node' => $source_node), $options, $bubbleable_metadata);
493     }
494     if (($node_type_tokens = \Drupal::token()->findWithPrefix($tokens, 'content-type')) && $node_type = node_type_load($node->bundle())) {
495       $replacements += \Drupal::token()->generate('content-type', $node_type_tokens, array('node_type' => $node_type), $options, $bubbleable_metadata);
496     }
497     if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
498       $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $node->urlInfo()), $options, $bubbleable_metadata);
499     }
500   }
501
502   // Content type tokens.
503   if ($type == 'content-type' && !empty($data['node_type'])) {
504     /* @var \Drupal\node\NodeTypeInterface $node_type */
505     $node_type = $data['node_type'];
506
507     foreach ($tokens as $name => $original) {
508       switch ($name) {
509         case 'name':
510           $replacements[$original] = $node_type->label();
511           break;
512         case 'machine-name':
513           $replacements[$original] = $node_type->id();
514           break;
515         case 'description':
516           $replacements[$original] = $node_type->getDescription();
517           break;
518         case 'node-count':
519           $count = \Drupal::entityQueryAggregate('node')
520             ->aggregate('nid', 'COUNT')
521             ->condition('type', $node_type->id())
522             ->execute();
523           $replacements[$original] = (int) $count;
524           break;
525         case 'edit-url':
526           $replacements[$original] = $node_type->url('edit-form', $url_options);
527           break;
528       }
529     }
530   }
531
532   // Taxonomy term tokens.
533   if ($type == 'term' && !empty($data['term'])) {
534     /* @var \Drupal\taxonomy\TermInterface $term */
535     $term = $data['term'];
536
537     /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
538     $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
539
540     foreach ($tokens as $name => $original) {
541       switch ($name) {
542         case 'edit-url':
543           $replacements[$original] = Url::fromRoute('entity.taxonomy_term.edit_form', ['taxonomy_term' => $term->id()], $url_options)->toString();
544           break;
545
546         case 'parents':
547           if ($parents = token_taxonomy_term_load_all_parents($term->id(), $langcode)) {
548             $replacements[$original] = token_render_array($parents, $options);
549           }
550           break;
551
552         case 'root':
553           $parents = $term_storage->loadAllParents($term->id());
554           $root_term = end($parents);
555           if ($root_term->id() != $term->id()) {
556             $replacements[$original] = $root_term->label();
557           }
558           break;
559       }
560     }
561
562     // Chained token relationships.
563     if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
564       $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $term->urlInfo()), $options, $bubbleable_metadata);
565     }
566     // [term:parents:*] chained tokens.
567     if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
568       if ($parents = token_taxonomy_term_load_all_parents($term->id(), $langcode)) {
569         $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options, $bubbleable_metadata);
570       }
571     }
572     if ($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
573       $parents = $term_storage->loadAllParents($term->id());
574       $root_term = end($parents);
575       if ($root_term->tid != $term->id()) {
576         $replacements += \Drupal::token()->generate('term', $root_tokens, array('term' => $root_term), $options, $bubbleable_metadata);
577       }
578     }
579   }
580
581   // Vocabulary tokens.
582   if ($type == 'vocabulary' && !empty($data['vocabulary'])) {
583     $vocabulary = $data['vocabulary'];
584
585     foreach ($tokens as $name => $original) {
586       switch ($name) {
587         case 'machine-name':
588           $replacements[$original] = $vocabulary->id();
589           break;
590         case 'edit-url':
591           $replacements[$original] = Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], $url_options)->toString();
592           break;
593       }
594     }
595   }
596
597   // File tokens.
598   if ($type == 'file' && !empty($data['file'])) {
599     $file = $data['file'];
600
601     foreach ($tokens as $name => $original) {
602       switch ($name) {
603         case 'basename':
604           $basename = pathinfo($file->uri->value, PATHINFO_BASENAME);
605           $replacements[$original] = $basename;
606           break;
607         case 'extension':
608           $extension = pathinfo($file->uri->value, PATHINFO_EXTENSION);
609           $replacements[$original] = $extension;
610           break;
611         case 'size-raw':
612           $replacements[$original] = (int) $file->filesize->value;
613           break;
614       }
615     }
616   }
617
618   // User tokens.
619   if ($type == 'user' && !empty($data['user'])) {
620     /* @var \Drupal\user\UserInterface $account */
621     $account = $data['user'];
622
623     foreach ($tokens as $name => $original) {
624       switch ($name) {
625         case 'picture':
626           if ($account instanceof UserInterface && $account->hasField('user_picture')) {
627             /** @var \Drupal\Core\Render\RendererInterface $renderer */
628             $renderer = \Drupal::service('renderer');
629             $output = [
630               '#theme' => 'user_picture',
631               '#account' => $account,
632             ];
633             $replacements[$original] = $renderer->renderPlain($output);
634           }
635           break;
636
637         case 'roles':
638           $roles = $account->getRoles();
639           $roles_names = array_combine($roles, $roles);
640           $replacements[$original] = token_render_array($roles_names, $options);
641           break;
642       }
643     }
644
645     // Chained token relationships.
646     if ($account instanceof UserInterface && $account->hasField('user_picture') && ($picture_tokens = \Drupal::token()->findWithPrefix($tokens, 'picture'))) {
647       $replacements += \Drupal::token()->generate('file', $picture_tokens, array('file' => $account->user_picture->entity), $options, $bubbleable_metadata);
648     }
649     if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
650       $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $account->urlInfo()), $options, $bubbleable_metadata);
651     }
652     if ($role_tokens = \Drupal::token()->findWithPrefix($tokens, 'roles')) {
653       $roles = $account->getRoles();
654       $roles_names = array_combine($roles, $roles);
655       $replacements += \Drupal::token()->generate('array', $role_tokens, array('array' => $roles_names), $options, $bubbleable_metadata);
656     }
657   }
658
659   // Current user tokens.
660   if ($type == 'current-user') {
661     foreach ($tokens as $name => $original) {
662       switch ($name) {
663         case 'ip-address':
664           $ip = \Drupal::request()->getClientIp();
665           $replacements[$original] = $ip;
666           break;
667       }
668     }
669   }
670
671   // Menu link tokens.
672   if ($type == 'menu-link' && !empty($data['menu-link'])) {
673     /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
674     $link = $data['menu-link'];
675     /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
676     $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
677
678     if ($link instanceof MenuLinkContentInterface) {
679       $link = $menu_link_manager->createInstance($link->getPluginId());
680     }
681
682     foreach ($tokens as $name => $original) {
683       switch ($name) {
684         case 'id':
685           $replacements[$original] = $link->getPluginId();
686           break;
687         case 'title':
688           $replacements[$original] = token_menu_link_translated_title($link, $langcode);
689           break;
690         case 'url':
691           $replacements[$original] = $link->getUrlObject()->setAbsolute()->toString();
692           break;
693         case 'parent':
694
695           /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
696           if ($link->getParent() && $parent = $menu_link_manager->createInstance($link->getParent())) {
697             $replacements[$original] = token_menu_link_translated_title($parent, $langcode);
698           }
699           break;
700         case 'parents':
701           if ($parents = token_menu_link_load_all_parents($link->getPluginId(), $langcode)) {
702             $replacements[$original] = token_render_array($parents, $options);
703           }
704           break;
705         case 'root';
706           if ($link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId(), $langcode))) {
707             $root = $menu_link_manager->createInstance(array_shift($parent_ids));
708             $replacements[$original] = token_menu_link_translated_title($root, $langcode);
709           }
710           break;
711       }
712     }
713
714     // Chained token relationships.
715     /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
716     if ($link->getParent() && ($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) && $parent = $menu_link_manager->createInstance($link->getParent())) {
717       $replacements += \Drupal::token()->generate('menu-link', $parent_tokens, array('menu-link' => $parent), $options, $bubbleable_metadata);
718     }
719     // [menu-link:parents:*] chained tokens.
720     if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
721       if ($parents = token_menu_link_load_all_parents($link->getPluginId(), $langcode)) {
722         $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options, $bubbleable_metadata);
723       }
724     }
725     if (($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) && $link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId(), $langcode))) {
726       $root = $menu_link_manager->createInstance(array_shift($parent_ids));
727       $replacements += \Drupal::token()->generate('menu-link', $root_tokens, array('menu-link' => $root), $options, $bubbleable_metadata);
728     }
729     if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
730       $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $link->getUrlObject()), $options, $bubbleable_metadata);
731     }
732
733   }
734
735   // Current page tokens.
736   if ($type == 'current-page') {
737     foreach ($tokens as $name => $original) {
738       switch ($name) {
739         case 'title':
740           $request = \Drupal::request();
741           $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
742           if ($route) {
743             $title = \Drupal::service('title_resolver')->getTitle($request, $route);
744             $replacements[$original] = token_render_array_value($title);
745           }
746           break;
747         case 'url':
748           $replacements[$original] = Url::fromRoute('<current>', [], $url_options)->toString();
749           break;
750         case 'page-number':
751           if ($page = \Drupal::request()->query->get('page')) {
752             // @see PagerDefault::execute()
753             $pager_page_array = explode(',', $page);
754             $page = $pager_page_array[0];
755           }
756           $replacements[$original] = (int) $page + 1;
757           break;
758       }
759     }
760
761     // @deprecated
762     // [current-page:arg] dynamic tokens.
763     if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'arg')) {
764       $path = ltrim(\Drupal::service('path.current')->getPath(), '/');
765       // Make sure its a system path.
766       $path = \Drupal::service('path.alias_manager')->getPathByAlias($path);
767       foreach ($arg_tokens as $name => $original) {
768         $parts = explode('/', $path);
769         if (is_numeric($name) && isset($parts[$name])) {
770           $replacements[$original] = $parts[$name];
771         }
772       }
773     }
774
775     // [current-page:query] dynamic tokens.
776     if ($query_tokens = \Drupal::token()->findWithPrefix($tokens, 'query')) {
777       foreach ($query_tokens as $name => $original) {
778         if (\Drupal::request()->query->has($name)) {
779           $value = \Drupal::request()->query->get($name);
780           $replacements[$original] = $value;
781         }
782       }
783     }
784
785     // Chained token relationships.
786     if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
787       $url = Url::fromRoute('<current>');
788       $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $url), $options, $bubbleable_metadata);
789     }
790   }
791
792   // URL tokens.
793   if ($type == 'url' && !empty($data['url'])) {
794     /** @var \Drupal\Core\Url $url */
795     $url = $data['url'];
796     // To retrieve the correct path, modify a copy of the Url object.
797     $path_url = clone $url;
798     $path = '/' . $path_url->setAbsolute(FALSE)->setOption('fragment', NULL)->getInternalPath();
799
800     foreach ($tokens as $name => $original) {
801       switch ($name) {
802         case 'path':
803           $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
804           $replacements[$original] = $value;
805           break;
806         case 'alias':
807           // @deprecated
808           $alias = \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode);
809           $replacements[$original] = $alias;
810           break;
811         case 'absolute':
812           $replacements[$original] = $url->setAbsolute()->toString();
813           break;
814         case 'relative':
815           $replacements[$original] = $url->setAbsolute(FALSE)->toString();
816           break;
817         case 'brief':
818           $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', $url->setAbsolute()->toString());
819           break;
820         case 'unaliased':
821           $unaliased = clone $url;
822           $replacements[$original] = $unaliased->setAbsolute()->setOption('alias', TRUE)->toString();
823           break;
824         case 'args':
825           $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
826           $replacements[$original] = token_render_array(explode('/', $value), $options);
827           break;
828
829       }
830     }
831
832     // [url:args:*] chained tokens.
833     if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'args')) {
834       $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
835       $replacements += \Drupal::token()->generate('array', $arg_tokens, array('array' => explode('/', ltrim($value, '/'))), $options, $bubbleable_metadata);
836     }
837
838     // [url:unaliased:*] chained tokens.
839     if ($unaliased_tokens = \Drupal::token()->findWithPrefix($tokens, 'unaliased')) {
840       $url->setOption('alias', TRUE);
841       $replacements += \Drupal::token()->generate('url', $unaliased_tokens, array('url' => $url), $options, $bubbleable_metadata);
842     }
843   }
844
845   // Entity tokens.
846   if (!empty($data[$type]) && $entity_type = \Drupal::service('token.entity_mapper')->getEntityTypeForTokenType($type)) {
847     /* @var \Drupal\Core\Entity\EntityInterface $entity */
848     $entity = $data[$type];
849
850     foreach ($tokens as $name => $original) {
851       switch ($name) {
852         case 'url':
853           if (_token_module($type, 'url') == 'token' && $url = $entity->url()) {
854             $replacements[$original] = $url;
855           }
856           break;
857
858         case 'original':
859           if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
860             $label = $entity->original->label();
861             $replacements[$original] = $label;
862           }
863           break;
864       }
865     }
866
867     // [entity:url:*] chained tokens.
868     if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
869       $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $entity->toUrl()), $options, $bubbleable_metadata);
870     }
871
872     // [entity:original:*] chained tokens.
873     if (($original_tokens = \Drupal::token()->findWithPrefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
874       $replacements += \Drupal::token()->generate($type, $original_tokens, array($type => $entity->original), $options, $bubbleable_metadata);
875     }
876
877     // Pass through to an generic 'entity' token type generation.
878     $entity_data = array(
879       'entity_type' => $entity_type,
880       'entity' => $entity,
881       'token_type' => $type,
882     );
883     // @todo Investigate passing through more data like everything from entity_extract_ids().
884     $replacements += \Drupal::token()->generate('entity', $tokens, $entity_data, $options, $bubbleable_metadata);
885   }
886
887   // Array tokens.
888   if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
889     $array = $data['array'];
890
891     $sort = isset($options['array sort']) ? $options['array sort'] : TRUE;
892     $keys = token_element_children($array, $sort);
893
894     /** @var \Drupal\Core\Render\RendererInterface $renderer */
895     $renderer = \Drupal::service('renderer');
896
897     foreach ($tokens as $name => $original) {
898       switch ($name) {
899         case 'first':
900           $value = $array[$keys[0]];
901           $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
902           $replacements[$original] = $value;
903           break;
904         case 'last':
905           $value = $array[$keys[count($keys) - 1]];
906           $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
907           $replacements[$original] =$value;
908           break;
909         case 'count':
910           $replacements[$original] = count($keys);
911           break;
912         case 'keys':
913           $replacements[$original] = token_render_array($keys, $options);
914           break;
915         case 'reversed':
916           $reversed = array_reverse($array, TRUE);
917           $replacements[$original] = token_render_array($reversed, $options);
918           break;
919         case 'join':
920           $replacements[$original] = token_render_array($array, array('join' => '') + $options);
921           break;
922       }
923     }
924
925     // [array:value:*] dynamic tokens.
926     if ($value_tokens = \Drupal::token()->findWithPrefix($tokens, 'value')) {
927       foreach ($value_tokens as $key => $original) {
928         if ($key[0] !== '#' && isset($array[$key])) {
929           $replacements[$original] = token_render_array_value($array[$key], $options);
930         }
931       }
932     }
933
934     // [array:join:*] dynamic tokens.
935     if ($join_tokens = \Drupal::token()->findWithPrefix($tokens, 'join')) {
936       foreach ($join_tokens as $join => $original) {
937         $replacements[$original] = token_render_array($array, array('join' => $join) + $options);
938       }
939     }
940
941     // [array:keys:*] chained tokens.
942     if ($key_tokens = \Drupal::token()->findWithPrefix($tokens, 'keys')) {
943       $replacements += \Drupal::token()->generate('array', $key_tokens, array('array' => $keys), $options, $bubbleable_metadata);
944     }
945
946     // [array:reversed:*] chained tokens.
947     if ($reversed_tokens = \Drupal::token()->findWithPrefix($tokens, 'reversed')) {
948       $replacements += \Drupal::token()->generate('array', $reversed_tokens, array('array' => array_reverse($array, TRUE)), array('array sort' => FALSE) + $options, $bubbleable_metadata);
949     }
950
951     // @todo Handle if the array values are not strings and could be chained.
952   }
953
954   // Random tokens.
955   if ($type == 'random') {
956     foreach ($tokens as $name => $original) {
957       switch ($name) {
958         case 'number':
959           $replacements[$original] = mt_rand();
960           break;
961       }
962     }
963
964     // [custom:hash:*] dynamic token.
965     if ($hash_tokens = \Drupal::token()->findWithPrefix($tokens, 'hash')) {
966       $algos = hash_algos();
967       foreach ($hash_tokens as $name => $original) {
968         if (in_array($name, $algos)) {
969           $replacements[$original] = hash($name, Crypt::randomBytes(55));
970         }
971       }
972     }
973   }
974
975   // If $type is a token type, $data[$type] is empty but $data[$entity_type] is
976   // not, re-run token replacements.
977   if (empty($data[$type]) && ($entity_type = \Drupal::service('token.entity_mapper')->getEntityTypeForTokenType($type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
978     $data[$type] = $data[$entity_type];
979     $options['recursive'] = TRUE;
980     $replacements += \Drupal::moduleHandler()->invokeAll('tokens', array($type, $tokens, $data, $options, $bubbleable_metadata));
981   }
982
983   // If the token type specifics a 'needs-data' value, and the value is not
984   // present in $data, then throw an error.
985   if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
986     // Only check when tests are running.
987     $type_info = \Drupal::token()->getTypeInfo($type);
988     if (!empty($type_info['needs-data']) && !isset($data[$type_info['needs-data']])) {
989       trigger_error(t('Attempting to perform token replacement for token type %type without required data', array('%type' => $type)), E_USER_WARNING);
990     }
991   }
992
993   return $replacements;
994 }
995
996 /**
997  * Implements hook_token_info() on behalf of book.module.
998  */
999 function book_token_info() {
1000   $info['types']['book'] = array(
1001     'name' => t('Book'),
1002     'description' => t('Tokens related to books.'),
1003     'needs-data' => 'book',
1004   );
1005
1006   $info['tokens']['book']['title'] = array(
1007     'name' => t('Title'),
1008     'description' => t('Title of the book.'),
1009   );
1010   $info['tokens']['book']['author'] = array(
1011     'name' => t('Author'),
1012     'description' => t('The author of the book.'),
1013     'type' => 'user',
1014   );
1015   $info['tokens']['book']['root'] = array(
1016     'name' => t('Root'),
1017     'description' => t('Top level of the book.'),
1018     'type' => 'node',
1019   );
1020   $info['tokens']['book']['parent'] = array(
1021     'name' => t('Parent'),
1022     'description' => t('Parent of the current page.'),
1023     'type' => 'node',
1024   );
1025   $info['tokens']['book']['parents'] = array(
1026     'name' => t('Parents'),
1027     'description' => t("An array of all the node's parents, starting with the root."),
1028     'type' => 'array',
1029   );
1030
1031   $info['tokens']['node']['book'] = array(
1032     'name' => t('Book'),
1033     'description' => t('The book page associated with the node.'),
1034     'type' => 'book',
1035   );
1036   return $info;
1037 }
1038
1039 /**
1040  * Implements hook_tokens() on behalf of book.module.
1041  */
1042 function book_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
1043   $replacements = array();
1044
1045   // Node tokens.
1046   if ($type == 'node' && !empty($data['node'])) {
1047     $book = $data['node']->book;
1048
1049     if (!empty($book['bid'])) {
1050       if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'book')) {
1051         $child_node = Node::load($book['nid']);
1052         $replacements += \Drupal::token()->generate('book', $book_tokens, array('book' => $child_node), $options, $bubbleable_metadata);
1053       }
1054     }
1055   }
1056   // Book tokens.
1057   else if ($type == 'book' && !empty($data['book'])) {
1058     $book = $data['book']->book;
1059
1060     if (!empty($book['bid'])) {
1061       $book_node = Node::load($book['bid']);
1062
1063       foreach ($tokens as $name => $original) {
1064         switch ($name) {
1065           case 'root':
1066           case 'title':
1067             $replacements[$original] = $book_node->getTitle();
1068             break;
1069           case 'parent':
1070             if (!empty($book['pid'])) {
1071               $parent_node = Node::load($book['pid']);
1072               $replacements[$original] = $parent_node->getTitle();
1073             }
1074             break;
1075           case 'parents':
1076             if ($parents = token_book_load_all_parents($book)) {
1077               $replacements[$original] = token_render_array($parents, $options);
1078             }
1079             break;
1080         }
1081       }
1082
1083       if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'author')) {
1084         $replacements += \Drupal::token()->generate('user', $book_tokens, array('user' => $book_node->getOwner()), $options, $bubbleable_metadata);
1085       }
1086       if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
1087         $replacements += \Drupal::token()->generate('node', $book_tokens, array('node' => $book_node), $options, $bubbleable_metadata);
1088       }
1089       if (!empty($book['pid']) && $book_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) {
1090         $parent_node = Node::load($book['pid']);
1091         $replacements += \Drupal::token()->generate('node', $book_tokens, array('node' => $parent_node), $options, $bubbleable_metadata);
1092       }
1093       if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
1094         $parents = token_book_load_all_parents($book);
1095         $replacements += \Drupal::token()->generate('array', $book_tokens, array('array' => $parents), $options, $bubbleable_metadata);
1096       }
1097     }
1098   }
1099
1100   return $replacements;
1101 }
1102
1103 /**
1104  * Implements hook_token_info() on behalf of menu_ui.module.
1105  */
1106 function menu_ui_token_info() {
1107   // Menu tokens.
1108   $info['types']['menu'] = array(
1109     'name' => t('Menus'),
1110     'description' => t('Tokens related to menus.'),
1111     'needs-data' => 'menu',
1112   );
1113   $info['tokens']['menu']['name'] = array(
1114     'name' => t('Name'),
1115     'description' => t("The name of the menu."),
1116   );
1117   $info['tokens']['menu']['machine-name'] = array(
1118     'name' => t('Machine-readable name'),
1119     'description' => t("The unique machine-readable name of the menu."),
1120   );
1121   $info['tokens']['menu']['description'] = array(
1122     'name' => t('Description'),
1123     'description' => t('The optional description of the menu.'),
1124   );
1125   $info['tokens']['menu']['menu-link-count'] = array(
1126     'name' => t('Menu link count'),
1127     'description' => t('The number of menu links belonging to the menu.'),
1128   );
1129   $info['tokens']['menu']['edit-url'] = array(
1130     'name' => t('Edit URL'),
1131     'description' => t("The URL of the menu's edit page."),
1132   );
1133
1134   $info['tokens']['menu-link']['menu'] = array(
1135     'name' => t('Menu'),
1136     'description' => t('The menu of the menu link.'),
1137     'type' => 'menu',
1138   );
1139   $info['tokens']['menu-link']['edit-url'] = array(
1140     'name' => t('Edit URL'),
1141     'description' => t("The URL of the menu link's edit page."),
1142   );
1143   $info['tokens']['node']['menu-link'] = array(
1144     'name' => t('Menu link'),
1145     'description' => t("The menu link for this node."),
1146     'type' => 'menu-link',
1147   );
1148
1149   return $info;
1150 }
1151
1152 /**
1153  * Implements hook_tokens() on behalf of menu_ui.module.
1154  */
1155 function menu_ui_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
1156   $replacements = array();
1157
1158   /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
1159   $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
1160
1161   $url_options = array('absolute' => TRUE);
1162   if (isset($options['langcode'])) {
1163     $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
1164     $langcode = $options['langcode'];
1165   }
1166   else {
1167     $langcode = NULL;
1168   }
1169
1170   // Node tokens.
1171   if ($type == 'node' && !empty($data['node'])) {
1172     /** @var \Drupal\node\NodeInterface $node */
1173     $node = $data['node'];
1174
1175     foreach ($tokens as $name => $original) {
1176       switch ($name) {
1177         case 'menu-link':
1178           // On node-form save we populate a calculated field with a menu_link
1179           // references.
1180           // @see token_node_menu_link_submit()
1181           if ($node->getFieldDefinition('menu_link') && $menu_link = $node->menu_link->entity) {
1182             /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
1183             $replacements[$original] = $menu_link->getTitle();
1184           }
1185           else {
1186             $url = $node->toUrl();
1187             if ($links = $menu_link_manager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters())) {
1188               $link = _token_menu_link_best_match($node, $links);
1189               $replacements[$original] = token_menu_link_translated_title($link, $langcode);
1190             }
1191           }
1192           break;
1193       }
1194
1195       // Chained token relationships.
1196       if ($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu-link')) {
1197         if ($node->getFieldDefinition('menu_link') && $menu_link = $node->menu_link->entity) {
1198           /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
1199           $replacements += \Drupal::token()->generate('menu-link', $menu_tokens, array('menu-link' => $menu_link), $options, $bubbleable_metadata);
1200         }
1201         else {
1202           $url = $node->urlInfo();
1203           if ($links = $menu_link_manager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters())) {
1204             $link = _token_menu_link_best_match($node, $links);
1205             $replacements += \Drupal::token()->generate('menu-link', $menu_tokens, array('menu-link' => $link), $options, $bubbleable_metadata);
1206           }
1207         }
1208       }
1209     }
1210   }
1211
1212   // Menu link tokens.
1213   if ($type == 'menu-link' && !empty($data['menu-link'])) {
1214     /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
1215     $link = $data['menu-link'];
1216
1217     if ($link instanceof MenuLinkContentInterface) {
1218       $link = $menu_link_manager->createInstance($link->getPluginId());
1219     }
1220
1221     foreach ($tokens as $name => $original) {
1222       switch ($name) {
1223         case 'menu':
1224           if ($menu = Menu::load($link->getMenuName())) {
1225             $replacements[$original] = $menu->label();
1226           }
1227           break;
1228
1229         case 'edit-url':
1230           $replacements[$original] = $link->getEditRoute()->setOptions($url_options)->toString();
1231           break;
1232       }
1233     }
1234
1235     // Chained token relationships.
1236     if (($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu')) && $menu = Menu::load($link->getMenuName())) {
1237       $replacements += \Drupal::token()->generate('menu', $menu_tokens, array('menu' => $menu), $options, $bubbleable_metadata);
1238     }
1239   }
1240
1241   // Menu tokens.
1242   if ($type == 'menu' && !empty($data['menu'])) {
1243     /** @var \Drupal\system\MenuInterface $menu */
1244     $menu = $data['menu'];
1245
1246     foreach ($tokens as $name => $original) {
1247       switch ($name) {
1248         case 'name':
1249           $replacements[$original] = $menu->label();
1250           break;
1251
1252         case 'machine-name':
1253           $replacements[$original] = $menu->id();
1254           break;
1255
1256         case 'description':
1257           $replacements[$original] = $menu->getDescription();
1258           break;
1259
1260         case 'menu-link-count':
1261           $replacements[$original] = $menu_link_manager->countMenuLinks($menu->id());
1262           break;
1263
1264         case 'edit-url':
1265           $replacements[$original] = Url::fromRoute('entity.menu.edit_form', ['menu' => $menu->id()], $url_options)->toString();
1266           break;
1267       }
1268     }
1269   }
1270
1271   return $replacements;
1272 }
1273
1274 /**
1275  * Returns a best matched link for a given node.
1276  *
1277  * If the url exists in multiple menus, default to the one set on the node
1278  * itself.
1279  *
1280  * @param \Drupal\node\NodeInterface $node
1281  *   The node to look up the default menu settings from.
1282  * @param array $links
1283  *   An array of instances keyed by plugin ID.
1284  *
1285  * @return \Drupal\Core\Menu\MenuLinkInterface
1286  *   A Link instance.
1287  */
1288 function _token_menu_link_best_match(NodeInterface $node, array $links) {
1289   // Get the menu ui defaults so we can determine what menu was
1290   // selected for this node. This ensures that if the node was added
1291   // to the menu via the node UI, we use that as a default. If it
1292   // was not added via the node UI then grab the first in the
1293   // retrieved array.
1294   $defaults = menu_ui_get_menu_link_defaults($node);
1295   if (isset($defaults['id']) && isset($links[$defaults['id']])) {
1296     $link = $links[$defaults['id']];
1297   }
1298   else {
1299     $link = reset($links);
1300   }
1301   return $link;
1302 }
1303
1304 /**
1305  * Implements hook_token_info_alter() on behalf of field.module.
1306  *
1307  * We use hook_token_info_alter() rather than hook_token_info() as other
1308  * modules may already have defined some field tokens.
1309  */
1310 function field_token_info_alter(&$info) {
1311   $type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
1312
1313   // Attach field tokens to their respecitve entity tokens.
1314   foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
1315     if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
1316       continue;
1317     }
1318
1319     // Make sure a token type exists for this entity.
1320     $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id);
1321     if (empty($token_type)) {
1322       continue;
1323     }
1324
1325     $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
1326     foreach ($fields as $field_name => $field) {
1327       /** @var \Drupal\field\FieldStorageConfigInterface $field */
1328       // Ensure the token type exists.
1329       if (!isset($info['types'][$token_type])) {
1330         continue;
1331       }
1332
1333       // Ensure the token implements FieldStorageConfigInterface or is defined
1334       // in token module.
1335       $provider = '';
1336       if (isset($info['types'][$token_type]['module'])) {
1337         $provider = $info['types'][$token_type]['module'];
1338       }
1339       if (!($field instanceof FieldStorageConfigInterface) && $provider != 'token') {
1340         continue;
1341       }
1342
1343       // If a token already exists for this field, then don't add it.
1344       if (isset($info['tokens'][$token_type][$field_name])) {
1345         continue;
1346       }
1347
1348       if ($token_type == 'comment' && $field_name == 'comment_body') {
1349         // Core provides the comment field as [comment:body].
1350         continue;
1351       }
1352
1353       // Do not define the token type if the field has no properties.
1354       if (!$field->getPropertyDefinitions()) {
1355         continue;
1356       }
1357
1358       // Generate a description for the token.
1359       $labels = _token_field_label($entity_type_id, $field_name);
1360       $label = array_shift($labels);
1361       $params['@type'] = $type_info[$field->getType()]['label'];
1362       if (!empty($labels)) {
1363         $params['%labels'] = implode(', ', $labels);
1364         $description = t('@type field. Also known as %labels.', $params);
1365       }
1366       else {
1367         $description = t('@type field.', $params);
1368       }
1369
1370       $cardinality = $field->getCardinality();
1371       $cardinality = ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $cardinality > 3) ? 3 : $cardinality;
1372       $field_token_name = $token_type . '-' . $field_name;
1373       $info['tokens'][$token_type][$field_name] = array(
1374         'name' => Html::escape($label),
1375         'description' => $description,
1376         'module' => 'token',
1377         // For multivalue fields the field token is a list type.
1378         'type' => $cardinality > 1 ? "list<$field_token_name>" : $field_token_name,
1379       );
1380
1381       // Field token type.
1382       $info['types'][$field_token_name] = [
1383         'name' => Html::escape($label),
1384         'description' => t('@label tokens.', ['@label' => Html::escape($label)]),
1385         'needs-data' => $field_token_name,
1386         'nested' => TRUE,
1387       ];
1388       // Field list token type.
1389       if ($cardinality > 1) {
1390         $info['types']["list<$field_token_name>"] = array(
1391           'name' => t('List of @type values', array('@type' => Html::escape($label))),
1392           'description' => t('Tokens for lists of @type values.', array('@type' => Html::escape($label))),
1393           'needs-data' => "list<$field_token_name>",
1394           'nested' => TRUE,
1395         );
1396       }
1397
1398       // Show a different token for each field delta.
1399       if ($cardinality > 1) {
1400         for ($delta = 0; $delta < $cardinality; $delta++) {
1401           $info['tokens']["list<$field_token_name>"][$delta] = [
1402             'name' => t('@type type with delta @delta', ['@type' => Html::escape($label), '@delta' => $delta]),
1403             'module' => 'token',
1404             'type' => $field_token_name,
1405           ];
1406         }
1407       }
1408
1409       // Property tokens.
1410       foreach ($field->getPropertyDefinitions() as $property => $property_definition) {
1411         if (is_subclass_of($property_definition->getClass(), 'Drupal\Core\TypedData\PrimitiveInterface')) {
1412           $info['tokens'][$field_token_name][$property] = [
1413             'name' => $property_definition->getLabel(),
1414             'description' => $property_definition->getDescription(),
1415             'module' => 'token',
1416           ];
1417         }
1418         elseif (($property_definition instanceof DataReferenceDefinitionInterface) && ($property_definition->getTargetDefinition() instanceof EntityDataDefinitionInterface)) {
1419           $referenced_entity_type = $property_definition->getTargetDefinition()->getEntityTypeId();
1420           $referenced_token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($referenced_entity_type);
1421           $info['tokens'][$field_token_name][$property] = [
1422             'name' => $property_definition->getLabel(),
1423             'description' => $property_definition->getDescription(),
1424             'module' => 'token',
1425             'type' => $referenced_token_type,
1426           ];
1427         }
1428       }
1429       // Provide image_with_image_style tokens for image fields.
1430       if ($field->getType() == 'image') {
1431         $image_styles = image_style_options(FALSE);
1432         foreach ($image_styles as $style => $description) {
1433           $info['tokens'][$field_token_name][$style] = [
1434             'name' => $description,
1435             'description' => t('Represents the image in the given image style.'),
1436             'type' => 'image_with_image_style',
1437           ];
1438         }
1439       }
1440       // Provide format token for datetime fields.
1441       if ($field->getType() == 'datetime') {
1442         $info['tokens'][$field_token_name]['date'] = $info['tokens'][$field_token_name]['value'];
1443         $info['tokens'][$field_token_name]['date']['name'] .= ' ' . t('format');
1444         $info['tokens'][$field_token_name]['date']['type'] = 'date';
1445       }
1446       if ($field->getType() == 'daterange') {
1447         $info['tokens'][$field_token_name]['start_date'] = $info['tokens'][$field_token_name]['value'];
1448         $info['tokens'][$field_token_name]['start_date']['name'] .= ' ' . t('format');
1449         $info['tokens'][$field_token_name]['start_date']['type'] = 'date';
1450         $info['tokens'][$field_token_name]['end_date'] = $info['tokens'][$field_token_name]['end_value'];
1451         $info['tokens'][$field_token_name]['end_date']['name'] .= ' ' . t('format');
1452         $info['tokens'][$field_token_name]['end_date']['type'] = 'date';
1453       }
1454     }
1455   }
1456 }
1457
1458 /**
1459  * Returns the label of a certain field.
1460  *
1461  * Therefore it looks up in all bundles to find the most used instance.
1462  *
1463  * Based on views_entity_field_label().
1464  *
1465  * @todo Resync this method with views_entity_field_label().
1466  */
1467 function _token_field_label($entity_type, $field_name) {
1468   $labels = [];
1469   // Count the amount of instances per label per field.
1470   foreach (array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type)) as $bundle) {
1471     $bundle_instances = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);
1472     if (isset($bundle_instances[$field_name])) {
1473       $instance = $bundle_instances[$field_name];
1474       $label = (string) $instance->getLabel();
1475       $labels[$label] = isset($labels[$label]) ? ++$labels[$label] : 1;
1476     }
1477   }
1478
1479   if (empty($labels)) {
1480     return [$field_name];
1481   }
1482
1483   // Sort the field labels by it most used label and return the labels.
1484   arsort($labels);
1485   return array_keys($labels);
1486 }
1487
1488 /**
1489  * Implements hook_tokens() on behalf of field.module.
1490  */
1491 function field_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
1492   $replacements = array();
1493   $langcode = isset($options['langcode']) ? $options['langcode'] : NULL;
1494   // Entity tokens.
1495   if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {
1496     /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
1497     $entity = $data['entity'];
1498     if (!($entity instanceof ContentEntityInterface)) {
1499       return $replacements;
1500     }
1501
1502     if (!isset($options['langcode'])) {
1503       // Set the active language in $options, so that it is passed along.
1504       $langcode = $options['langcode'] = $entity->language()->getId();
1505     }
1506     // Obtain the entity with the correct language.
1507     $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
1508
1509     $view_mode_name = $entity->getEntityTypeId() . '.' . $entity->bundle() . '.token';
1510     $view_display = \Drupal::entityTypeManager()->getStorage('entity_view_display')->load($view_mode_name);
1511     $token_view_display = (!empty($view_display) && $view_display->status());
1512     foreach ($tokens as $name => $original) {
1513       // For the [entity:field_name] token.
1514       if (strpos($name, ':') === FALSE) {
1515         $field_name = $name;
1516         $token_name = $name;
1517       }
1518       // For [entity:field_name:0], [entity:field_name:0:value] and
1519       // [entity:field_name:value] tokens.
1520       else {
1521         list($field_name, $delta) = explode(':', $name, 2);
1522         if (!is_numeric($delta)) {
1523           unset($delta);
1524         }
1525         $token_name = $field_name;
1526       }
1527       // Ensure the entity has the requested field and that the token for it is
1528       // defined by token.module.
1529       if (!$entity->hasField($field_name) || _token_module($data['token_type'], $token_name) != 'token') {
1530         continue;
1531       }
1532
1533       $display_options = 'token';
1534       // Do not continue if the field is empty.
1535       if ($entity->get($field_name)->isEmpty()) {
1536         continue;
1537       }
1538       // Handle [entity:field_name] and [entity:field_name:0] tokens.
1539       if ($field_name === $name || isset($delta)) {
1540         if (!$token_view_display) {
1541           // We don't have the token view display and should fall back on
1542           // default formatters. If the field has specified a specific formatter
1543           // to be used by default with tokens, use that, otherwise use the
1544           // default formatter.
1545           /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
1546           $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
1547           $field_type_definition = $field_type_manager->getDefinition($entity->getFieldDefinition($field_name)->getType());
1548           $display_options = [
1549             'type' => !empty($field_type_definition['default_token_formatter']) ? $field_type_definition['default_token_formatter'] : $field_type_definition['default_formatter'],
1550             'label' => 'hidden',
1551           ];
1552         }
1553
1554         // Render only one delta.
1555         if (isset($delta)) {
1556           if ($field_delta = $entity->{$field_name}[$delta]) {
1557             $field_output = $field_delta->view($display_options);
1558           }
1559           // If no such delta exists, let's not replace the token.
1560           else {
1561             continue;
1562           }
1563         }
1564         // Render the whole field (with all deltas).
1565         else {
1566           $field_output = $entity->$field_name->view($display_options);
1567           // If we are displaying all field items we need this #pre_render
1568           // callback.
1569           $field_output['#pre_render'][] = 'token_pre_render_field_token';
1570         }
1571         $field_output['#token_options'] = $options;
1572         $replacements[$original] = \Drupal::service('renderer')->renderPlain($field_output);
1573       }
1574       // Handle [entity:field_name:value] and [entity:field_name:0:value]
1575       // tokens.
1576       else if ($field_tokens = \Drupal::token()->findWithPrefix($tokens, $field_name)) {
1577         $property_token_data = [
1578           'field_property' => TRUE,
1579           $data['entity_type'] . '-' . $field_name => $entity->$field_name,
1580           'field_name' => $data['entity_type'] . '-' . $field_name,
1581         ];
1582         $replacements += \Drupal::token()->generate($field_name, $field_tokens, $property_token_data, $options, $bubbleable_metadata);
1583       }
1584     }
1585
1586     // Remove the cloned object from memory.
1587     unset($entity);
1588   }
1589   elseif (!empty($data['field_property'])) {
1590     foreach ($tokens as $token => $original) {
1591       $filtered_tokens = $tokens;
1592       $delta = 0;
1593       $parts = explode(':', $token);
1594       if (is_numeric($parts[0])) {
1595         if (count($parts) > 1) {
1596           $delta = $parts[0];
1597           $property_name = $parts[1];
1598           // Pre-filter the tokens to select those with the correct delta.
1599           $filtered_tokens = \Drupal::token()->findWithPrefix($tokens, $delta);
1600           // Remove the delta to unify between having and not having one.
1601           array_shift($parts);
1602         }
1603         else {
1604           // Token is fieldname:delta, which is invalid.
1605           continue;
1606         }
1607       }
1608       else {
1609         $property_name = $parts[0];
1610       }
1611
1612       if (isset($data[$data['field_name']][$delta])) {
1613         $field_item = $data[$data['field_name']][$delta];
1614       }
1615       else {
1616         // The field has no such delta, abort replacement.
1617         continue;
1618       }
1619
1620       if (isset($field_item->$property_name) && ($field_item->$property_name instanceof FieldableEntityInterface)) {
1621         // Entity reference field.
1622         $entity = $field_item->$property_name;
1623         // Obtain the referenced entity with the correct language.
1624         $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
1625
1626         if (count($parts) > 1) {
1627           $field_tokens = \Drupal::token()->findWithPrefix($filtered_tokens, $property_name);
1628           $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity->getEntityTypeId(), TRUE);
1629           $replacements += \Drupal::token()->generate($token_type, $field_tokens, [$token_type => $entity], $options, $bubbleable_metadata);
1630         }
1631         else {
1632           $replacements[$original] = $entity->label();
1633         }
1634       }
1635       elseif (($field_item->getFieldDefinition()->getType() == 'image') && ($style = ImageStyle::load($property_name))) {
1636         // Handle [node:field_name:image_style:property] tokens and multivalued
1637         // [node:field_name:delta:image_style:property] tokens. If the token is
1638         // of the form [node:field_name:image_style], provide the URL as a
1639         // replacement.
1640         $property_name = isset($parts[1]) ? $parts[1] : 'url';
1641         $entity = $field_item->entity;
1642         $original_uri = $entity->getFileUri();
1643
1644         // Only generate the image derivative if needed.
1645         if ($property_name === 'width' || $property_name === 'height') {
1646           $dimensions = [
1647             'width' => $field_item->width,
1648             'height' => $field_item->height,
1649           ];
1650           $style->transformDimensions($dimensions, $original_uri);
1651           $replacements[$original] = $dimensions[$property_name];
1652         }
1653         elseif ($property_name === 'uri') {
1654           $replacements[$original] = $style->buildUri($original_uri);
1655         }
1656         elseif ($property_name === 'url') {
1657           $replacements[$original] = $style->buildUrl($original_uri);
1658         }
1659         else {
1660           // Generate the image derivative, if it doesn't already exist.
1661           $derivative_uri = $style->buildUri($original_uri);
1662           $derivative_exists = TRUE;
1663           if (!file_exists($derivative_uri)) {
1664             $derivative_exists = $style->createDerivative($original_uri, $derivative_uri);
1665           }
1666           if ($derivative_exists) {
1667             $image = \Drupal::service('image.factory')->get($derivative_uri);
1668             // Provide the replacement.
1669             switch ($property_name) {
1670               case 'mimetype':
1671                 $replacements[$original] = $image->getMimeType();
1672                 break;
1673               case 'filesize' :
1674                 $replacements[$original] = $image->getFileSize();
1675                 break;
1676             }
1677           }
1678         }
1679       }
1680       elseif (in_array($field_item->getFieldDefinition()->getType(), ['datetime', 'daterange']) && in_array($property_name, ['date', 'start_date', 'end_date'])) {
1681         $datetime = $field_item->$property_name->getTimestamp();
1682         if($property_name == $token) {
1683           $replacements[$original] = $datetime;
1684         }
1685         else {
1686           $field_tokens = \Drupal::token()->findWithPrefix($tokens, $property_name);
1687           $replacements += \Drupal::token()->generate('date', $field_tokens, ['date' => $datetime], $options, $bubbleable_metadata);
1688         }
1689       }
1690       else {
1691         $replacements[$original] = $field_item->$property_name;
1692       }
1693     }
1694   }
1695   return $replacements;
1696 }
1697
1698 /**
1699  * Pre-render callback for field output used with tokens.
1700  */
1701 function token_pre_render_field_token($elements) {
1702   // Remove the field theme hook, attachments, and JavaScript states.
1703   unset($elements['#theme']);
1704   unset($elements['#states']);
1705   unset($elements['#attached']);
1706
1707   // Prevent multi-value fields from appearing smooshed together by appending
1708   // a join suffix to all but the last value.
1709   $deltas = Element::getVisibleChildren($elements);
1710   $count = count($deltas);
1711   if ($count > 1) {
1712     $join = isset($elements['#token_options']['join']) ? $elements['#token_options']['join'] : ", ";
1713     foreach ($deltas as $index => $delta) {
1714       // Do not add a suffix to the last item.
1715       if ($index < ($count - 1)) {
1716         $elements[$delta] += array('#suffix' => $join);
1717       }
1718     }
1719   }
1720   return $elements;
1721 }