token.tokens.inc in Token 8
Same filename and directory in other branches
Token callbacks for the token module.
File
token.tokens.incView source
<?php
/**
* @file
* Token callbacks for the token module.
*/
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\system\Entity\Menu;
use Drupal\user\UserInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\image\Entity\ImageStyle;
/**
* Implements hook_token_info_alter().
*/
function token_token_info_alter(&$info) {
// Force 'date' type tokens to require input and add a 'current-date' type.
// @todo Remove when http://drupal.org/node/943028 is fixed.
$info['types']['date']['needs-data'] = 'date';
$info['types']['current-date'] = [
'name' => t('Current date'),
'description' => t('Tokens related to the current date and time.'),
'type' => 'date',
];
// Add a 'dynamic' key to any tokens that have chained but dynamic tokens.
$info['tokens']['date']['custom']['dynamic'] = TRUE;
// Remove deprecated tokens from being listed.
unset($info['tokens']['node']['tnid']);
unset($info['tokens']['node']['type']);
unset($info['tokens']['node']['type-name']);
// Support 'url' type tokens for core tokens.
if (isset($info['tokens']['comment']['url']) && \Drupal::moduleHandler()
->moduleExists('comment')) {
$info['tokens']['comment']['url']['type'] = 'url';
}
if (isset($info['tokens']['node']['url']) && \Drupal::moduleHandler()
->moduleExists('node')) {
$info['tokens']['node']['url']['type'] = 'url';
}
if (isset($info['tokens']['term']['url']) && \Drupal::moduleHandler()
->moduleExists('taxonomy')) {
$info['tokens']['term']['url']['type'] = 'url';
}
$info['tokens']['user']['url']['type'] = 'url';
// Add [token:url] tokens for any URI-able entities.
$entities = \Drupal::entityTypeManager()
->getDefinitions();
foreach ($entities as $entity_info) {
// Do not generate tokens if the entity doesn't define a token type or is
// not a content entity.
if (!$entity_info
->get('token_type') || !$entity_info instanceof ContentEntityTypeInterface) {
continue;
}
$token_type = $entity_info
->get('token_type');
if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
// Define tokens for entity type's without their own integration.
$info['types'][$entity_info
->id()] = [
'name' => $entity_info
->getLabel(),
'needs-data' => $entity_info
->id(),
'module' => 'token',
];
}
// Add [entity:url] tokens if they do not already exist.
// @todo Support entity:label
if (!isset($info['tokens'][$token_type]['url'])) {
$info['tokens'][$token_type]['url'] = [
'name' => t('URL'),
'description' => t('The URL of the @entity.', [
'@entity' => mb_strtolower($entity_info
->getLabel()),
]),
'module' => 'token',
'type' => 'url',
];
}
// Add [entity:language] tokens if they do not already exist.
if (!isset($info['tokens'][$token_type]['language'])) {
$info['tokens'][$token_type]['language'] = [
'name' => t('Language'),
'description' => t('The language of the @entity.', [
'@entity' => mb_strtolower($entity_info
->getLabel()),
]),
'module' => 'token',
'type' => 'language',
];
}
// Add [entity:original] tokens if they do not already exist.
if (!isset($info['tokens'][$token_type]['original'])) {
$info['tokens'][$token_type]['original'] = [
'name' => t('Original @entity', [
'@entity' => mb_strtolower($entity_info
->getLabel()),
]),
'description' => t('The original @entity data if the @entity is being updated or saved.', [
'@entity' => mb_strtolower($entity_info
->getLabel()),
]),
'module' => 'token',
'type' => $token_type,
];
}
}
// Add support for custom date formats.
// @todo Remove when http://drupal.org/node/1173706 is fixed.
$date_format_types = \Drupal::entityTypeManager()
->getStorage('date_format')
->loadMultiple();
foreach ($date_format_types as $date_format_type => $date_format_type_info) {
/* @var \Drupal\system\Entity\DateFormat $date_format_type_info */
if (!isset($info['tokens']['date'][$date_format_type])) {
$info['tokens']['date'][$date_format_type] = [
'name' => Html::escape($date_format_type_info
->label()),
'description' => t("A date in '@type' format. (%date)", [
'@type' => $date_format_type,
'%date' => \Drupal::service('date.formatter')
->format(\Drupal::time()
->getRequestTime(), $date_format_type),
]),
'module' => 'token',
];
}
}
}
/**
* Implements hook_token_info().
*/
function token_token_info() {
// Node tokens.
if (\Drupal::moduleHandler()
->moduleExists('node')) {
$info['tokens']['node']['source'] = [
'name' => t('Translation source node'),
'description' => t("The source node for this current node's translation set."),
'type' => 'node',
];
$info['tokens']['node']['log'] = [
'name' => t('Revision log message'),
'description' => t('The explanation of the most recent changes made to the node.'),
];
$info['tokens']['node']['content-type'] = [
'name' => t('Content type'),
'description' => t('The content type of the node.'),
'type' => 'content-type',
];
// Content type tokens.
$info['types']['content-type'] = [
'name' => t('Content types'),
'description' => t('Tokens related to content types.'),
'needs-data' => 'node_type',
];
$info['tokens']['content-type']['name'] = [
'name' => t('Name'),
'description' => t('The name of the content type.'),
];
$info['tokens']['content-type']['machine-name'] = [
'name' => t('Machine-readable name'),
'description' => t('The unique machine-readable name of the content type.'),
];
$info['tokens']['content-type']['description'] = [
'name' => t('Description'),
'description' => t('The optional description of the content type.'),
];
$info['tokens']['content-type']['node-count'] = [
'name' => t('Node count'),
'description' => t('The number of nodes belonging to the content type.'),
];
$info['tokens']['content-type']['edit-url'] = [
'name' => t('Edit URL'),
'description' => t("The URL of the content type's edit page."),
];
}
// Taxonomy term and vocabulary tokens.
if (\Drupal::moduleHandler()
->moduleExists('taxonomy')) {
$info['tokens']['term']['edit-url'] = [
'name' => t('Edit URL'),
'description' => t("The URL of the taxonomy term's edit page."),
];
$info['tokens']['term']['parents'] = [
'name' => t('Parents'),
'description' => t("An array of all the term's parents, starting with the root."),
'type' => 'array',
];
$info['tokens']['term']['root'] = [
'name' => t('Root term'),
'description' => t("The root term of the taxonomy term."),
'type' => 'term',
];
$info['tokens']['vocabulary']['machine-name'] = [
'name' => t('Machine-readable name'),
'description' => t('The unique machine-readable name of the vocabulary.'),
];
$info['tokens']['vocabulary']['edit-url'] = [
'name' => t('Edit URL'),
'description' => t("The URL of the vocabulary's edit page."),
];
}
// File tokens.
$info['tokens']['file']['basename'] = [
'name' => t('Base name'),
'description' => t('The base name of the file.'),
];
$info['tokens']['file']['extension'] = [
'name' => t('Extension'),
'description' => t('The extension of the file.'),
];
$info['tokens']['file']['size-raw'] = [
'name' => t('File byte size'),
'description' => t('The size of the file, in bytes.'),
];
// User tokens.
// Add information on the restricted user tokens.
$info['tokens']['user']['cancel-url'] = [
'name' => t('Account cancellation URL'),
'description' => t('The URL of the confirm delete page for the user account.'),
'restricted' => TRUE,
];
$info['tokens']['user']['one-time-login-url'] = [
'name' => t('One-time login URL'),
'description' => t('The URL of the one-time login page for the user account.'),
'restricted' => TRUE,
];
$info['tokens']['user']['roles'] = [
'name' => t('Roles'),
'description' => t('The user roles associated with the user account.'),
'type' => 'array',
];
// Current user tokens.
$info['tokens']['current-user']['ip-address'] = [
'name' => t('IP address'),
'description' => t('The IP address of the current user.'),
];
// Menu link tokens (work regardless if menu module is enabled or not).
$info['types']['menu-link'] = [
'name' => t('Menu links'),
'description' => t('Tokens related to menu links.'),
'needs-data' => 'menu-link',
];
$info['tokens']['menu-link']['mlid'] = [
'name' => t('Link ID'),
'description' => t('The unique ID of the menu link.'),
];
$info['tokens']['menu-link']['title'] = [
'name' => t('Title'),
'description' => t('The title of the menu link.'),
];
$info['tokens']['menu-link']['url'] = [
'name' => t('URL'),
'description' => t('The URL of the menu link.'),
'type' => 'url',
];
$info['tokens']['menu-link']['parent'] = [
'name' => t('Parent'),
'description' => t("The menu link's parent."),
'type' => 'menu-link',
];
$info['tokens']['menu-link']['parents'] = [
'name' => t('Parents'),
'description' => t("An array of all the menu link's parents, starting with the root."),
'type' => 'array',
];
$info['tokens']['menu-link']['root'] = [
'name' => t('Root'),
'description' => t("The menu link's root."),
'type' => 'menu-link',
];
// Language tokens.
$info['types']['language'] = [
'name' => t('Language'),
'description' => t('Tokens related to site language.'),
];
$info['tokens']['language']['name'] = [
'name' => t('Language name'),
'description' => t('The language name.'),
];
$info['tokens']['language']['langcode'] = [
'name' => t('Language code'),
'description' => t('The language code.'),
];
$info['tokens']['language']['direction'] = [
'name' => t('Direction'),
'description' => t('Whether the language is written left-to-right (ltr) or right-to-left (rtl).'),
];
$info['tokens']['language']['domain'] = [
'name' => t('Domain'),
'description' => t('The domain name to use for the language.'),
];
$info['tokens']['language']['prefix'] = [
'name' => t('Path prefix'),
'description' => t('Path prefix for URLs in the language.'),
];
// Current page tokens.
$info['types']['current-page'] = [
'name' => t('Current page'),
'description' => t('Tokens related to the current page request.'),
];
$info['tokens']['current-page']['title'] = [
'name' => t('Title'),
'description' => t('The title of the current page.'),
];
$info['tokens']['current-page']['url'] = [
'name' => t('URL'),
'description' => t('The URL of the current page.'),
'type' => 'url',
];
$info['tokens']['current-page']['page-number'] = [
'name' => t('Page number'),
'description' => t('The page number of the current page when viewing paged lists.'),
];
$info['tokens']['current-page']['query'] = [
'name' => t('Query string value'),
'description' => t('The value of a specific query string field of the current page.'),
'dynamic' => TRUE,
];
$info['tokens']['current-page']['interface-language'] = [
'name' => t('Interface language'),
'description' => t('The active user interface language.'),
'type' => 'language',
];
$info['tokens']['current-page']['content-language'] = [
'name' => t('Content language'),
'description' => t('The active content language.'),
'type' => 'language',
];
// URL tokens.
$info['types']['url'] = [
'name' => t('URL'),
'description' => t('Tokens related to URLs.'),
'needs-data' => 'path',
];
$info['tokens']['url']['path'] = [
'name' => t('Path'),
'description' => t('The path component of the URL.'),
];
$info['tokens']['url']['relative'] = [
'name' => t('Relative URL'),
'description' => t('The relative URL.'),
];
$info['tokens']['url']['absolute'] = [
'name' => t('Absolute URL'),
'description' => t('The absolute URL.'),
];
$info['tokens']['url']['brief'] = [
'name' => t('Brief URL'),
'description' => t('The URL without the protocol and trailing backslash.'),
];
$info['tokens']['url']['unaliased'] = [
'name' => t('Unaliased URL'),
'description' => t('The unaliased URL.'),
'type' => 'url',
];
$info['tokens']['url']['args'] = [
'name' => t('Arguments'),
'description' => t("The specific argument of the current page (e.g. 'arg:1' on the page 'node/1' returns '1')."),
'type' => 'array',
];
// Array tokens.
$info['types']['array'] = [
'name' => t('Array'),
'description' => t('Tokens related to arrays of strings.'),
'needs-data' => 'array',
'nested' => TRUE,
];
$info['tokens']['array']['first'] = [
'name' => t('First'),
'description' => t('The first element of the array.'),
];
$info['tokens']['array']['last'] = [
'name' => t('Last'),
'description' => t('The last element of the array.'),
];
$info['tokens']['array']['count'] = [
'name' => t('Count'),
'description' => t('The number of elements in the array.'),
];
$info['tokens']['array']['reversed'] = [
'name' => t('Reversed'),
'description' => t('The array reversed.'),
'type' => 'array',
];
$info['tokens']['array']['keys'] = [
'name' => t('Keys'),
'description' => t('The array of keys of the array.'),
'type' => 'array',
];
$info['tokens']['array']['join'] = [
'name' => t('Imploded'),
'description' => t('The values of the array joined together with a custom string in-between each value.'),
'dynamic' => TRUE,
];
$info['tokens']['array']['value'] = [
'name' => t('Value'),
'description' => t('The specific value of the array.'),
'dynamic' => TRUE,
];
// Random tokens.
$info['types']['random'] = [
'name' => t('Random'),
'description' => t('Tokens related to random data.'),
];
$info['tokens']['random']['number'] = [
'name' => t('Number'),
'description' => t('A random number from 0 to @max.', [
'@max' => mt_getrandmax(),
]),
];
$info['tokens']['random']['hash'] = [
'name' => t('Hash'),
'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', [
'@hash-algos' => implode(', ', hash_algos()),
]),
'dynamic' => TRUE,
];
// Define image_with_image_style token type.
if (\Drupal::moduleHandler()
->moduleExists('image')) {
$info['types']['image_with_image_style'] = [
'name' => t('Image with image style'),
'needs-data' => 'image_with_image_style',
'module' => 'token',
'nested' => TRUE,
];
// Provide tokens for the ImageStyle attributes.
$info['tokens']['image_with_image_style']['mimetype'] = [
'name' => t('MIME type'),
'description' => t('The MIME type (image/png, image/bmp, etc.) of the image.'),
];
$info['tokens']['image_with_image_style']['filesize'] = [
'name' => t('File size'),
'description' => t('The file size of the image.'),
];
$info['tokens']['image_with_image_style']['height'] = [
'name' => t('Height'),
'description' => t('The height the image, in pixels.'),
];
$info['tokens']['image_with_image_style']['width'] = [
'name' => t('Width'),
'description' => t('The width of the image, in pixels.'),
];
$info['tokens']['image_with_image_style']['uri'] = [
'name' => t('URI'),
'description' => t('The URI to the image.'),
];
$info['tokens']['image_with_image_style']['url'] = [
'name' => t('URL'),
'description' => t('The URL to the image.'),
];
}
return $info;
}
/**
* Implements hook_tokens().
*/
function token_tokens($type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
$replacements = [];
$language_manager = \Drupal::languageManager();
$url_options = [
'absolute' => TRUE,
];
if (isset($options['langcode'])) {
$url_options['language'] = $language_manager
->getLanguage($options['langcode']);
$langcode = $options['langcode'];
}
else {
$langcode = $language_manager
->getCurrentLanguage()
->getId();
}
// Date tokens.
if ($type == 'date') {
$date = !empty($data['date']) ? $data['date'] : \Drupal::time()
->getRequestTime();
// @todo Remove when http://drupal.org/node/1173706 is fixed.
$date_format_types = \Drupal::entityTypeManager()
->getStorage('date_format')
->loadMultiple();
foreach ($tokens as $name => $original) {
if (isset($date_format_types[$name]) && _token_module('date', $name) == 'token') {
$replacements[$original] = \Drupal::service('date.formatter')
->format($date, $name, '', NULL, $langcode);
}
}
}
// Current date tokens.
// @todo Remove when http://drupal.org/node/943028 is fixed.
if ($type == 'current-date') {
$replacements += \Drupal::token()
->generate('date', $tokens, [
'date' => \Drupal::time()
->getRequestTime(),
], $options, $bubbleable_metadata);
}
// Comment tokens.
if ($type == 'comment' && !empty($data['comment'])) {
/* @var \Drupal\comment\CommentInterface $comment */
$comment = $data['comment'];
// Chained token relationships.
if ($url_tokens = \Drupal::token()
->findWithPrefix($tokens, 'url')) {
// Add fragment to url options.
$replacements += \Drupal::token()
->generate('url', $url_tokens, [
'url' => $comment
->toUrl('canonical', [
'fragment' => "comment-{$comment->id()}",
]),
], $options, $bubbleable_metadata);
}
}
// Node tokens.
if ($type == 'node' && !empty($data['node'])) {
/* @var \Drupal\node\NodeInterface $node */
$node = $data['node'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'log':
$replacements[$original] = (string) $node->revision_log->value;
break;
case 'content-type':
$type_name = \Drupal::entityTypeManager()
->getStorage('node_type')
->load($node
->getType())
->label();
$replacements[$original] = $type_name;
break;
}
}
// Chained token relationships.
if (($parent_tokens = \Drupal::token()
->findWithPrefix($tokens, 'source')) && ($source_node = $node
->getUntranslated())) {
$replacements += \Drupal::token()
->generate('node', $parent_tokens, [
'node' => $source_node,
], $options, $bubbleable_metadata);
}
if (($node_type_tokens = \Drupal::token()
->findWithPrefix($tokens, 'content-type')) && ($node_type = NodeType::load($node
->bundle()))) {
$replacements += \Drupal::token()
->generate('content-type', $node_type_tokens, [
'node_type' => $node_type,
], $options, $bubbleable_metadata);
}
if ($url_tokens = \Drupal::token()
->findWithPrefix($tokens, 'url')) {
$replacements += \Drupal::token()
->generate('url', $url_tokens, [
'url' => $node
->toUrl(),
], $options, $bubbleable_metadata);
}
}
// Content type tokens.
if ($type == 'content-type' && !empty($data['node_type'])) {
/* @var \Drupal\node\NodeTypeInterface $node_type */
$node_type = $data['node_type'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'name':
$replacements[$original] = $node_type
->label();
break;
case 'machine-name':
$replacements[$original] = $node_type
->id();
break;
case 'description':
$replacements[$original] = $node_type
->getDescription();
break;
case 'node-count':
$count = \Drupal::entityQueryAggregate('node')
->aggregate('nid', 'COUNT')
->condition('type', $node_type
->id())
->execute();
$replacements[$original] = (int) $count;
break;
case 'edit-url':
$result = $node_type
->toUrl('edit-form', $url_options)
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
}
}
}
// Taxonomy term tokens.
if ($type == 'term' && !empty($data['term'])) {
/* @var \Drupal\taxonomy\TermInterface $term */
$term = $data['term'];
/** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
$term_storage = \Drupal::entityTypeManager()
->getStorage('taxonomy_term');
foreach ($tokens as $name => $original) {
switch ($name) {
case 'edit-url':
$result = Url::fromRoute('entity.taxonomy_term.edit_form', [
'taxonomy_term' => $term
->id(),
], $url_options)
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
case 'parents':
if ($parents = token_taxonomy_term_load_all_parents($term
->id(), $langcode)) {
$replacements[$original] = token_render_array($parents, $options);
}
break;
case 'root':
$parents = $term_storage
->loadAllParents($term
->id());
$root_term = end($parents);
if ($root_term
->id() != $term
->id()) {
$root_term = \Drupal::service('entity.repository')
->getTranslationFromContext($root_term, $langcode);
$replacements[$original] = $root_term
->label();
}
break;
}
}
// Chained token relationships.
if ($url_tokens = \Drupal::token()
->findWithPrefix($tokens, 'url')) {
$replacements += \Drupal::token()
->generate('url', $url_tokens, [
'url' => $term
->toUrl(),
], $options, $bubbleable_metadata);
}
// [term:parents:*] chained tokens.
if ($parents_tokens = \Drupal::token()
->findWithPrefix($tokens, 'parents')) {
if ($parents = token_taxonomy_term_load_all_parents($term
->id(), $langcode)) {
$replacements += \Drupal::token()
->generate('array', $parents_tokens, [
'array' => $parents,
], $options, $bubbleable_metadata);
}
}
if ($root_tokens = \Drupal::token()
->findWithPrefix($tokens, 'root')) {
$parents = $term_storage
->loadAllParents($term
->id());
$root_term = end($parents);
if ($root_term->tid != $term
->id()) {
$replacements += \Drupal::token()
->generate('term', $root_tokens, [
'term' => $root_term,
], $options, $bubbleable_metadata);
}
}
}
// Vocabulary tokens.
if ($type == 'vocabulary' && !empty($data['vocabulary'])) {
$vocabulary = $data['vocabulary'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'machine-name':
$replacements[$original] = $vocabulary
->id();
break;
case 'edit-url':
$result = Url::fromRoute('entity.taxonomy_vocabulary.edit_form', [
'taxonomy_vocabulary' => $vocabulary
->id(),
], $url_options)
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
}
}
}
// File tokens.
if ($type == 'file' && !empty($data['file'])) {
$file = $data['file'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'basename':
$basename = pathinfo($file->uri->value, PATHINFO_BASENAME);
$replacements[$original] = $basename;
break;
case 'extension':
$extension = pathinfo($file->uri->value, PATHINFO_EXTENSION);
$replacements[$original] = $extension;
break;
case 'size-raw':
$replacements[$original] = (int) $file->filesize->value;
break;
}
}
}
// User tokens.
if ($type == 'user' && !empty($data['user'])) {
/* @var \Drupal\user\UserInterface $account */
$account = $data['user'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'picture':
if ($account instanceof UserInterface && $account
->hasField('user_picture')) {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$output = [
'#theme' => 'user_picture',
'#account' => $account,
];
$replacements[$original] = $renderer
->renderPlain($output);
}
break;
case 'roles':
$roles = $account
->getRoles();
$roles_names = array_combine($roles, $roles);
$replacements[$original] = token_render_array($roles_names, $options);
break;
}
}
// Chained token relationships.
if ($account instanceof UserInterface && $account
->hasField('user_picture') && ($picture_tokens = \Drupal::token()
->findWithPrefix($tokens, 'picture'))) {
$replacements += \Drupal::token()
->generate('file', $picture_tokens, [
'file' => $account->user_picture->entity,
], $options, $bubbleable_metadata);
}
if ($url_tokens = \Drupal::token()
->findWithPrefix($tokens, 'url')) {
$replacements += \Drupal::token()
->generate('url', $url_tokens, [
'url' => $account
->toUrl(),
], $options, $bubbleable_metadata);
}
if ($role_tokens = \Drupal::token()
->findWithPrefix($tokens, 'roles')) {
$roles = $account
->getRoles();
$roles_names = array_combine($roles, $roles);
$replacements += \Drupal::token()
->generate('array', $role_tokens, [
'array' => $roles_names,
], $options, $bubbleable_metadata);
}
}
// Current user tokens.
if ($type == 'current-user') {
foreach ($tokens as $name => $original) {
switch ($name) {
case 'ip-address':
$ip = \Drupal::request()
->getClientIp();
$replacements[$original] = $ip;
break;
}
}
}
// Menu link tokens.
if ($type == 'menu-link' && !empty($data['menu-link'])) {
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
$link = $data['menu-link'];
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
if ($link instanceof MenuLinkContentInterface) {
$link = $menu_link_manager
->createInstance($link
->getPluginId());
}
foreach ($tokens as $name => $original) {
switch ($name) {
case 'id':
$replacements[$original] = $link
->getPluginId();
break;
case 'title':
$replacements[$original] = token_menu_link_translated_title($link, $langcode);
break;
case 'url':
$result = $link
->getUrlObject()
->setAbsolute()
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
case 'parent':
/** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
if ($link
->getParent() && ($parent = $menu_link_manager
->createInstance($link
->getParent()))) {
$replacements[$original] = token_menu_link_translated_title($parent, $langcode);
}
break;
case 'parents':
if ($parents = token_menu_link_load_all_parents($link
->getPluginId(), $langcode)) {
$replacements[$original] = token_render_array($parents, $options);
}
break;
case 'root':
if ($link
->getParent() && ($parent_ids = array_keys(token_menu_link_load_all_parents($link
->getPluginId(), $langcode)))) {
$root = $menu_link_manager
->createInstance(array_shift($parent_ids));
$replacements[$original] = token_menu_link_translated_title($root, $langcode);
}
break;
}
}
// Chained token relationships.
/** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
if ($link
->getParent() && ($parent_tokens = \Drupal::token()
->findWithPrefix($tokens, 'parent')) && ($parent = $menu_link_manager
->createInstance($link
->getParent()))) {
$replacements += \Drupal::token()
->generate('menu-link', $parent_tokens, [
'menu-link' => $parent,
], $options, $bubbleable_metadata);
}
// [menu-link:parents:*] chained tokens.
if ($parents_tokens = \Drupal::token()
->findWithPrefix($tokens, 'parents')) {
if ($parents = token_menu_link_load_all_parents($link
->getPluginId(), $langcode)) {
$replacements += \Drupal::token()
->generate('array', $parents_tokens, [
'array' => $parents,
], $options, $bubbleable_metadata);
}
}
if (($root_tokens = \Drupal::token()
->findWithPrefix($tokens, 'root')) && $link
->getParent() && ($parent_ids = array_keys(token_menu_link_load_all_parents($link
->getPluginId(), $langcode)))) {
$root = $menu_link_manager
->createInstance(array_shift($parent_ids));
$replacements += \Drupal::token()
->generate('menu-link', $root_tokens, [
'menu-link' => $root,
], $options, $bubbleable_metadata);
}
if ($url_tokens = \Drupal::token()
->findWithPrefix($tokens, 'url')) {
$replacements += \Drupal::token()
->generate('url', $url_tokens, [
'url' => $link
->getUrlObject(),
], $options, $bubbleable_metadata);
}
}
// Language tokens.
if ($type == 'language' && !empty($langcode)) {
$language = $language_manager
->getLanguage($langcode);
if ($language) {
foreach ($tokens as $name => $original) {
switch ($name) {
case 'name':
$replacements[$original] = $language
->getName();
break;
case 'langcode':
$replacements[$original] = $langcode;
break;
case 'direction':
$replacements[$original] = $language
->getDirection();
break;
case 'domain':
if (!isset($language_url_domains)) {
$language_url_domains = \Drupal::config('language.negotiation')
->get('url.domains');
}
if (isset($language_url_domains[$langcode])) {
$replacements[$original] = $language_url_domains[$langcode];
}
break;
case 'prefix':
if (!isset($language_url_prefixes)) {
$language_url_prefixes = \Drupal::config('language.negotiation')
->get('url.prefixes');
}
if (isset($language_url_prefixes[$langcode])) {
$replacements[$original] = $language_url_prefixes[$langcode];
}
break;
}
}
}
}
// Current page tokens.
if ($type == 'current-page') {
$request = \Drupal::request();
foreach ($tokens as $name => $original) {
switch ($name) {
case 'title':
$route = $request->attributes
->get(RouteObjectInterface::ROUTE_OBJECT);
if ($route) {
$title = \Drupal::service('title_resolver')
->getTitle($request, $route);
$replacements[$original] = token_render_array_value($title);
}
break;
case 'url':
$bubbleable_metadata
->addCacheContexts([
'url.path',
]);
try {
$url = Url::createFromRequest($request)
->setOptions($url_options);
} catch (\Exception $e) {
// Url::createFromRequest() can fail, e.g. on 404 pages.
// Fall back and try again with Url::fromUserInput().
try {
$url = Url::fromUserInput($request
->getPathInfo(), $url_options);
} catch (\Exception $e) {
// Instantiation would fail again on malformed urls.
}
}
if (isset($url)) {
$result = $url
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
}
break;
case 'page-number':
if ($page = $request->query
->get('page')) {
// @see PagerDefault::execute()
$pager_page_array = explode(',', $page);
$page = $pager_page_array[0];
}
$replacements[$original] = (int) $page + 1;
break;
}
// [current-page:interface-language:*] chained tokens.
if ($language_interface_tokens = \Drupal::token()
->findWithPrefix($tokens, 'interface-language')) {
$language_interface = $language_manager
->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE);
$langcode = $language_interface
->getId();
$replacements += \Drupal::token()
->generate('language', $language_interface_tokens, $data, [
'langcode' => $langcode,
] + $options, $bubbleable_metadata);
}
// [current-page:content-language:*] chained tokens.
if ($language_content_tokens = \Drupal::token()
->findWithPrefix($tokens, 'content-language')) {
$language_content = $language_manager
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
$langcode = $language_content
->getId();
$replacements += \Drupal::token()
->generate('language', $language_content_tokens, $data, [
'langcode' => $langcode,
] + $options, $bubbleable_metadata);
}
}
// @deprecated
// [current-page:arg] dynamic tokens.
if ($arg_tokens = \Drupal::token()
->findWithPrefix($tokens, 'arg')) {
$path = ltrim(\Drupal::service('path.current')
->getPath(), '/');
// Make sure its a system path.
$path = \Drupal::service('path_alias.manager')
->getPathByAlias($path);
foreach ($arg_tokens as $name => $original) {
$parts = explode('/', $path);
if (is_numeric($name) && isset($parts[$name])) {
$replacements[$original] = $parts[$name];
}
}
}
// [current-page:query] dynamic tokens.
if ($query_tokens = \Drupal::token()
->findWithPrefix($tokens, 'query')) {
$bubbleable_metadata
->addCacheContexts([
'url.query_args',
]);
foreach ($query_tokens as $name => $original) {
if (\Drupal::request()->query
->has($name)) {
$value = \Drupal::request()->query
->get($name);
$replacements[$original] = $value;
}
}
}
// Chained token relationships.
if ($url_tokens = \Drupal::token()
->findWithPrefix($tokens, 'url')) {
$url = NULL;
try {
$url = Url::createFromRequest($request)
->setOptions($url_options);
} catch (\Exception $e) {
// Url::createFromRequest() can fail, e.g. on 404 pages.
// Fall back and try again with Url::fromUserInput().
try {
$url = Url::fromUserInput($request
->getPathInfo(), $url_options);
} catch (\Exception $e) {
// Instantiation would fail again on malformed urls.
}
}
// Add cache contexts to ensure this token functions on a per-path basis
$bubbleable_metadata
->addCacheContexts([
'url.path',
]);
$replacements += \Drupal::token()
->generate('url', $url_tokens, [
'url' => $url,
], $options, $bubbleable_metadata);
}
}
// URL tokens.
if ($type == 'url' && !empty($data['url'])) {
/** @var \Drupal\Core\Url $url */
$url = $data['url'];
// To retrieve the correct path, modify a copy of the Url object.
$path_url = clone $url;
$path = '/';
// Ensure the URL is routed to avoid throwing an exception.
if ($url
->isRouted()) {
$path .= $path_url
->setAbsolute(FALSE)
->setOption('fragment', NULL)
->getInternalPath();
}
foreach ($tokens as $name => $original) {
switch ($name) {
case 'path':
$value = !$url
->getOption('alias') ? \Drupal::service('path_alias.manager')
->getAliasByPath($path, $langcode) : $path;
$replacements[$original] = $value;
break;
case 'alias':
// @deprecated
$alias = \Drupal::service('path_alias.manager')
->getAliasByPath($path, $langcode);
$replacements[$original] = $alias;
break;
case 'absolute':
$result = $url
->setAbsolute()
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
case 'relative':
$result = $url
->setAbsolute(FALSE)
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
case 'brief':
$result = $url
->setAbsolute()
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = preg_replace([
'!^https?://!',
'!/$!',
], '', $result
->getGeneratedUrl());
break;
case 'unaliased':
$unaliased = clone $url;
$result = $unaliased
->setAbsolute()
->setOption('alias', TRUE)
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
case 'args':
$value = !$url
->getOption('alias') ? \Drupal::service('path_alias.manager')
->getAliasByPath($path, $langcode) : $path;
$replacements[$original] = token_render_array(explode('/', $value), $options);
break;
}
}
// [url:args:*] chained tokens.
if ($arg_tokens = \Drupal::token()
->findWithPrefix($tokens, 'args')) {
$value = !$url
->getOption('alias') ? \Drupal::service('path_alias.manager')
->getAliasByPath($path, $langcode) : $path;
$replacements += \Drupal::token()
->generate('array', $arg_tokens, [
'array' => explode('/', ltrim($value, '/')),
], $options, $bubbleable_metadata);
}
// [url:unaliased:*] chained tokens.
if ($unaliased_tokens = \Drupal::token()
->findWithPrefix($tokens, 'unaliased')) {
$url
->setOption('alias', TRUE);
$replacements += \Drupal::token()
->generate('url', $unaliased_tokens, [
'url' => $url,
], $options, $bubbleable_metadata);
}
}
// Entity tokens.
if (!empty($data[$type]) && ($entity_type = \Drupal::service('token.entity_mapper')
->getEntityTypeForTokenType($type))) {
/* @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $data[$type];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'url':
if (_token_module($type, 'url') === 'token' && !$entity
->isNew() && $entity
->hasLinkTemplate('canonical')) {
$result = $entity
->toUrl('canonical')
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
}
break;
case 'original':
if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
$label = $entity->original
->label();
$replacements[$original] = $label;
}
break;
}
}
// [entity:url:*] chained tokens.
if (($url_tokens = \Drupal::token()
->findWithPrefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
$replacements += \Drupal::token()
->generate('url', $url_tokens, [
'url' => $entity
->toUrl(),
], $options, $bubbleable_metadata);
}
// [entity:original:*] chained tokens.
if (($original_tokens = \Drupal::token()
->findWithPrefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
$replacements += \Drupal::token()
->generate($type, $original_tokens, [
$type => $entity->original,
], $options, $bubbleable_metadata);
}
// [entity:language:*] chained tokens.
if (($language_tokens = \Drupal::token()
->findWithPrefix($tokens, 'language')) && _token_module($type, 'language') == 'token') {
$language_options = array_merge($options, [
'langcode' => $entity
->get('langcode')->value,
]);
$replacements += \Drupal::token()
->generate('language', $language_tokens, [], $language_options, $bubbleable_metadata);
}
// Pass through to an generic 'entity' token type generation.
$entity_data = [
'entity_type' => $entity_type,
'entity' => $entity,
'token_type' => $type,
];
// @todo Investigate passing through more data like everything from entity_extract_ids().
$replacements += \Drupal::token()
->generate('entity', $tokens, $entity_data, $options, $bubbleable_metadata);
}
// Array tokens.
if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
$array = $data['array'];
$sort = isset($options['array sort']) ? $options['array sort'] : TRUE;
$keys = token_element_children($array, $sort);
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
foreach ($tokens as $name => $original) {
switch ($name) {
case 'first':
$value = $array[$keys[0]];
$value = is_array($value) ? $renderer
->renderPlain($value) : (string) $value;
$replacements[$original] = $value;
break;
case 'last':
$value = $array[$keys[count($keys) - 1]];
$value = is_array($value) ? $renderer
->renderPlain($value) : (string) $value;
$replacements[$original] = $value;
break;
case 'count':
$replacements[$original] = count($keys);
break;
case 'keys':
$replacements[$original] = token_render_array($keys, $options);
break;
case 'reversed':
$reversed = array_reverse($array, TRUE);
$replacements[$original] = token_render_array($reversed, $options);
break;
case 'join':
$replacements[$original] = token_render_array($array, [
'join' => '',
] + $options);
break;
}
}
// [array:value:*] dynamic tokens.
if ($value_tokens = \Drupal::token()
->findWithPrefix($tokens, 'value')) {
foreach ($value_tokens as $key => $original) {
if ((is_int($key) || $key[0] !== '#') && isset($array[$key])) {
$replacements[$original] = token_render_array_value($array[$key], $options);
}
}
}
// [array:join:*] dynamic tokens.
if ($join_tokens = \Drupal::token()
->findWithPrefix($tokens, 'join')) {
foreach ($join_tokens as $join => $original) {
$replacements[$original] = token_render_array($array, [
'join' => $join,
] + $options);
}
}
// [array:keys:*] chained tokens.
if ($key_tokens = \Drupal::token()
->findWithPrefix($tokens, 'keys')) {
$replacements += \Drupal::token()
->generate('array', $key_tokens, [
'array' => $keys,
], $options, $bubbleable_metadata);
}
// [array:reversed:*] chained tokens.
if ($reversed_tokens = \Drupal::token()
->findWithPrefix($tokens, 'reversed')) {
$replacements += \Drupal::token()
->generate('array', $reversed_tokens, [
'array' => array_reverse($array, TRUE),
], [
'array sort' => FALSE,
] + $options, $bubbleable_metadata);
}
// @todo Handle if the array values are not strings and could be chained.
}
// Random tokens.
if ($type == 'random') {
foreach ($tokens as $name => $original) {
switch ($name) {
case 'number':
$replacements[$original] = mt_rand();
break;
}
}
// [custom:hash:*] dynamic token.
if ($hash_tokens = \Drupal::token()
->findWithPrefix($tokens, 'hash')) {
$algos = hash_algos();
foreach ($hash_tokens as $name => $original) {
if (in_array($name, $algos)) {
$replacements[$original] = hash($name, random_bytes(55));
}
}
}
}
// If $type is a token type, $data[$type] is empty but $data[$entity_type] is
// not, re-run token replacements.
if (empty($data[$type]) && ($entity_type = \Drupal::service('token.entity_mapper')
->getEntityTypeForTokenType($type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
$data[$type] = $data[$entity_type];
$options['recursive'] = TRUE;
$replacements += \Drupal::moduleHandler()
->invokeAll('tokens', [
$type,
$tokens,
$data,
$options,
$bubbleable_metadata,
]);
}
// If the token type specifics a 'needs-data' value, and the value is not
// present in $data, then throw an error.
if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
// Only check when tests are running.
$type_info = \Drupal::token()
->getTypeInfo($type);
if (!empty($type_info['needs-data']) && !isset($data[$type_info['needs-data']])) {
trigger_error(t('Attempting to perform token replacement for token type %type without required data', [
'%type' => $type,
]), E_USER_WARNING);
}
}
return $replacements;
}
/**
* Implements hook_token_info() on behalf of book.module.
*/
function book_token_info() {
$info['types']['book'] = [
'name' => t('Book'),
'description' => t('Tokens related to books.'),
'needs-data' => 'book',
];
$info['tokens']['book']['title'] = [
'name' => t('Title'),
'description' => t('Title of the book.'),
];
$info['tokens']['book']['author'] = [
'name' => t('Author'),
'description' => t('The author of the book.'),
'type' => 'user',
];
$info['tokens']['book']['root'] = [
'name' => t('Root'),
'description' => t('Top level of the book.'),
'type' => 'node',
];
$info['tokens']['book']['parent'] = [
'name' => t('Parent'),
'description' => t('Parent of the current page.'),
'type' => 'node',
];
$info['tokens']['book']['parents'] = [
'name' => t('Parents'),
'description' => t("An array of all the node's parents, starting with the root."),
'type' => 'array',
];
$info['tokens']['node']['book'] = [
'name' => t('Book'),
'description' => t('The book page associated with the node.'),
'type' => 'book',
];
return $info;
}
/**
* Implements hook_tokens() on behalf of book.module.
*/
function book_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
$replacements = [];
// Node tokens.
if ($type == 'node' && !empty($data['node'])) {
$book = $data['node']->book;
if (!empty($book['bid'])) {
if ($book_tokens = \Drupal::token()
->findWithPrefix($tokens, 'book')) {
$child_node = Node::load($book['nid']);
$replacements += \Drupal::token()
->generate('book', $book_tokens, [
'book' => $child_node,
], $options, $bubbleable_metadata);
}
}
}
elseif ($type == 'book' && !empty($data['book'])) {
$book = $data['book']->book;
if (!empty($book['bid'])) {
$book_node = Node::load($book['bid']);
foreach ($tokens as $name => $original) {
switch ($name) {
case 'root':
case 'title':
$replacements[$original] = $book_node
->getTitle();
break;
case 'parent':
if (!empty($book['pid'])) {
$parent_node = Node::load($book['pid']);
$replacements[$original] = $parent_node
->getTitle();
}
break;
case 'parents':
if ($parents = token_book_load_all_parents($book)) {
$replacements[$original] = token_render_array($parents, $options);
}
break;
}
}
if ($book_tokens = \Drupal::token()
->findWithPrefix($tokens, 'author')) {
$replacements += \Drupal::token()
->generate('user', $book_tokens, [
'user' => $book_node
->getOwner(),
], $options, $bubbleable_metadata);
}
if ($book_tokens = \Drupal::token()
->findWithPrefix($tokens, 'root')) {
$replacements += \Drupal::token()
->generate('node', $book_tokens, [
'node' => $book_node,
], $options, $bubbleable_metadata);
}
if (!empty($book['pid']) && ($book_tokens = \Drupal::token()
->findWithPrefix($tokens, 'parent'))) {
$parent_node = Node::load($book['pid']);
$replacements += \Drupal::token()
->generate('node', $book_tokens, [
'node' => $parent_node,
], $options, $bubbleable_metadata);
}
if ($book_tokens = \Drupal::token()
->findWithPrefix($tokens, 'parents')) {
$parents = token_book_load_all_parents($book);
$replacements += \Drupal::token()
->generate('array', $book_tokens, [
'array' => $parents,
], $options, $bubbleable_metadata);
}
}
}
return $replacements;
}
/**
* Implements hook_token_info() on behalf of menu_ui.module.
*/
function menu_ui_token_info() {
// Menu tokens.
$info['types']['menu'] = [
'name' => t('Menus'),
'description' => t('Tokens related to menus.'),
'needs-data' => 'menu',
];
$info['tokens']['menu']['name'] = [
'name' => t('Name'),
'description' => t("The name of the menu."),
];
$info['tokens']['menu']['machine-name'] = [
'name' => t('Machine-readable name'),
'description' => t("The unique machine-readable name of the menu."),
];
$info['tokens']['menu']['description'] = [
'name' => t('Description'),
'description' => t('The optional description of the menu.'),
];
$info['tokens']['menu']['menu-link-count'] = [
'name' => t('Menu link count'),
'description' => t('The number of menu links belonging to the menu.'),
];
$info['tokens']['menu']['edit-url'] = [
'name' => t('Edit URL'),
'description' => t("The URL of the menu's edit page."),
];
$info['tokens']['menu-link']['menu'] = [
'name' => t('Menu'),
'description' => t('The menu of the menu link.'),
'type' => 'menu',
];
$info['tokens']['menu-link']['edit-url'] = [
'name' => t('Edit URL'),
'description' => t("The URL of the menu link's edit page."),
];
if (\Drupal::moduleHandler()
->moduleExists('node')) {
$info['tokens']['node']['menu-link'] = [
'name' => t('Menu link'),
'description' => t("The menu link for this node."),
'type' => 'menu-link',
];
}
return $info;
}
/**
* Implements hook_tokens() on behalf of menu_ui.module.
*/
function menu_ui_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
$replacements = [];
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
$url_options = [
'absolute' => TRUE,
];
if (isset($options['langcode'])) {
$url_options['language'] = \Drupal::languageManager()
->getLanguage($options['langcode']);
$langcode = $options['langcode'];
}
else {
$langcode = NULL;
}
// Node tokens.
if ($type == 'node' && !empty($data['node'])) {
/** @var \Drupal\node\NodeInterface $node */
$node = $data['node'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'menu-link':
// On node-form save we populate a calculated field with a menu_link
// references.
// @see token_node_menu_link_submit()
if ($node
->getFieldDefinition('menu_link') && ($menu_link = $node->menu_link->entity)) {
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
$replacements[$original] = $menu_link
->getTitle();
}
else {
$url = $node
->toUrl();
if ($links = $menu_link_manager
->loadLinksByRoute($url
->getRouteName(), $url
->getRouteParameters())) {
$link = _token_menu_link_best_match($node, $links);
$replacements[$original] = token_menu_link_translated_title($link, $langcode);
}
}
break;
}
// Chained token relationships.
if ($menu_tokens = \Drupal::token()
->findWithPrefix($tokens, 'menu-link')) {
if ($node
->getFieldDefinition('menu_link') && ($menu_link = $node->menu_link->entity)) {
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
$replacements += \Drupal::token()
->generate('menu-link', $menu_tokens, [
'menu-link' => $menu_link,
], $options, $bubbleable_metadata);
}
else {
$url = $node
->toUrl();
if ($links = $menu_link_manager
->loadLinksByRoute($url
->getRouteName(), $url
->getRouteParameters())) {
$link = _token_menu_link_best_match($node, $links);
$replacements += \Drupal::token()
->generate('menu-link', $menu_tokens, [
'menu-link' => $link,
], $options, $bubbleable_metadata);
}
}
}
}
}
// Menu link tokens.
if ($type == 'menu-link' && !empty($data['menu-link'])) {
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
$link = $data['menu-link'];
if ($link instanceof MenuLinkContentInterface) {
$link = $menu_link_manager
->createInstance($link
->getPluginId());
}
foreach ($tokens as $name => $original) {
switch ($name) {
case 'menu':
if ($menu = Menu::load($link
->getMenuName())) {
$replacements[$original] = $menu
->label();
}
break;
case 'edit-url':
$result = $link
->getEditRoute()
->setOptions($url_options)
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
}
}
// Chained token relationships.
if (($menu_tokens = \Drupal::token()
->findWithPrefix($tokens, 'menu')) && ($menu = Menu::load($link
->getMenuName()))) {
$replacements += \Drupal::token()
->generate('menu', $menu_tokens, [
'menu' => $menu,
], $options, $bubbleable_metadata);
}
}
// Menu tokens.
if ($type == 'menu' && !empty($data['menu'])) {
/** @var \Drupal\system\MenuInterface $menu */
$menu = $data['menu'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'name':
$replacements[$original] = $menu
->label();
break;
case 'machine-name':
$replacements[$original] = $menu
->id();
break;
case 'description':
$replacements[$original] = $menu
->getDescription();
break;
case 'menu-link-count':
$replacements[$original] = $menu_link_manager
->countMenuLinks($menu
->id());
break;
case 'edit-url':
$result = Url::fromRoute('entity.menu.edit_form', [
'menu' => $menu
->id(),
], $url_options)
->toString(TRUE);
$bubbleable_metadata
->addCacheableDependency($result);
$replacements[$original] = $result
->getGeneratedUrl();
break;
}
}
}
return $replacements;
}
/**
* Returns a best matched link for a given node.
*
* If the url exists in multiple menus, default to the one set on the node
* itself.
*
* @param \Drupal\node\NodeInterface $node
* The node to look up the default menu settings from.
* @param array $links
* An array of instances keyed by plugin ID.
*
* @return \Drupal\Core\Menu\MenuLinkInterface
* A Link instance.
*/
function _token_menu_link_best_match(NodeInterface $node, array $links) {
// Get the menu ui defaults so we can determine what menu was
// selected for this node. This ensures that if the node was added
// to the menu via the node UI, we use that as a default. If it
// was not added via the node UI then grab the first in the
// retrieved array.
$defaults = menu_ui_get_menu_link_defaults($node);
if (isset($defaults['id']) && isset($links[$defaults['id']])) {
$link = $links[$defaults['id']];
}
else {
$link = reset($links);
}
return $link;
}
/**
* Implements hook_token_info_alter() on behalf of field.module.
*
* We use hook_token_info_alter() rather than hook_token_info() as other
* modules may already have defined some field tokens.
*/
function field_token_info_alter(&$info) {
$type_info = \Drupal::service('plugin.manager.field.field_type')
->getDefinitions();
// Attach field tokens to their respecitve entity tokens.
foreach (\Drupal::entityTypeManager()
->getDefinitions() as $entity_type_id => $entity_type) {
if (!$entity_type
->entityClassImplements(ContentEntityInterface::class)) {
continue;
}
// Make sure a token type exists for this entity.
$token_type = \Drupal::service('token.entity_mapper')
->getTokenTypeForEntityType($entity_type_id);
if (empty($token_type) || !isset($info['types'][$token_type])) {
continue;
}
$fields = \Drupal::service('entity_field.manager')
->getFieldStorageDefinitions($entity_type_id);
foreach ($fields as $field_name => $field) {
/** @var \Drupal\field\FieldStorageConfigInterface $field */
// Ensure the token implements FieldStorageConfigInterface or is defined
// in token module.
$provider = '';
if (isset($info['types'][$token_type]['module'])) {
$provider = $info['types'][$token_type]['module'];
}
if (!$field instanceof FieldStorageConfigInterface && $provider != 'token') {
continue;
}
// If a token already exists for this field, then don't add it.
if (isset($info['tokens'][$token_type][$field_name])) {
continue;
}
if ($token_type == 'comment' && $field_name == 'comment_body') {
// Core provides the comment field as [comment:body].
continue;
}
// Do not define the token type if the field has no properties.
if (!$field
->getPropertyDefinitions()) {
continue;
}
// Generate a description for the token.
$labels = _token_field_label($entity_type_id, $field_name);
$label = array_shift($labels);
$params['@type'] = $type_info[$field
->getType()]['label'];
if (!empty($labels)) {
$params['%labels'] = implode(', ', $labels);
$description = t('@type field. Also known as %labels.', $params);
}
else {
$description = t('@type field.', $params);
}
$cardinality = $field
->getCardinality();
$cardinality = $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $cardinality > 3 ? 3 : $cardinality;
$field_token_name = $token_type . '-' . $field_name;
$info['tokens'][$token_type][$field_name] = [
'name' => Html::escape($label),
'description' => $description,
'module' => 'token',
// For multivalue fields the field token is a list type.
'type' => $cardinality > 1 ? "list<{$field_token_name}>" : $field_token_name,
];
// Field token type.
$info['types'][$field_token_name] = [
'name' => Html::escape($label),
'description' => t('@label tokens.', [
'@label' => Html::escape($label),
]),
'needs-data' => $field_token_name,
'nested' => TRUE,
];
// Field list token type.
if ($cardinality > 1) {
$info['types']["list<{$field_token_name}>"] = [
'name' => t('List of @type values', [
'@type' => Html::escape($label),
]),
'description' => t('Tokens for lists of @type values.', [
'@type' => Html::escape($label),
]),
'needs-data' => "list<{$field_token_name}>",
'nested' => TRUE,
];
}
// Show a different token for each field delta.
if ($cardinality > 1) {
for ($delta = 0; $delta < $cardinality; $delta++) {
$info['tokens']["list<{$field_token_name}>"][$delta] = [
'name' => t('@type type with delta @delta', [
'@type' => Html::escape($label),
'@delta' => $delta,
]),
'module' => 'token',
'type' => $field_token_name,
];
}
}
// Property tokens.
foreach ($field
->getPropertyDefinitions() as $property => $property_definition) {
if (is_subclass_of($property_definition
->getClass(), 'Drupal\\Core\\TypedData\\PrimitiveInterface')) {
$info['tokens'][$field_token_name][$property] = [
'name' => $property_definition
->getLabel(),
'description' => $property_definition
->getDescription(),
'module' => 'token',
];
}
elseif ($property_definition instanceof DataReferenceDefinitionInterface && $property_definition
->getTargetDefinition() instanceof EntityDataDefinitionInterface) {
$referenced_entity_type = $property_definition
->getTargetDefinition()
->getEntityTypeId();
$referenced_token_type = \Drupal::service('token.entity_mapper')
->getTokenTypeForEntityType($referenced_entity_type);
$info['tokens'][$field_token_name][$property] = [
'name' => $property_definition
->getLabel(),
'description' => $property_definition
->getDescription(),
'module' => 'token',
'type' => $referenced_token_type,
];
}
}
// Provide image_with_image_style tokens for image fields.
if ($field
->getType() == 'image') {
$image_styles = image_style_options(FALSE);
foreach ($image_styles as $style => $description) {
$info['tokens'][$field_token_name][$style] = [
'name' => $description,
'description' => t('Represents the image in the given image style.'),
'type' => 'image_with_image_style',
];
}
}
// Provide format token for datetime fields.
$date_fields = [
'datetime',
'timestamp',
'created',
'changed',
];
if (in_array($field
->getType(), $date_fields, TRUE)) {
$info['tokens'][$field_token_name]['date'] = $info['tokens'][$field_token_name]['value'];
$info['tokens'][$field_token_name]['date']['name'] .= ' ' . t('format');
$info['tokens'][$field_token_name]['date']['type'] = 'date';
}
if ($field
->getType() == 'daterange' || $field
->getType() == 'date_recur') {
$info['tokens'][$field_token_name]['start_date'] = $info['tokens'][$field_token_name]['value'];
$info['tokens'][$field_token_name]['start_date']['name'] .= ' ' . t('format');
$info['tokens'][$field_token_name]['start_date']['type'] = 'date';
$info['tokens'][$field_token_name]['end_date'] = $info['tokens'][$field_token_name]['end_value'];
$info['tokens'][$field_token_name]['end_date']['name'] .= ' ' . t('format');
$info['tokens'][$field_token_name]['end_date']['type'] = 'date';
}
}
}
}
/**
* Returns the label of a certain field.
*
* Therefore it looks up in all bundles to find the most used instance.
*
* Based on views_entity_field_label().
*
* @todo Resync this method with views_entity_field_label().
*/
function _token_field_label($entity_type, $field_name) {
$labels = [];
// Count the amount of instances per label per field.
foreach (array_keys(\Drupal::service('entity_type.bundle.info')
->getBundleInfo($entity_type)) as $bundle) {
$bundle_instances = \Drupal::service('entity_field.manager')
->getFieldDefinitions($entity_type, $bundle);
if (isset($bundle_instances[$field_name])) {
$instance = $bundle_instances[$field_name];
$label = (string) $instance
->getLabel();
$labels[$label] = isset($labels[$label]) ? ++$labels[$label] : 1;
}
}
if (empty($labels)) {
return [
$field_name,
];
}
// Sort the field labels by it most used label and return the labels.
arsort($labels);
return array_keys($labels);
}
/**
* Implements hook_tokens() on behalf of field.module.
*/
function field_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
$replacements = [];
$langcode = isset($options['langcode']) ? $options['langcode'] : NULL;
// Entity tokens.
if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {
/* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $data['entity'];
if (!$entity instanceof ContentEntityInterface) {
return $replacements;
}
if (!isset($options['langcode'])) {
// Set the active language in $options, so that it is passed along.
$langcode = $options['langcode'] = $entity
->language()
->getId();
}
// Obtain the entity with the correct language.
$entity = \Drupal::service('entity.repository')
->getTranslationFromContext($entity, $langcode);
foreach ($tokens as $name => $original) {
// For the [entity:field_name] token.
if (strpos($name, ':') === FALSE) {
$field_name = $name;
$token_name = $name;
}
else {
list($field_name, $delta) = explode(':', $name, 2);
if (!is_numeric($delta)) {
unset($delta);
}
$token_name = $field_name;
}
// Ensure the entity has the requested field and that the token for it is
// defined by token.module.
if (!$entity
->hasField($field_name) || _token_module($data['token_type'], $token_name) != 'token') {
continue;
}
$display_options = 'token';
// Do not continue if the field is empty.
if ($entity
->get($field_name)
->isEmpty()) {
continue;
}
// Handle [entity:field_name] and [entity:field_name:0] tokens.
if ($field_name === $name || isset($delta)) {
$view_display = token_get_token_view_display($entity);
if (!$view_display) {
// We don't have the token view display and should fall back on
// default formatters. If the field has specified a specific formatter
// to be used by default with tokens, use that, otherwise use the
// default formatter.
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$field_type_definition = $field_type_manager
->getDefinition($entity
->getFieldDefinition($field_name)
->getType());
$display_options = [
'type' => !empty($field_type_definition['default_token_formatter']) ? $field_type_definition['default_token_formatter'] : $field_type_definition['default_formatter'],
'label' => 'hidden',
];
}
// Render only one delta.
if (isset($delta)) {
if ($field_delta = $entity->{$field_name}[$delta]) {
$field_output = $field_delta
->view($display_options);
}
else {
continue;
}
}
else {
$field_output = $entity->{$field_name}
->view($display_options);
// If we are displaying all field items we need this #pre_render
// callback.
$field_output['#pre_render'][] = '\\Drupal\\token\\TokenFieldRender::preRender';
}
$field_output['#token_options'] = $options;
$replacements[$original] = \Drupal::service('renderer')
->renderPlain($field_output);
}
elseif ($field_tokens = \Drupal::token()
->findWithPrefix($tokens, $field_name)) {
// With multiple nested tokens for the same field name, this might
// match the same field multiple times. Filter out those that have
// already been replaced.
$field_tokens = array_filter($field_tokens, function ($token) use ($replacements) {
return !isset($replacements[$token]);
});
if ($field_tokens) {
$property_token_data = [
'field_property' => TRUE,
$data['entity_type'] . '-' . $field_name => $entity->{$field_name},
'field_name' => $data['entity_type'] . '-' . $field_name,
];
// The optimization above only works on the root level, if there is
// more than one nested field it would still replace all in every
// call. Replace them one by one instead, which is slightly slower
// but ensures that the number of replacements do not grow
// exponentialy.
foreach ($field_tokens as $field_token_key => $field_token_value) {
$replacements += \Drupal::token()
->generate($field_name, [
$field_token_key => $field_token_value,
], $property_token_data, $options, $bubbleable_metadata);
}
}
}
}
// Remove the cloned object from memory.
unset($entity);
}
elseif (!empty($data['field_property'])) {
foreach ($tokens as $token => $original) {
$filtered_tokens = $tokens;
$delta = 0;
$parts = explode(':', $token);
if (is_numeric($parts[0])) {
if (count($parts) > 1) {
$delta = $parts[0];
$property_name = $parts[1];
// Pre-filter the tokens to select those with the correct delta.
$filtered_tokens = \Drupal::token()
->findWithPrefix($tokens, $delta);
// Remove the delta to unify between having and not having one.
array_shift($parts);
}
else {
// Token is fieldname:delta, which is invalid.
continue;
}
}
else {
$property_name = $parts[0];
}
if (isset($data[$data['field_name']][$delta])) {
$field_item = $data[$data['field_name']][$delta];
}
else {
// The field has no such delta, abort replacement.
continue;
}
if (isset($field_item->{$property_name}) && $field_item->{$property_name} instanceof FieldableEntityInterface) {
// Entity reference field.
$entity = $field_item->{$property_name};
// Obtain the referenced entity with the correct language.
$entity = \Drupal::service('entity.repository')
->getTranslationFromContext($entity, $langcode);
if (count($parts) > 1) {
$field_tokens = \Drupal::token()
->findWithPrefix($filtered_tokens, $property_name);
$token_type = \Drupal::service('token.entity_mapper')
->getTokenTypeForEntityType($entity
->getEntityTypeId(), TRUE);
$replacements += \Drupal::token()
->generate($token_type, $field_tokens, [
$token_type => $entity,
], $options, $bubbleable_metadata);
}
else {
$replacements[$original] = $entity
->label();
}
}
elseif ($field_item
->getFieldDefinition()
->getType() == 'image' && ($style = ImageStyle::load($property_name))) {
// Handle [node:field_name:image_style:property] tokens and multivalued
// [node:field_name:delta:image_style:property] tokens. If the token is
// of the form [node:field_name:image_style], provide the URL as a
// replacement.
$property_name = isset($parts[1]) ? $parts[1] : 'url';
$entity = $field_item->entity;
if (!empty($field_item->entity)) {
$original_uri = $entity
->getFileUri();
// Only generate the image derivative if needed.
if ($property_name === 'width' || $property_name === 'height') {
$dimensions = [
'width' => $field_item->width,
'height' => $field_item->height,
];
$style
->transformDimensions($dimensions, $original_uri);
$replacements[$original] = $dimensions[$property_name];
}
elseif ($property_name === 'uri') {
$replacements[$original] = $style
->buildUri($original_uri);
}
elseif ($property_name === 'url') {
$replacements[$original] = $style
->buildUrl($original_uri);
}
else {
// Generate the image derivative, if it doesn't already exist.
$derivative_uri = $style
->buildUri($original_uri);
$derivative_exists = TRUE;
if (!file_exists($derivative_uri)) {
$derivative_exists = $style
->createDerivative($original_uri, $derivative_uri);
}
if ($derivative_exists) {
$image = \Drupal::service('image.factory')
->get($derivative_uri);
// Provide the replacement.
switch ($property_name) {
case 'mimetype':
$replacements[$original] = $image
->getMimeType();
break;
case 'filesize':
$replacements[$original] = $image
->getFileSize();
break;
}
}
}
}
}
elseif (in_array($field_item
->getFieldDefinition()
->getType(), [
'datetime',
'daterange',
'date_recur',
]) && in_array($property_name, [
'date',
'start_date',
'end_date',
]) && !empty($field_item->{$property_name})) {
$timestamp = $field_item->{$property_name}
->getTimestamp();
// If the token is an exact match for the property or the delta and the
// property, use the timestamp as-is.
if ($property_name == $token || "{$delta}:{$property_name}" == $token) {
$replacements[$original] = $timestamp;
}
else {
$date_tokens = \Drupal::token()
->findWithPrefix($filtered_tokens, $property_name);
$replacements += \Drupal::token()
->generate('date', $date_tokens, [
'date' => $timestamp,
], $options, $bubbleable_metadata);
}
}
elseif (in_array($field_item
->getFieldDefinition()
->getType(), [
'timestamp',
'created',
'changed',
]) && in_array($property_name, [
'date',
])) {
$timestamp = $field_item->value;
if ($property_name == $token || "{$delta}:{$property_name}" == $token) {
$replacements[$original] = $timestamp;
}
else {
$field_tokens = \Drupal::token()
->findWithPrefix($filtered_tokens, $property_name);
$replacements += \Drupal::token()
->generate('date', $field_tokens, [
'date' => $timestamp,
], $options, $bubbleable_metadata);
}
}
else {
$replacements[$original] = $field_item->{$property_name};
}
}
}
return $replacements;
}
/**
* Returns the token view display for the given entity if enabled.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*
* @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface|null
* The view display or null.
*/
function token_get_token_view_display(EntityInterface $entity) {
$view_mode_name = $entity
->getEntityTypeId() . '.' . $entity
->bundle() . '.token';
$view_display = \Drupal::entityTypeManager()
->getStorage('entity_view_display')
->load($view_mode_name);
return $view_display && $view_display
->status() ? $view_display : NULL;
}
Functions
Name | Description |
---|---|
book_tokens | Implements hook_tokens() on behalf of book.module. |
book_token_info | Implements hook_token_info() on behalf of book.module. |
field_tokens | Implements hook_tokens() on behalf of field.module. |
field_token_info_alter | Implements hook_token_info_alter() on behalf of field.module. |
menu_ui_tokens | Implements hook_tokens() on behalf of menu_ui.module. |
menu_ui_token_info | Implements hook_token_info() on behalf of menu_ui.module. |
token_get_token_view_display | Returns the token view display for the given entity if enabled. |
token_tokens | Implements hook_tokens(). |
token_token_info | Implements hook_token_info(). |
token_token_info_alter | Implements hook_token_info_alter(). |
_token_field_label | Returns the label of a certain field. |
_token_menu_link_best_match | Returns a best matched link for a given node. |