Skip to content
token.tokens.inc 55.6 KiB
Newer Older
<?php

/**
 * @file
 * Token callbacks for the token module.
 */
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
use Drupal\Core\Render\Element;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Html;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
Arild Matsson's avatar
Arild Matsson committed
use Drupal\Core\Url;
use Drupal\field\FieldStorageConfigInterface;
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
use Drupal\menu_link_content\MenuLinkContentInterface;
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
use Drupal\system\Entity\Menu;
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
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;

/**
 * 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'] = array(
    '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;
  // The [file:size] may not always return in kilobytes.
  // @todo Remove when http://drupal.org/node/1193044 is fixed.
  if (!empty($info['tokens']['file']['size'])) {
    $info['tokens']['file']['size']['description'] = t('The size of the file.');
  }
  // Remove deprecated tokens from being listed.
  unset($info['tokens']['node']['tnid']);
  unset($info['tokens']['node']['type']);
  unset($info['tokens']['node']['type-name']);
  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 => $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)) {
    $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'] = array(
        'name' => t('URL'),
        'description' => t('The URL of the @entity.', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),

    // Add [entity:original] tokens if they do not already exist.
    if (!isset($info['tokens'][$token_type]['original'])) {
      $info['tokens'][$token_type]['original'] = array(
        'name' => t('Original @entity', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
        'description' => t('The original @entity data if the @entity is being updated or saved.', array('@entity' => Unicode::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] = array(
        'name' => Html::escape($date_format_type_info->label()),
        'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => format_date(REQUEST_TIME, $date_format_type))),
        'module' => 'token',
      );
    }
  }
}

/**
 * Implements hook_token_info().
 */
function token_token_info() {
  $info['tokens']['node']['source'] = array(
    'name' => t('Translation source node'),
    'description' => t("The source node for this current node's translation set."),
    'type' => 'node',
  );
  $info['tokens']['node']['log'] = array(
    'name' => t('Revision log message'),
    'description' => t('The explanation of the most recent changes made to the node.'),
  );
  $info['tokens']['node']['content-type'] = array(
    'name' => t('Content type'),
    'description' => t('The content type of the node.'),
    'type' => 'content-type',
  );

  // Content type tokens.
  $info['types']['content-type'] = array(
    'description' => t('Tokens related to content types.'),
  );
  $info['tokens']['content-type']['name'] = array(
    'name' => t('Name'),
    'description' => t('The name of the content type.'),
  );
  $info['tokens']['content-type']['machine-name'] = array(
    'name' => t('Machine-readable name'),
    'description' => t('The unique machine-readable name of the content type.'),
  );
  $info['tokens']['content-type']['description'] = array(
    'name' => t('Description'),
    'description' => t('The optional description of the content type.'),
  );
  $info['tokens']['content-type']['node-count'] = array(
    'name' => t('Node count'),
    'description' => t('The number of nodes belonging to the content type.'),
  $info['tokens']['content-type']['edit-url'] = array(
    'name' => t('Edit URL'),
    'description' => t("The URL of the content type's edit page."),
    // 'type' => 'url',
  if (\Drupal::moduleHandler()->moduleExists('taxonomy')) {
    $info['tokens']['term']['edit-url'] = array(
      'name' => t('Edit URL'),
      'description' => t("The URL of the taxonomy term's edit page."),
      // 'type' => 'url',
    $info['tokens']['term']['parents'] = array(
      'name' => t('Parents'),
      'description' => t("An array of all the term's parents, starting with the root."),
      'type' => 'array',
    );
    $info['tokens']['term']['root'] = array(
      'name' => t('Root term'),
      'description' => t("The root term of the taxonomy term."),
      'type' => 'term',
    );

    $info['tokens']['vocabulary']['machine-name'] = array(
      'name' => t('Machine-readable name'),
      'description' => t('The unique machine-readable name of the vocabulary.'),
    );
    $info['tokens']['vocabulary']['edit-url'] = array(
      'name' => t('Edit URL'),
      'description' => t("The URL of the vocabulary's edit page."),
      // 'type' => 'url',
  // File tokens.
  $info['tokens']['file']['basename'] = array(
    'name' => t('Base name'),
    'description' => t('The base name of the file.'),
  );
  $info['tokens']['file']['extension'] = array(
    'name' => t('Extension'),
    'description' => t('The extension of the file.'),
  $info['tokens']['file']['size-raw'] = array(
    'name' => t('File byte size'),
    'description' => t('The size of the file, in bytes.'),
  );
  // Add information on the restricted user tokens.
  $info['tokens']['user']['cancel-url'] = array(
    'name' => t('Account cancellation URL'),
    'description' => t('The URL of the confirm delete page for the user account.'),
    'restricted' => TRUE,
    // 'type' => 'url',
  );
  $info['tokens']['user']['one-time-login-url'] = array(
    'name' => t('One-time login URL'),
    'description' => t('The URL of the one-time login page for the user account.'),
    'restricted' => TRUE,
    // 'type' => 'url',
  $info['tokens']['user']['roles'] = array(
    'name' => t('Roles'),
    'description' => t('The user roles associated with the user account.'),
    'type' => 'array',
  );
  // Current user tokens.
  $info['tokens']['current-user']['ip-address'] = array(
    'name' => t('IP address'),
    'description' => 'The IP address of the current user.',
  );

  // Menu link tokens (work regardless if menu module is enabled or not).
  $info['types']['menu-link'] = array(
    'name' => t('Menu links'),
    'description' => t('Tokens related to menu links.'),
    'needs-data' => 'menu-link',
  );
  $info['tokens']['menu-link']['mlid'] = array(
    'name' => t('Link ID'),
    'description' => t('The unique ID of the menu link.'),
  );
  $info['tokens']['menu-link']['title'] = array(
    'name' => t('Title'),
    'description' => t('The title of the menu link.'),
  );
  $info['tokens']['menu-link']['url'] = array(
    'name' => t('URL'),
    'description' => t('The URL of the menu link.'),
  );
  $info['tokens']['menu-link']['parent'] = array(
    'name' => t('Parent'),
    'description' => t("The menu link's parent."),
    'type' => 'menu-link',
  );
  $info['tokens']['menu-link']['parents'] = array(
    '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'] = array(
    'name' => t('Root'),
    'description' => t("The menu link's root."),
    'type' => 'menu-link',
  );
  // Current page tokens.
  $info['types']['current-page'] = array(
    'name' => t('Current page'),
    'description' => t('Tokens related to the current page request.'),
  );
  $info['tokens']['current-page']['title'] = array(
    'name' => t('Title'),
    'description' => t('The title of the current page.'),
  );
  $info['tokens']['current-page']['url'] = array(
    'name' => t('URL'),
    'description' => t('The URL of the current page.'),
  );
  $info['tokens']['current-page']['page-number'] = array(
    'name' => t('Page number'),
    'description' => t('The page number of the current page when viewing paged lists.'),
  );
  $info['tokens']['current-page']['query'] = array(
    'name' => t('Query string value'),
    'description' => t('The value of a specific query string field of the current page.'),
    'dynamic' => TRUE,
  );
  $info['types']['url'] = array(
    'name' => t('URL'),
    'description' => t('Tokens related to URLs.'),
  );
  $info['tokens']['url']['path'] = array(
    'name' => t('Path'),
    'description' => t('The path component of the URL.'),
  );
  $info['tokens']['url']['relative'] = array(
    'name' => t('Relative URL'),
    'description' => t('The relative URL.'),
  );
  $info['tokens']['url']['absolute'] = array(
    'name' => t('Absolute URL'),
    'description' => t('The absolute URL.'),
  );
  $info['tokens']['url']['brief'] = array(
    'name' => t('Brief URL'),
    'description' => t('The URL without the protocol and trailing backslash.'),
  );
  $info['tokens']['url']['unaliased'] = array(
    'name' => t('Unaliased URL'),
    'description' => t('The unaliased URL.'),
    'type' => 'url',
  );
  $info['tokens']['url']['args'] = array(
    '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'] = array(
    'name' => t('Array'),
    'description' => t('Tokens related to arrays of strings.'),
    'needs-data' => 'array',
  );
  $info['tokens']['array']['first'] = array(
    'name' => t('First'),
    'description' => t('The first element of the array.'),
  );
  $info['tokens']['array']['last'] = array(
    'name' => t('Last'),
    'description' => t('The last element of the array.'),
  );
  $info['tokens']['array']['count'] = array(
    'name' => t('Count'),
    'description' => t('The number of elements in the array.'),
  );
  $info['tokens']['array']['reversed'] = array(
    'name' => t('Reversed'),
    'description' => t('The array reversed.'),
    'type' => 'array',
  );
  $info['tokens']['array']['keys'] = array(
    'name' => t('Keys'),
    'description' => t('The array of keys of the array.'),
    'type' => 'array',
  );
  $info['tokens']['array']['join'] = array(
    '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'] = array(
    'name' => t('Value'),
    'description' => t('The specific value of the array.'),
    'dynamic' => TRUE,
  );
  // Random tokens.
  $info['types']['random'] = array(
    'name' => t('Random'),
    'description' => ('Tokens related to random data.'),
  );
  $info['tokens']['random']['number'] = array(
    'name' => t('Number'),
    'description' => t('A random number from 0 to @max.', array('@max' => mt_getrandmax())),
  );
  $info['tokens']['random']['hash'] = array(
    'name' => t('Hash'),
    'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', array('@hash-algos' => implode(', ', hash_algos()))),
    'dynamic' => TRUE,
  );

function token_tokens($type, array $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {

  $url_options = array('absolute' => TRUE);
    $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
  // Date tokens.
  if ($type == 'date') {
    $date = !empty($data['date']) ? $data['date'] : REQUEST_TIME;

    // @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] = format_date($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, array('date' => REQUEST_TIME), $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, array('url' => $comment->urlInfo('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) {
          $replacements[$original] = (string) $node->revision_log->value;
          $type_name = \Drupal::entityTypeManager()->getStorage('node_type')->load($node->getType())->label();
          $replacements[$original] = $type_name;
    if (($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'source')) && $source_node = $node->getUntranslated()) {
      $replacements += \Drupal::token()->generate('node', $parent_tokens, array('node' => $source_node), $options, $bubbleable_metadata);
    if (($node_type_tokens = \Drupal::token()->findWithPrefix($tokens, 'content-type')) && $node_type = node_type_load($node->bundle())) {
      $replacements += \Drupal::token()->generate('content-type', $node_type_tokens, array('node_type' => $node_type), $options, $bubbleable_metadata);
    if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $node->urlInfo()), $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();
          $replacements[$original] = $node_type->id();
          $replacements[$original] = $node_type->getDescription();
          $count = \Drupal::entityQueryAggregate('node')
            ->aggregate('nid', 'COUNT')
            ->condition('type', $node_type->id())
            ->execute();
          $replacements[$original] = (int) $count;
          break;
          $replacements[$original] = $node_type->url('edit-form', $url_options);
  }

  // Taxonomy term tokens.
  if ($type == 'term' && !empty($data['term'])) {
    /* @var \Drupal\taxonomy\TermInterface $term */
    /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
    $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
    foreach ($tokens as $name => $original) {
      switch ($name) {
        case 'edit-url':
          $replacements[$original] = Url::fromRoute('entity.taxonomy_term.edit_form', ['taxonomy_term' => $term->id()], $url_options)->toString();
          if ($parents = token_taxonomy_term_load_all_parents($term->id())) {
            $replacements[$original] = token_render_array($parents, $options);
          }
          break;

          $parents = $term_storage->loadAllParents($term->id());
          if ($root_term->id() != $term->id()) {
            $replacements[$original] = $root_term->label();

    // Chained token relationships.
    if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $term->urlInfo()), $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())) {
        $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options, $bubbleable_metadata);
    if ($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
      $parents = $term_storage->loadAllParents($term->id());
      if ($root_term->tid != $term->id()) {
        $replacements += \Drupal::token()->generate('term', $root_tokens, array('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();
          $replacements[$original] = Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], $url_options)->toString();
  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;
        case 'extension':
          $extension = pathinfo($file->uri->value, PATHINFO_EXTENSION);
          $replacements[$original] = $extension;
          $replacements[$original] = (int) $file->filesize->value;
  // 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':
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
          if ($account instanceof UserInterface && $account->hasField('user_picture')) {
Kim Pepper's avatar
Kim Pepper committed
            /** @var \Drupal\Core\Render\RendererInterface $renderer */
            $renderer = \Drupal::service('renderer');
            $output = [
              '#theme' => 'user_picture',
              '#account' => $account,
            ];
            $replacements[$original] = $renderer->renderPlain($output);
          $roles = $account->getRoles();
          $roles_names = array_combine($roles, $roles);
          $replacements[$original] = token_render_array($roles_names, $options);
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
    if ($account instanceof UserInterface && $account->hasField('user_picture') && ($picture_tokens = \Drupal::token()->findWithPrefix($tokens, 'picture'))) {
      $replacements += \Drupal::token()->generate('file', $picture_tokens, array('file' => $account->user_picture->entity), $options, $bubbleable_metadata);
    if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $account->urlInfo()), $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('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;
  // Menu link tokens.
  if ($type == 'menu-link' && !empty($data['menu-link'])) {
Kim Pepper's avatar
Kim Pepper committed
    /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
    $link = $data['menu-link'];
Kim Pepper's avatar
Kim Pepper committed
    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
    if ($link instanceof MenuLinkContentInterface) {
      $link = $menu_link_manager->createInstance($link->getPluginId());
    }

    foreach ($tokens as $name => $original) {
      switch ($name) {
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
        case 'id':
Kim Pepper's avatar
Kim Pepper committed
          $replacements[$original] = $link->getPluginId();
          $replacements[$original] = $link->getTitle();
          $replacements[$original] = $link->getUrlObject()->setAbsolute()->toString();
Kim Pepper's avatar
Kim Pepper committed

          /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
          if ($link->getParent() && $parent = $menu_link_manager->createInstance($link->getParent())) {
            $replacements[$original] = $parent->getTitle();
Kim Pepper's avatar
Kim Pepper committed
          if ($parents = token_menu_link_load_all_parents($link->getPluginId())) {
            $replacements[$original] = token_render_array($parents, $options);
          }
          break;
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
          if ($link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId()))) {
            $root = $menu_link_manager->createInstance(array_shift($parent_ids));
            $replacements[$original] = $root->getTitle();
Kim Pepper's avatar
Kim Pepper committed
    /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
    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, array('menu-link' => $parent), $options, $bubbleable_metadata);
    // [menu-link:parents:*] chained tokens.
    if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
Kim Pepper's avatar
Kim Pepper committed
      if ($parents = token_menu_link_load_all_parents($link->getPluginId())) {
        $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options, $bubbleable_metadata);
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
    if (($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) && $link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId()))) {
      $root = $menu_link_manager->createInstance(array_shift($parent_ids));
      $replacements += \Drupal::token()->generate('menu-link', $root_tokens, array('menu-link' => $root), $options, $bubbleable_metadata);
    if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $link->getUrlObject()), $options, $bubbleable_metadata);
  // Current page tokens.
  if ($type == 'current-page') {
    foreach ($tokens as $name => $original) {
      switch ($name) {
          $request = \Drupal::request();
          $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
          $title = \Drupal::service('title_resolver')->getTitle($request, $route);
          $replacements[$original] = $title;
          $replacements[$original] = Url::fromRoute('<current>', [], $url_options)->toString();
          if ($page = \Drupal::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:arg] dynamic tokens.
    if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'arg')) {
      $path = ltrim(\Drupal::service('path.current')->getPath(), '/');
      // Make sure its a system path.
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
      $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')) {
      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')) {
      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $url), $options, $bubbleable_metadata);
  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 = '/' . $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;
          $alias = \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode);
          $replacements[$original] = $alias;
          break;
        case 'absolute':
          $replacements[$original] = $url->setAbsolute()->toString();
          break;
        case 'relative':
          $replacements[$original] = $url->setAbsolute(FALSE)->toString();
          $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', $url->setAbsolute()->toString());
          $unaliased = clone $url;
          $replacements[$original] = $unaliased->setAbsolute()->setOption('alias', TRUE)->toString();
          $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
          $replacements[$original] = token_render_array(explode('/', $value), $options);
    // [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('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, array('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) {
          if (_token_module($type, 'url') == 'token' && $url = $entity->url()) {
            $replacements[$original] = $url;

        case 'original':
          if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
            $label = $entity->original->label();
            $replacements[$original] = $label;
    // [entity:url:*] chained tokens.
    if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
      $replacements += \Drupal::token()->generate('url', $url_tokens, array('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, array($type => $entity->original), $options, $bubbleable_metadata);
    // Pass through to an generic 'entity' token type generation.
    $entity_data = array(
      '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 = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
          $replacements[$original] = $value;
          $value = $array[$keys[count($keys) - 1]];
          $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
          $replacements[$original] =$value;
          $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, array('join' => '') + $options);
          break;
      }
    }

    if ($value_tokens = \Drupal::token()->findWithPrefix($tokens, 'value')) {
        if ($key[0] !== '#' && isset($array[$key])) {
          $replacements[$original] = token_render_array_value($array[$key], $options);
        }
      }
    }
    if ($join_tokens = \Drupal::token()->findWithPrefix($tokens, 'join')) {
      foreach ($join_tokens as $join => $original) {
        $replacements[$original] = token_render_array($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('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' => array_reverse($array, TRUE)), array('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, Crypt::randomBytes(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'])) {
    $replacements += \Drupal::moduleHandler()->invokeAll('tokens', array($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', array('%type' => $type)), E_USER_WARNING);
    }
 * Implements hook_token_info() on behalf of book.module.
function book_token_info() {
  $info['tokens']['node']['book'] = array(
    'name' => t('Book'),
    'description' => t('The book page associated with the node.'),
  return $info;
}

/**
 * Implements hook_tokens() on behalf of book.module.
 */
function book_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
  $replacements = array();

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

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

      foreach ($tokens as $name => $original) {
        switch ($name) {
          case 'book':
            $replacements[$original] = $link['title'];
      if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'book')) {
        $replacements += \Drupal::token()->generate('menu-link', $book_tokens, array('menu-link' => $link), $options, $bubbleable_metadata);
Sascha Grossenbacher's avatar
Sascha Grossenbacher committed
 * Implements hook_token_info() on behalf of menu_ui.module.