5 * Token callbacks for the token module.
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;
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;
30 * Implements hook_token_info_alter().
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.'),
42 // Add a 'dynamic' key to any tokens that have chained but dynamic tokens.
43 $info['tokens']['date']['custom']['dynamic'] = TRUE;
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.');
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']);
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';
60 if (isset($info['tokens']['node']['url']) && \Drupal::moduleHandler()->moduleExists('node')) {
61 $info['tokens']['node']['url']['type'] = 'url';
63 if (isset($info['tokens']['term']['url']) && \Drupal::moduleHandler()->moduleExists('taxonomy')) {
64 $info['tokens']['term']['url']['type'] = 'url';
66 $info['tokens']['user']['url']['type'] = 'url';
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)) {
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(),
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(
92 'description' => t('The URL of the @entity.', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
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()))),
104 'type' => $token_type,
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))),
125 * Implements hook_token_info().
127 function token_token_info() {
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."),
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.'),
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',
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',
150 $info['tokens']['content-type']['name'] = array(
152 'description' => t('The name of the content type.'),
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.'),
158 $info['tokens']['content-type']['description'] = array(
159 'name' => t('Description'),
160 'description' => t('The optional description of the content type.'),
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.'),
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."),
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."),
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."),
184 $info['tokens']['term']['root'] = array(
185 'name' => t('Root term'),
186 'description' => t("The root term of the taxonomy term."),
190 $info['tokens']['vocabulary']['machine-name'] = array(
191 'name' => t('Machine-readable name'),
192 'description' => t('The unique machine-readable name of the vocabulary.'),
194 $info['tokens']['vocabulary']['edit-url'] = array(
195 'name' => t('Edit URL'),
196 'description' => t("The URL of the vocabulary's edit page."),
202 $info['tokens']['file']['basename'] = array(
203 'name' => t('Base name'),
204 'description' => t('The base name of the file.'),
206 $info['tokens']['file']['extension'] = array(
207 'name' => t('Extension'),
208 'description' => t('The extension of the file.'),
210 $info['tokens']['file']['size-raw'] = array(
211 'name' => t('File byte size'),
212 'description' => t('The size of the file, in bytes.'),
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,
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,
229 $info['tokens']['user']['roles'] = array(
230 'name' => t('Roles'),
231 'description' => t('The user roles associated with the user account.'),
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.',
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',
247 $info['tokens']['menu-link']['mlid'] = array(
248 'name' => t('Link ID'),
249 'description' => t('The unique ID of the menu link.'),
251 $info['tokens']['menu-link']['title'] = array(
252 'name' => t('Title'),
253 'description' => t('The title of the menu link.'),
255 $info['tokens']['menu-link']['url'] = array(
257 'description' => t('The URL of the menu link.'),
260 $info['tokens']['menu-link']['parent'] = array(
261 'name' => t('Parent'),
262 'description' => t("The menu link's parent."),
263 'type' => 'menu-link',
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."),
270 $info['tokens']['menu-link']['root'] = array(
272 'description' => t("The menu link's root."),
273 'type' => 'menu-link',
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.'),
281 $info['tokens']['current-page']['title'] = array(
282 'name' => t('Title'),
283 'description' => t('The title of the current page.'),
285 $info['tokens']['current-page']['url'] = array(
287 'description' => t('The URL of the current page.'),
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.'),
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.'),
301 $info['types']['url'] = array(
303 'description' => t('Tokens related to URLs.'),
304 'needs-data' => 'path',
306 $info['tokens']['url']['path'] = array(
308 'description' => t('The path component of the URL.'),
310 $info['tokens']['url']['relative'] = array(
311 'name' => t('Relative URL'),
312 'description' => t('The relative URL.'),
314 $info['tokens']['url']['absolute'] = array(
315 'name' => t('Absolute URL'),
316 'description' => t('The absolute URL.'),
318 $info['tokens']['url']['brief'] = array(
319 'name' => t('Brief URL'),
320 'description' => t('The URL without the protocol and trailing backslash.'),
322 $info['tokens']['url']['unaliased'] = array(
323 'name' => t('Unaliased URL'),
324 'description' => t('The unaliased URL.'),
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')."),
334 $info['types']['array'] = array(
335 'name' => t('Array'),
336 'description' => t('Tokens related to arrays of strings.'),
337 'needs-data' => 'array',
340 $info['tokens']['array']['first'] = array(
341 'name' => t('First'),
342 'description' => t('The first element of the array.'),
344 $info['tokens']['array']['last'] = array(
346 'description' => t('The last element of the array.'),
348 $info['tokens']['array']['count'] = array(
349 'name' => t('Count'),
350 'description' => t('The number of elements in the array.'),
352 $info['tokens']['array']['reversed'] = array(
353 'name' => t('Reversed'),
354 'description' => t('The array reversed.'),
357 $info['tokens']['array']['keys'] = array(
359 'description' => t('The array of keys of the array.'),
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.'),
367 $info['tokens']['array']['value'] = array(
368 'name' => t('Value'),
369 'description' => t('The specific value of the array.'),
374 $info['types']['random'] = array(
375 'name' => t('Random'),
376 'description' => t('Tokens related to random data.'),
378 $info['tokens']['random']['number'] = array(
379 'name' => t('Number'),
380 'description' => t('A random number from 0 to @max.', array('@max' => mt_getrandmax())),
382 $info['tokens']['random']['hash'] = array(
384 'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', array('@hash-algos' => implode(', ', hash_algos()))),
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',
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.'),
402 $info['tokens']['image_with_image_style']['filesize'] = [
403 'name' => t('File size'),
404 'description' => t('The file size of the image.'),
406 $info['tokens']['image_with_image_style']['height'] = [
407 'name' => t('Height'),
408 'description' => t('The height the image, in pixels.'),
410 $info['tokens']['image_with_image_style']['width'] = [
411 'name' => t('Width'),
412 'description' => t('The width of the image, in pixels.'),
414 $info['tokens']['image_with_image_style']['uri'] = [
416 'description' => t('The URI to the image.'),
418 $info['tokens']['image_with_image_style']['url'] = [
420 'description' => t('The URL to the image.'),
428 * Implements hook_tokens().
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'];
439 $langcode = $language_manager->getCurrentLanguage()->getId();
443 if ($type == 'date') {
444 $date = !empty($data['date']) ? $data['date'] : REQUEST_TIME;
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);
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);
462 if ($type == 'comment' && !empty($data['comment'])) {
463 /* @var \Drupal\comment\CommentInterface $comment */
464 $comment = $data['comment'];
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);
474 if ($type == 'node' && !empty($data['node'])) {
475 /* @var \Drupal\node\NodeInterface $node */
476 $node = $data['node'];
478 foreach ($tokens as $name => $original) {
481 $replacements[$original] = (string) $node->revision_log->value;
484 $type_name = \Drupal::entityTypeManager()->getStorage('node_type')->load($node->getType())->label();
485 $replacements[$original] = $type_name;
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);
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);
497 if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
498 $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $node->urlInfo()), $options, $bubbleable_metadata);
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'];
507 foreach ($tokens as $name => $original) {
510 $replacements[$original] = $node_type->label();
513 $replacements[$original] = $node_type->id();
516 $replacements[$original] = $node_type->getDescription();
519 $count = \Drupal::entityQueryAggregate('node')
520 ->aggregate('nid', 'COUNT')
521 ->condition('type', $node_type->id())
523 $replacements[$original] = (int) $count;
526 $replacements[$original] = $node_type->url('edit-form', $url_options);
532 // Taxonomy term tokens.
533 if ($type == 'term' && !empty($data['term'])) {
534 /* @var \Drupal\taxonomy\TermInterface $term */
535 $term = $data['term'];
537 /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
538 $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
540 foreach ($tokens as $name => $original) {
543 $replacements[$original] = Url::fromRoute('entity.taxonomy_term.edit_form', ['taxonomy_term' => $term->id()], $url_options)->toString();
547 if ($parents = token_taxonomy_term_load_all_parents($term->id(), $langcode)) {
548 $replacements[$original] = token_render_array($parents, $options);
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();
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);
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);
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);
581 // Vocabulary tokens.
582 if ($type == 'vocabulary' && !empty($data['vocabulary'])) {
583 $vocabulary = $data['vocabulary'];
585 foreach ($tokens as $name => $original) {
588 $replacements[$original] = $vocabulary->id();
591 $replacements[$original] = Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], $url_options)->toString();
598 if ($type == 'file' && !empty($data['file'])) {
599 $file = $data['file'];
601 foreach ($tokens as $name => $original) {
604 $basename = pathinfo($file->uri->value, PATHINFO_BASENAME);
605 $replacements[$original] = $basename;
608 $extension = pathinfo($file->uri->value, PATHINFO_EXTENSION);
609 $replacements[$original] = $extension;
612 $replacements[$original] = (int) $file->filesize->value;
619 if ($type == 'user' && !empty($data['user'])) {
620 /* @var \Drupal\user\UserInterface $account */
621 $account = $data['user'];
623 foreach ($tokens as $name => $original) {
626 if ($account instanceof UserInterface && $account->hasField('user_picture')) {
627 /** @var \Drupal\Core\Render\RendererInterface $renderer */
628 $renderer = \Drupal::service('renderer');
630 '#theme' => 'user_picture',
631 '#account' => $account,
633 $replacements[$original] = $renderer->renderPlain($output);
638 $roles = $account->getRoles();
639 $roles_names = array_combine($roles, $roles);
640 $replacements[$original] = token_render_array($roles_names, $options);
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);
649 if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
650 $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $account->urlInfo()), $options, $bubbleable_metadata);
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);
659 // Current user tokens.
660 if ($type == 'current-user') {
661 foreach ($tokens as $name => $original) {
664 $ip = \Drupal::request()->getClientIp();
665 $replacements[$original] = $ip;
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');
678 if ($link instanceof MenuLinkContentInterface) {
679 $link = $menu_link_manager->createInstance($link->getPluginId());
682 foreach ($tokens as $name => $original) {
685 $replacements[$original] = $link->getPluginId();
688 $replacements[$original] = token_menu_link_translated_title($link, $langcode);
691 $replacements[$original] = $link->getUrlObject()->setAbsolute()->toString();
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);
701 if ($parents = token_menu_link_load_all_parents($link->getPluginId(), $langcode)) {
702 $replacements[$original] = token_render_array($parents, $options);
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);
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);
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);
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);
729 if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
730 $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $link->getUrlObject()), $options, $bubbleable_metadata);
735 // Current page tokens.
736 if ($type == 'current-page') {
737 foreach ($tokens as $name => $original) {
740 $request = \Drupal::request();
741 $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
743 $title = \Drupal::service('title_resolver')->getTitle($request, $route);
744 $replacements[$original] = token_render_array_value($title);
748 $replacements[$original] = Url::fromRoute('<current>', [], $url_options)->toString();
751 if ($page = \Drupal::request()->query->get('page')) {
752 // @see PagerDefault::execute()
753 $pager_page_array = explode(',', $page);
754 $page = $pager_page_array[0];
756 $replacements[$original] = (int) $page + 1;
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];
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;
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);
793 if ($type == 'url' && !empty($data['url'])) {
794 /** @var \Drupal\Core\Url $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();
800 foreach ($tokens as $name => $original) {
803 $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
804 $replacements[$original] = $value;
808 $alias = \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode);
809 $replacements[$original] = $alias;
812 $replacements[$original] = $url->setAbsolute()->toString();
815 $replacements[$original] = $url->setAbsolute(FALSE)->toString();
818 $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', $url->setAbsolute()->toString());
821 $unaliased = clone $url;
822 $replacements[$original] = $unaliased->setAbsolute()->setOption('alias', TRUE)->toString();
825 $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
826 $replacements[$original] = token_render_array(explode('/', $value), $options);
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);
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);
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];
850 foreach ($tokens as $name => $original) {
853 if (_token_module($type, 'url') == 'token' && $url = $entity->url()) {
854 $replacements[$original] = $url;
859 if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
860 $label = $entity->original->label();
861 $replacements[$original] = $label;
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);
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);
877 // Pass through to an generic 'entity' token type generation.
878 $entity_data = array(
879 'entity_type' => $entity_type,
881 'token_type' => $type,
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);
888 if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
889 $array = $data['array'];
891 $sort = isset($options['array sort']) ? $options['array sort'] : TRUE;
892 $keys = token_element_children($array, $sort);
894 /** @var \Drupal\Core\Render\RendererInterface $renderer */
895 $renderer = \Drupal::service('renderer');
897 foreach ($tokens as $name => $original) {
900 $value = $array[$keys[0]];
901 $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
902 $replacements[$original] = $value;
905 $value = $array[$keys[count($keys) - 1]];
906 $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
907 $replacements[$original] =$value;
910 $replacements[$original] = count($keys);
913 $replacements[$original] = token_render_array($keys, $options);
916 $reversed = array_reverse($array, TRUE);
917 $replacements[$original] = token_render_array($reversed, $options);
920 $replacements[$original] = token_render_array($array, array('join' => '') + $options);
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);
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);
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);
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);
951 // @todo Handle if the array values are not strings and could be chained.
955 if ($type == 'random') {
956 foreach ($tokens as $name => $original) {
959 $replacements[$original] = mt_rand();
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));
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));
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);
993 return $replacements;
997 * Implements hook_token_info() on behalf of book.module.
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',
1006 $info['tokens']['book']['title'] = array(
1007 'name' => t('Title'),
1008 'description' => t('Title of the book.'),
1010 $info['tokens']['book']['author'] = array(
1011 'name' => t('Author'),
1012 'description' => t('The author of the book.'),
1015 $info['tokens']['book']['root'] = array(
1016 'name' => t('Root'),
1017 'description' => t('Top level of the book.'),
1020 $info['tokens']['book']['parent'] = array(
1021 'name' => t('Parent'),
1022 'description' => t('Parent of the current page.'),
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."),
1031 $info['tokens']['node']['book'] = array(
1032 'name' => t('Book'),
1033 'description' => t('The book page associated with the node.'),
1040 * Implements hook_tokens() on behalf of book.module.
1042 function book_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
1043 $replacements = array();
1046 if ($type == 'node' && !empty($data['node'])) {
1047 $book = $data['node']->book;
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);
1057 else if ($type == 'book' && !empty($data['book'])) {
1058 $book = $data['book']->book;
1060 if (!empty($book['bid'])) {
1061 $book_node = Node::load($book['bid']);
1063 foreach ($tokens as $name => $original) {
1067 $replacements[$original] = $book_node->getTitle();
1070 if (!empty($book['pid'])) {
1071 $parent_node = Node::load($book['pid']);
1072 $replacements[$original] = $parent_node->getTitle();
1076 if ($parents = token_book_load_all_parents($book)) {
1077 $replacements[$original] = token_render_array($parents, $options);
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);
1086 if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
1087 $replacements += \Drupal::token()->generate('node', $book_tokens, array('node' => $book_node), $options, $bubbleable_metadata);
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);
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);
1100 return $replacements;
1104 * Implements hook_token_info() on behalf of menu_ui.module.
1106 function menu_ui_token_info() {
1108 $info['types']['menu'] = array(
1109 'name' => t('Menus'),
1110 'description' => t('Tokens related to menus.'),
1111 'needs-data' => 'menu',
1113 $info['tokens']['menu']['name'] = array(
1114 'name' => t('Name'),
1115 'description' => t("The name of the menu."),
1117 $info['tokens']['menu']['machine-name'] = array(
1118 'name' => t('Machine-readable name'),
1119 'description' => t("The unique machine-readable name of the menu."),
1121 $info['tokens']['menu']['description'] = array(
1122 'name' => t('Description'),
1123 'description' => t('The optional description of the menu.'),
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.'),
1129 $info['tokens']['menu']['edit-url'] = array(
1130 'name' => t('Edit URL'),
1131 'description' => t("The URL of the menu's edit page."),
1134 $info['tokens']['menu-link']['menu'] = array(
1135 'name' => t('Menu'),
1136 'description' => t('The menu of the menu link.'),
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."),
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',
1153 * Implements hook_tokens() on behalf of menu_ui.module.
1155 function menu_ui_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
1156 $replacements = array();
1158 /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
1159 $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
1161 $url_options = array('absolute' => TRUE);
1162 if (isset($options['langcode'])) {
1163 $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
1164 $langcode = $options['langcode'];
1171 if ($type == 'node' && !empty($data['node'])) {
1172 /** @var \Drupal\node\NodeInterface $node */
1173 $node = $data['node'];
1175 foreach ($tokens as $name => $original) {
1178 // On node-form save we populate a calculated field with a menu_link
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();
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);
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);
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);
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'];
1217 if ($link instanceof MenuLinkContentInterface) {
1218 $link = $menu_link_manager->createInstance($link->getPluginId());
1221 foreach ($tokens as $name => $original) {
1224 if ($menu = Menu::load($link->getMenuName())) {
1225 $replacements[$original] = $menu->label();
1230 $replacements[$original] = $link->getEditRoute()->setOptions($url_options)->toString();
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);
1242 if ($type == 'menu' && !empty($data['menu'])) {
1243 /** @var \Drupal\system\MenuInterface $menu */
1244 $menu = $data['menu'];
1246 foreach ($tokens as $name => $original) {
1249 $replacements[$original] = $menu->label();
1252 case 'machine-name':
1253 $replacements[$original] = $menu->id();
1257 $replacements[$original] = $menu->getDescription();
1260 case 'menu-link-count':
1261 $replacements[$original] = $menu_link_manager->countMenuLinks($menu->id());
1265 $replacements[$original] = Url::fromRoute('entity.menu.edit_form', ['menu' => $menu->id()], $url_options)->toString();
1271 return $replacements;
1275 * Returns a best matched link for a given node.
1277 * If the url exists in multiple menus, default to the one set on the node
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.
1285 * @return \Drupal\Core\Menu\MenuLinkInterface
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
1294 $defaults = menu_ui_get_menu_link_defaults($node);
1295 if (isset($defaults['id']) && isset($links[$defaults['id']])) {
1296 $link = $links[$defaults['id']];
1299 $link = reset($links);
1305 * Implements hook_token_info_alter() on behalf of field.module.
1307 * We use hook_token_info_alter() rather than hook_token_info() as other
1308 * modules may already have defined some field tokens.
1310 function field_token_info_alter(&$info) {
1311 $type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
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')) {
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)) {
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])) {
1333 // Ensure the token implements FieldStorageConfigInterface or is defined
1336 if (isset($info['types'][$token_type]['module'])) {
1337 $provider = $info['types'][$token_type]['module'];
1339 if (!($field instanceof FieldStorageConfigInterface) && $provider != 'token') {
1343 // If a token already exists for this field, then don't add it.
1344 if (isset($info['tokens'][$token_type][$field_name])) {
1348 if ($token_type == 'comment' && $field_name == 'comment_body') {
1349 // Core provides the comment field as [comment:body].
1353 // Do not define the token type if the field has no properties.
1354 if (!$field->getPropertyDefinitions()) {
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);
1367 $description = t('@type field.', $params);
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,
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,
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>",
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,
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',
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,
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',
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';
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';
1459 * Returns the label of a certain field.
1461 * Therefore it looks up in all bundles to find the most used instance.
1463 * Based on views_entity_field_label().
1465 * @todo Resync this method with views_entity_field_label().
1467 function _token_field_label($entity_type, $field_name) {
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;
1479 if (empty($labels)) {
1480 return [$field_name];
1483 // Sort the field labels by it most used label and return the labels.
1485 return array_keys($labels);
1489 * Implements hook_tokens() on behalf of field.module.
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;
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;
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();
1506 // Obtain the entity with the correct language.
1507 $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
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;
1518 // For [entity:field_name:0], [entity:field_name:0:value] and
1519 // [entity:field_name:value] tokens.
1521 list($field_name, $delta) = explode(':', $name, 2);
1522 if (!is_numeric($delta)) {
1525 $token_name = $field_name;
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') {
1533 $display_options = 'token';
1534 // Do not continue if the field is empty.
1535 if ($entity->get($field_name)->isEmpty()) {
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',
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);
1559 // If no such delta exists, let's not replace the token.
1564 // Render the whole field (with all deltas).
1566 $field_output = $entity->$field_name->view($display_options);
1567 // If we are displaying all field items we need this #pre_render
1569 $field_output['#pre_render'][] = 'token_pre_render_field_token';
1571 $field_output['#token_options'] = $options;
1572 $replacements[$original] = \Drupal::service('renderer')->renderPlain($field_output);
1574 // Handle [entity:field_name:value] and [entity:field_name:0:value]
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,
1582 $replacements += \Drupal::token()->generate($field_name, $field_tokens, $property_token_data, $options, $bubbleable_metadata);
1586 // Remove the cloned object from memory.
1589 elseif (!empty($data['field_property'])) {
1590 foreach ($tokens as $token => $original) {
1591 $filtered_tokens = $tokens;
1593 $parts = explode(':', $token);
1594 if (is_numeric($parts[0])) {
1595 if (count($parts) > 1) {
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);
1604 // Token is fieldname:delta, which is invalid.
1609 $property_name = $parts[0];
1612 if (isset($data[$data['field_name']][$delta])) {
1613 $field_item = $data[$data['field_name']][$delta];
1616 // The field has no such delta, abort replacement.
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);
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);
1632 $replacements[$original] = $entity->label();
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
1640 $property_name = isset($parts[1]) ? $parts[1] : 'url';
1641 $entity = $field_item->entity;
1642 $original_uri = $entity->getFileUri();
1644 // Only generate the image derivative if needed.
1645 if ($property_name === 'width' || $property_name === 'height') {
1647 'width' => $field_item->width,
1648 'height' => $field_item->height,
1650 $style->transformDimensions($dimensions, $original_uri);
1651 $replacements[$original] = $dimensions[$property_name];
1653 elseif ($property_name === 'uri') {
1654 $replacements[$original] = $style->buildUri($original_uri);
1656 elseif ($property_name === 'url') {
1657 $replacements[$original] = $style->buildUrl($original_uri);
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);
1666 if ($derivative_exists) {
1667 $image = \Drupal::service('image.factory')->get($derivative_uri);
1668 // Provide the replacement.
1669 switch ($property_name) {
1671 $replacements[$original] = $image->getMimeType();
1674 $replacements[$original] = $image->getFileSize();
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;
1686 $field_tokens = \Drupal::token()->findWithPrefix($tokens, $property_name);
1687 $replacements += \Drupal::token()->generate('date', $field_tokens, ['date' => $datetime], $options, $bubbleable_metadata);
1691 $replacements[$original] = $field_item->$property_name;
1695 return $replacements;
1699 * Pre-render callback for field output used with tokens.
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']);
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);
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);