Commit 6c6c0db7 by Manzar Hussain

add token

parent 6f121b3c
language: php
cache:
bundler: true
directories:
- $HOME/tmp/drush
- $HOME/.bundle
apt: true
php:
- 5.4
- 5.5
env:
- PATH=$PATH:/home/travis/.composer/vendor/bin
# This will create the database
mysql:
database: drupal
username: root
encoding: utf8
# To be able to run a webbrowser
# If we need anything more powerful
# than e.g. phantomjs
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
install:
# Grab Drush
- composer global require drush/drush:dev-master --prefer-source
- cd /home/travis/.composer/vendor/drush/drush && cd -
# Make sure we don't fail when checking out projects
- echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
# LAMP package installation (mysql is already started)
- sudo apt-get update
- sudo apt-get install apache2 libapache2-mod-fastcgi
# enable php-fpm, travis does not support any other method with php and apache
- sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
- sudo a2enmod rewrite actions fastcgi alias
- echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- ~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm
# Make sure the apache root is in our wanted directory
- echo "$(curl -fsSL https://gist.githubusercontent.com/nickveenhof/11386315/raw/b8abaf9304fe12b5cc7752d39c29c1edae8ac2e6/gistfile1.txt)" | sed -e "s,PATH,$TRAVIS_BUILD_DIR/../drupal,g" | sudo tee /etc/apache2/sites-available/default > /dev/null
# Set sendmail so drush doesn't throw an error during site install.
- echo "sendmail_path='true'" >> `php --ini | grep "Loaded Configuration" | awk '{print $4}'`
# Forward the errors to the syslog so we can print them
- echo "error_log=syslog" >> `php --ini | grep "Loaded Configuration" | awk '{print $4}'`
# Get latest drupal 8 core
- cd $TRAVIS_BUILD_DIR/..
- git clone --depth 1 --branch 8.0.x http://git.drupal.org/project/drupal.git
# Restart apache and test it
- sudo service apache2 restart
- curl -v "http://localhost"
# Re-enable when trying to get CodeSniffer doesn't return a 403 anymore.
#- composer global require drupal/coder:\>7
before_script:
- cd $TRAVIS_BUILD_DIR/../drupal
# Update drupal core
- git pull origin 8.0.x
# Install the site
- drush -v site-install minimal --db-url=mysql://root:@localhost/drupal --yes
- drush en --yes simpletest
- drush cr
- phpenv rehash
script:
# go to our Drupal module directory
- mkdir $TRAVIS_BUILD_DIR/../drupal/modules/token
- cp -R $TRAVIS_BUILD_DIR/* $TRAVIS_BUILD_DIR/../drupal/modules/token/
# go to our Drupal main directory
- cd $TRAVIS_BUILD_DIR/../drupal
- ls -la $TRAVIS_BUILD_DIR/../drupal/sites/default
# Run the tests
- php core/scripts/run-tests.sh --verbose --color --concurrency 4 --php `which php` --url http://localhost "token" | tee /tmp/test.txt; TEST_EXIT=${PIPESTATUS[0]}
- echo $TEST_EXIT
# Check if we had fails in the run-tests.sh script
# Exit with the inverted value, because if there are no fails found, it will exit with 1 and for us that\
# is a good thing so invert it to 0. Travis has some issues with the exclamation mark in front so we have to fiddle a
# bit.
# Also make the grep case insensitive and fail on run-tests.sh regular fails as well on fatal errors.
- TEST_OUTPUT=$(! egrep -i "([0-9]+ fails)|(PHP Fatal error)|([0-9]+ exceptions)" /tmp/test.txt > /dev/null)$?
- echo $TEST_OUTPUT
- cd $TRAVIS_BUILD_DIR/../drupal/core
- ./vendor/bin/phpunit --verbose --debug ../modules/token/; TEST_PHPUNIT=$?
- echo $TEST_PHPUNIT
# if the TEST_EXIT status is 0 AND the TEST_OUTPUT status is also 0 it means we succeeded, in all other cases we
# failed.
# Re-enable when trying to get CodeSniffer doesn't return a 403 anymore.
#- /home/travis/.composer/vendor/bin/phpcs --standard=/home/travis/.composer/vendor/drupal/coder/coder_sniffer/Drupal --extensions=php,inc,test,module,install --ignore=css/ $TRAVIS_BUILD_DIR/../drupal/modules/search_api
- php -i | grep 'php.ini'
- sudo cat /var/log/apache2/error.log
- sudo cat /var/log/syslog | grep 'php' | cat # Suppress grep exit status 1
# Exit the build
- if [ $TEST_EXIT -eq 0 ] && [ $TEST_OUTPUT -eq 0 ] && [ $TEST_PHPUNIT -eq 0 ]; then exit 0; else exit 1; fi
CONTENTS OF THIS FILE
---------------------
* Introduction
* Recommended modules
* Installation
* Configuration
* Troubleshooting
* Maintainers
INTRODUCTION
------------
Provides common and resuable token UI elements and missing core tokens.
* For a full description of the module, visit the project page:
https://drupal.org/project/token
* To submit bug reports and feature suggestions, or to track changes:
https://drupal.org/project/issues/token
RECOMMENDED MODULES
-------------------
* No extra module is required.
INSTALLATION
------------
* Install as usual, see
https://www.drupal.org/docs/8/extending-drupal-8/installing-contributed-modules-find-import-enable-configure-drupal-8 for further
information.
CONFIGURATION
-------------
* No configuration is needed.
TROUBLESHOOTING
---------------
* Token module doesn't provide any visible functions to the user on its own, it
just provides token handling services for other modules.
MAINTAINERS
-----------
Current maintainers:
* Dave Reid (https://drupal.org/user/53892)
* Sascha Grossenbacher (Berdir) (https://www.drupal.org/user/214652)
{
"name": "drupal/token",
"type": "drupal-module",
"license": "GPL-2.0-or-later",
"description": "Provides a user interface for the Token API, some missing core tokens.",
"extra": {
"drush": {
"services": {
"drush.services.yml": "^9 || ^10"
}
}
}
}
table.treetable span.indenter {
display: inline-block;
margin: 0;
padding: 0;
text-align: right;
/* Disable text selection of nodes (for better D&D UX) */
user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-webkit-user-select: none;
/* Force content-box box model for indenter (Bootstrap compatibility) */
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
width: 19px;
}
table.treetable span.indenter a {
background-position: left center;
background-repeat: no-repeat;
display: inline-block;
text-decoration: none;
width: 19px;
}
.token-tree-dialog {
border: 1px solid #6b6b6b;
border-radius: 5px 5px 0 0;
box-shadow: 0 0 10px #6b6b6b;
}
.token-tree {
font-size: 0.85em;
margin-left: 19px;
}
.ui-dialog-content .token-tree {
margin-left: 0;
}
.token-tree td, .token-tree th {
padding-top: 0;
padding-bottom: 0;
}
.token-group {
font-weight: bold;
}
.js .token-group {
font-weight: normal;
}
/* Prevent the token columns from being wrapped. */
.token-tree td.token-key {
white-space: nowrap;
}
table.treetable {
}
table.treetable caption {
}
table.treetable thead {
}
table.treetable thead tr th {
}
table.treetable tbody tr td {
cursor: default;
}
table.treetable span {
background-position: center left;
background-repeat: no-repeat;
padding: .2em 0 .2em 1.5em;
}
table.treetable tr.collapsed span.indenter a {
background-image: url(data:image/png;base64,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);
}
table.treetable tr.expanded span.indenter a {
background-image: url(data:image/png;base64,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);
}
table.treetable tr.branch {
background-color: #f9f9f9;
}
table.treetable tr.selected {
background-color: #3875d7;
color: #fff;
}
table.treetable tr span.indenter {
margin-left: -19px;
}
table.treetable tr span.indenter a {
outline: none; /* Expander shows outline after upgrading to 3.0 (#141) */
}
table.treetable tr.accept {
background-color: #a3bce4;
color: #fff
}
services:
token.commands:
class: Drupal\token\Commands\TokenCommands
arguments:
- '@module_handler'
tags:
- { name: drush.command }
(function ($, Drupal, drupalSettings) {
'use strict';
Drupal.behaviors.tokenTree = {
attach: function (context, settings) {
$('table.token-tree', context).once('token-tree').each(function () {
$(this).treetable({ expandable: true });
});
}
};
Drupal.behaviors.tokenInsert = {
attach: function (context, settings) {
// Keep track of which textfield was last selected/focused.
$('textarea, input[type="text"]', context).focus(function () {
drupalSettings.tokenFocusedField = this;
});
$('.token-click-insert .token-key', context).once('token-click-insert').each(function () {
var newThis = $('<a href="javascript:void(0);" title="' + Drupal.t('Insert this token into your form') + '">' + $(this).html() + '</a>').click(function () {
var content = this.text;
// Always work in normal text areas that currently have focus.
if (drupalSettings.tokenFocusedField && (drupalSettings.tokenFocusedField.tokenDialogFocus || drupalSettings.tokenFocusedField.tokenHasFocus)) {
insertAtCursor(drupalSettings.tokenFocusedField, content);
}
// Direct tinyMCE support.
else if (typeof(tinyMCE) != 'undefined' && tinyMCE.activeEditor) {
tinyMCE.activeEditor.execCommand('mceInsertContent', false, content);
}
// Direct CKEditor support. Only works if the field currently has focus,
// which is unusual since the dialog is open.
else if (typeof(CKEDITOR) != 'undefined' && CKEDITOR.currentInstance) {
CKEDITOR.currentInstance.insertHtml(content);
}
// Direct CodeMirror support.
else if (typeof(CodeMirror) != 'undefined' && drupalSettings.tokenFocusedField && $(drupalSettings.tokenFocusedField).parents('.CodeMirror').length) {
var editor = $(drupalSettings.tokenFocusedField).parents('.CodeMirror')[0].CodeMirror;
editor.replaceSelection(content);
editor.focus();
}
// WYSIWYG support, should work in all editors if available.
else if (Drupal.wysiwyg && Drupal.wysiwyg.activeId) {
Drupal.wysiwyg.instances[Drupal.wysiwyg.activeId].insert(content)
}
// CKeditor module support.
else if (typeof(CKEDITOR) != 'undefined' && typeof(Drupal.ckeditorActiveId) != 'undefined') {
CKEDITOR.instances[Drupal.ckeditorActiveId].insertHtml(content);
}
else if (drupalSettings.tokenFocusedField) {
insertAtCursor(drupalSettings.tokenFocusedField, content);
}
else {
alert(Drupal.t('First click a text field to insert your tokens into.'));
}
return false;
});
$(this).html(newThis);
});
function insertAtCursor(editor, content) {
// Record the current scroll position.
var scroll = editor.scrollTop;
// IE support.
if (document.selection) {
editor.focus();
var sel = document.selection.createRange();
sel.text = content;
}
// Mozilla/Firefox/Netscape 7+ support.
else if (editor.selectionStart || editor.selectionStart == '0') {
var startPos = editor.selectionStart;
var endPos = editor.selectionEnd;
editor.value = editor.value.substring(0, startPos) + content + editor.value.substring(endPos, editor.value.length);
}
// Fallback, just add to the end of the content.
else {
editor.value += content;
}
// Ensure the textarea does not unexpectedly scroll.
editor.scrollTop = scroll;
}
}
};
})(jQuery, Drupal, drupalSettings);
<?php
namespace Drupal\token\Commands;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drush\Commands\DrushCommands;
/**
* TokenCommands provides the Drush hook implementation for cache clears.
*/
class TokenCommands extends DrushCommands {
/**
* The module_handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* TokenCommands constructor.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* The module_handler service.
*/
public function __construct(ModuleHandlerInterface $moduleHandler) {
$this->moduleHandler = $moduleHandler;
}
/**
* Adds a cache clear option for tokens.
*
* @param array $types
* The Drush clear types to make available.
* @param bool $includeBootstrappedTypes
* Whether to include types only available in a bootstrapped Drupal or not.
*
* @hook on-event cache-clear
*/
public function cacheClear(array &$types, $includeBootstrappedTypes) {
if (!$includeBootstrappedTypes || !$this->moduleHandler->moduleExists('token')) {
return;
}
$types['token'] = 'token_clear_cache';
}
}
<?php
namespace Drupal\token\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\token\TreeBuilderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Returns autocomplete responses for tokens.
*/
class TokenAutocompleteController extends ControllerBase {
/**
* @var \Drupal\token\TreeBuilderInterface
*/
protected $treeBuilder;
public function __construct(TreeBuilderInterface $tree_builder) {
$this->treeBuilder = $tree_builder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('token.tree_builder')
);
}
/**
* Retrieves suggestions for block category autocompletion.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param string $token_type
* The token type.
* @param string $filter
* The autocomplete filter.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON response containing autocomplete suggestions.
*/
public function autocomplete($token_type, $filter, Request $request) {
$filter = substr($filter, strrpos($filter, '['));
$matches = [];
if (!mb_strlen($filter)) {
$matches["[{$token_type}:"] = 0;
}
else {
$depth = max(1, substr_count($filter, ':'));
$tree = $this->treeBuilder->buildTree($token_type, ['flat' => TRUE, 'depth' => $depth]);
foreach (array_keys($tree) as $token) {
if (strpos($token, $filter) === 0) {
$matches[$token] = levenshtein($token, $filter);
if (isset($tree[$token]['children'])) {
$token = rtrim($token, ':]') . ':';
$matches[$token] = levenshtein($token, $filter);
}
}
}
}
asort($matches);
$keys = array_keys($matches);
$matches = array_combine($keys, $keys);
return new JsonResponse($matches);
}
}
<?php
namespace Drupal\token\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Clears cache for tokens.
*/
class TokenCacheController extends ControllerBase {
/**
* Clear caches and redirect back to the frontpage.
*/
public function flush() {
token_clear_cache();
$this->messenger()->addMessage($this->t('Token registry caches cleared.'));
return $this->redirect('<front>');
}
}
<?php
namespace Drupal\token\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\token\TokenEntityMapperInterface;
use Drupal\token\TreeBuilderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Devel integration for tokens.
*/
class TokenDevelController extends ControllerBase {
/**
* @var \Drupal\token\TreeBuilderInterface
*/
protected $treeBuilder;
/**
* @var \Drupal\token\TokenEntityMapperInterface
*/
protected $entityMapper;
public function __construct(TreeBuilderInterface $tree_builder, TokenEntityMapperInterface $entity_mapper) {
$this->treeBuilder = $tree_builder;
$this->entityMapper = $entity_mapper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('token.tree_builder'),
$container->get('token.entity_mapper')
);
}
/**
* Prints the loaded structure of the current entity.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* A RouteMatch object.
*
* @return array
* Array of page elements to render.
*/
public function entityTokens(RouteMatchInterface $route_match) {
$output = [];
$parameter_name = $route_match->getRouteObject()->getOption('_token_entity_type_id');
$entity = $route_match->getParameter($parameter_name);
if ($entity && $entity instanceof EntityInterface) {
$output = $this->renderTokenTree($entity);
}
return $output;
}
/**
* Render the token tree for the specified entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which the token tree should be rendered.
*
* @return array
* Render array of the token tree for the $entity.
*
* @see static::entityLoad
*/
protected function renderTokenTree(EntityInterface $entity) {
$this->moduleHandler()->loadInclude('token', 'pages.inc');
$entity_type = $entity->getEntityTypeId();
$token_type = $this->entityMapper->getTokenTypeForEntityType($entity_type);
$options = [
'flat' => TRUE,
'values' => TRUE,
'data' => [$token_type => $entity],
];
$token_tree = [
$token_type => [
'tokens' => $this->treeBuilder->buildTree($token_type, $options),
],
];
// foreach ($tree as $token => $token_info) {
// if (!isset($token_info['value']) && !empty($token_info['parent']) && !isset($tree[$token_info['parent']]['value'])) {
// continue;
// }
// }
$build['tokens'] = [
'#type' => 'token_tree_table',
'#show_restricted' => FALSE,
'#show_nested' => FALSE,
'#skip_empty_values' => TRUE,
'#token_tree' => $token_tree,
'#columns' => ['token', 'value'],
'#empty' => $this->t('No tokens available.'),
];
return $build;
}
}
<?php
namespace Drupal\token\Controller;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Controller\ControllerBase;
use Drupal\token\TreeBuilderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Returns tree responses for tokens.
*/
class TokenTreeController extends ControllerBase {
/**
* @var \Drupal\token\TreeBuilderInterface
*/
protected $treeBuilder;
public function __construct(TreeBuilderInterface $tree_builder) {
$this->treeBuilder = $tree_builder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('token.tree_builder')
);
}
/**
* Page callback to output a token tree as an empty page.
*/
function outputTree(Request $request) {
$options = $request->query->has('options') ? Json::decode($request->query->get('options')) : [];
// The option token_types may only be an array OR 'all'. If it is not set,
// we assume that only global token types are requested.
$token_types = !empty($options['token_types']) ? $options['token_types'] : [];
if ($token_types == 'all') {
$build = $this->treeBuilder->buildAllRenderable($options);
}
else {
$build = $this->treeBuilder->buildRenderable($token_types, $options);
}
$build['#cache']['contexts'][] = 'url.query_args:options';
$build['#title'] = $this->t('Available tokens');
// If this is an AJAX/modal request, add a wrapping div to the contents so
// that Drupal.behaviors.tokenTree and Drupal.behaviors.tokenAttach can
// stil find the elements they need to.
// @see https://www.drupal.org/project/token/issues/2994671
// @see https://www.drupal.org/node/2940704
// @see http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/
if ($request->isXmlHttpRequest()) {
$build['#prefix'] = '<div>';
$build['#suffix'] = '</div>';
}
return $build;
}
}
<?php
namespace Drupal\token\Element;
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Element\Table;
/**
* Provides a render element for a token tree table.
*
* @RenderElement("token_tree_table")
*/
class TokenTreeTable extends Table {
protected static $cssFilter = [' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '', ':' => '--', '?' => '', '<' => '-', '>' => '-'];
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#header' => [],
'#rows' => [],
'#token_tree' => [],
'#columns' => ['name', 'token', 'description'],
'#empty' => '',
'#show_restricted' => FALSE,
'#show_nested' => FALSE,
'#skip_empty_values' => FALSE,
'#click_insert' => TRUE,
'#sticky' => FALSE,
'#responsive' => TRUE,
'#input' => FALSE,
'#pre_render' => [
[$class, 'preRenderTokenTree'],
[$class, 'preRenderTable'],
],
'#theme' => 'table__token_tree',
'#attached' => [
'library' => [
'token/token',
],
],
];
}
/**
* Pre-render the token tree to transform rows in the token tree.
*
* @param array $element
*
* @return array
* The processed element.
*/
public static function preRenderTokenTree($element) {
$multiple_token_types = count($element['#token_tree']) > 1;
foreach ($element['#token_tree'] as $token_type => $type_info) {
// Do not show nested tokens.
if (!empty($type_info['nested']) && empty($element['#show_nested'])) {
continue;
}
if ($multiple_token_types) {
$row = static::formatRow($token_type, $type_info, $element['#columns'], TRUE);
$element['#rows'][] = $row;
}
foreach ($type_info['tokens'] as $token => $token_info) {
if (!empty($token_info['restricted']) && empty($element['#show_restricted'])) {
continue;
}
if ($element['#skip_empty_values'] && empty($token_info['value']) && !empty($token_info['parent']) && !isset($tree[$token_info['parent']]['value'])) {
continue;
}
if ($multiple_token_types && !isset($token_info['parent'])) {
$token_info['parent'] = $token_type;
}
$row = static::formatRow($token, $token_info, $element['#columns']);
$element['#rows'][] = $row;
}
}
if (!empty($element['#rows'])) {
$element['#attached']['library'][] = 'token/jquery.treeTable';
}
// Fill headers if one is not specified.
if (empty($element['#header'])) {
$column_map = [
'name' => t('Name'),
'token' => t('Token'),
'value' => t('Value'),
'description' => t('Description'),
];
foreach ($element['#columns'] as $col) {
$element['#header'][] = $column_map[$col];
}
}
$element['#attributes']['class'][] = 'token-tree';
if ($element['#click_insert']) {
$element['#caption'] = t('Click a token to insert it into the field you\'ve last clicked.');
$element['#attributes']['class'][] = 'token-click-insert';
}
return $element;
}
protected static function cleanCssIdentifier($id) {
return 'token-' . Html::cleanCssIdentifier(trim($id, '[]'), static::$cssFilter);
}
protected static function formatRow($token, $token_info, $columns, $is_group = FALSE) {
$row = [
'id' => static::cleanCssIdentifier($token),
'data-tt-id' => static::cleanCssIdentifier($token),
'class' => [],
'data' => [],
];
foreach ($columns as $col) {
switch ($col) {
case 'name':
$row['data'][$col] = $token_info['name'];
break;
case 'token':
$row['data'][$col]['data'] = $token;
$row['data'][$col]['class'][] = 'token-key';
break;
case 'description':
$row['data'][$col] = isset($token_info['description']) ? $token_info['description'] : '';
break;
case 'value':
$row['data'][$col] = !$is_group && isset($token_info['value']) ? $token_info['value'] : '';
break;
}
}
if ($is_group) {
// This is a token type/group.
$row['class'][] = 'token-group';
}
elseif (!empty($token_info['parent'])) {
$row['data-tt-parent-id'] = static::cleanCssIdentifier($token_info['parent']);
unset($row['parent']);
}
return $row;
}
}
<?php
namespace Drupal\token;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;
/**
* Defines a menu link list class for storen menu link information.
*
* @see token_entity_base_field_info()
*/
class MenuLinkFieldItemList extends EntityReferenceFieldItemList {
use ComputedItemListTrait;
/**
* {@inheritdoc}
*/
protected function computeValue() {
// This field does not really compute anything, it is used to store
// the referenced menu link.
return NULL;
}
}
<?php
namespace Drupal\token\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DevelLocalTask extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
protected $entityTypeManager;
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->hasLinkTemplate('token-devel')) {
$this->derivatives["$entity_type_id.token_devel_tab"] = [
'route_name' => "entity.$entity_type_id.token_devel",
'weight' => 110,
'title' => $this->t('Tokens'),
'parent_id' => "devel.entities:$entity_type_id.devel_tab",
];
}
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
}
<?php
namespace Drupal\token\Routing;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for Devel routes.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler) {
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
if ($devel_render = $entity_type->getLinkTemplate('token-devel')) {
$options = [
'_admin_route' => TRUE,
'_token_entity_type_id' => $entity_type_id,
'parameters' => [
$entity_type_id => [
'type' => 'entity:' . $entity_type_id,
],
],
];
$route = new Route(
$devel_render,
[
'_controller' => '\Drupal\token\Controller\TokenDevelController::entityTokens',
'_title' => 'Devel Tokens',
],
[
'_permission' => 'access devel information',
'_module_dependencies' => 'devel',
],
$options
);
$collection->add("entity.$entity_type_id.token_devel", $route);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
$events[RoutingEvents::ALTER] = array('onAlterRoutes', 100);
return $events;
}
}
<?php
namespace Drupal\token;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Utility\Token as TokenBase;
/**
* Service to retrieve token information.
*
* This service replaces the core's token service and provides the same
* functionality by extending it. It also provides additional functionality
* commonly required by the additional support provided by token module and
* other modules.
*/
class Token extends TokenBase implements TokenInterface {
/**
* Token definitions.
*
* @var array[]|null
* An array of token definitions, or NULL when the definitions are not set.
*
* @see self::resetInfo()
*/
protected $globalTokenTypes;
/**
* {@inheritdoc}
*/
public function getInfo() {
if (empty($this->tokenInfo)) {
$cache_id = 'token_info_sorted:' . $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
$cache = $this->cache->get($cache_id);
if ($cache) {
$this->tokenInfo = $cache->data;
}
else {
$token_info = $this->moduleHandler->invokeAll('token_info');
$this->moduleHandler->alter('token_info', $token_info);
foreach (array_keys($token_info['types']) as $type_key) {
if (isset($token_info['types'][$type_key]['type'])) {
$base_type = $token_info['types'][$type_key]['type'];
// If this token type extends another token type, then merge in
// the base token type's tokens.
if (isset($token_info['tokens'][$base_type])) {
$token_info['tokens'] += [$type_key => []];
$token_info['tokens'][$type_key] += $token_info['tokens'][$base_type];
}
}
else {
// Add a 'type' value to each token type information.
$token_info['types'][$type_key]['type'] = $type_key;
}
}
// Pre-sort tokens.
$by_name = $this->prepareMultisort($token_info['types']);
array_multisort($by_name, SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $token_info['types']);
foreach (array_keys($token_info['tokens']) as $type) {
$by_name = $this->prepareMultisort($token_info['tokens'][$type]);
array_multisort($by_name, SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $token_info['tokens'][$type]);
}
$this->tokenInfo = $token_info;
$this->cache->set($cache_id, $this->tokenInfo, CacheBackendInterface::CACHE_PERMANENT, [
static::TOKEN_INFO_CACHE_TAG,
]);
}
}
return $this->tokenInfo;
}
/**
* Extracts data from the token data for use in array_multisort().
*
* @param array $token_info
* List of tokens or token types, each element must have a name key.
*
* @return string[]
* List of the names keyed by the token key.
*/
protected function prepareMultisort($token_info) {
$by_name = [];
foreach ($token_info as $key => $token_info_element) {
$by_name[$key] = $token_info_element['name'];
}
return $by_name;
}
/**
* {@inheritdoc}
*/
public function getTokenInfo($token_type, $token) {
if (empty($this->tokenInfo)) {
$this->getInfo();
}
return isset($this->tokenInfo['tokens'][$token_type][$token]) ? $this->tokenInfo['tokens'][$token_type][$token] : NULL;
}
/**
* {@inheritdoc}
*/
public function getTypeInfo($token_type) {
if (empty($this->tokenInfo)) {
$this->getInfo();
}
return isset($this->tokenInfo['types'][$token_type]) ? $this->tokenInfo['types'][$token_type] : NULL;
}
/**
* {@inheritdoc}
*/
public function getGlobalTokenTypes() {
if (empty($this->globalTokenTypes)) {
$token_info = $this->getInfo();
foreach ($token_info['types'] as $type => $type_info) {
// If the token types has not specified that 'needs-data' => TRUE, then
// it is a global token type that will always be replaced in any context.
if (empty($type_info['needs-data'])) {
$this->globalTokenTypes[] = $type;
}
}
}
return $this->globalTokenTypes;
}
/**
* {@inheritdoc}
*/
function getInvalidTokens($type, $tokens) {
$token_info = $this->getInfo();
$invalid_tokens = [];
foreach ($tokens as $token => $full_token) {
if (isset($token_info['tokens'][$type][$token])) {
continue;
}
// Split token up if it has chains.
$parts = explode(':', $token, 2);
if (!isset($token_info['tokens'][$type][$parts[0]])) {
// This is an invalid token (not defined).
$invalid_tokens[] = $full_token;
}
elseif (count($parts) == 2) {
$sub_token_info = $token_info['tokens'][$type][$parts[0]];
if (!empty($sub_token_info['dynamic'])) {
// If this token has been flagged as a dynamic token, skip it.
continue;
}
elseif (empty($sub_token_info['type'])) {
// If the token has chains, but does not support it, it is invalid.
$invalid_tokens[] = $full_token;
}
else {
// Recursively check the chained tokens.
$sub_tokens = $this->findWithPrefix([$token => $full_token], $parts[0]);
$invalid_tokens = array_merge($invalid_tokens, $this->getInvalidTokens($sub_token_info['type'], $sub_tokens));
}
}
}
return $invalid_tokens;
}
/**
* {@inheritdoc}
*/
public function getInvalidTokensByContext($value, array $valid_types = []) {
if (in_array('all', $valid_types)) {
$info = $this->getInfo();
$valid_types = array_keys($info['types']);
}
else {
// Add the token types that are always valid in global context.
$valid_types = array_merge($valid_types, $this->getGlobalTokenTypes());
}
$invalid_tokens = [];
$value_tokens = is_string($value) ? $this->scan($value) : $value;
foreach ($value_tokens as $type => $tokens) {
if (!in_array($type, $valid_types)) {
// If the token type is not a valid context, its tokens are invalid.
$invalid_tokens = array_merge($invalid_tokens, array_values($tokens));
}
else {
// Check each individual token for validity.
$invalid_tokens = array_merge($invalid_tokens, $this->getInvalidTokens($type, $tokens));
}
}
array_unique($invalid_tokens);
return $invalid_tokens;
}
/**
* {@inheritdoc}
*/
public function resetInfo() {
parent::resetInfo();
$this->globalTokenTypes = NULL;
}
}
<?php
namespace Drupal\token;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Service to provide mappings between entity and token types.
*
* Why do we need this? Because when the token API was moved to core we did not
* reuse the entity type as the base name for taxonomy terms and vocabulary
* tokens.
*/
class TokenEntityMapper implements TokenEntityMapperInterface {
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* @var array
*/
protected $entityMappings;
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler) {
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function getEntityTypeMappings() {
if (empty($this->entityMappings)) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type => $info) {
$this->entityMappings[$entity_type] = $info->get('token_type') ?: $entity_type;
}
// Allow modules to alter the mapping array.
$this->moduleHandler->alter('token_entity_mapping', $this->entityMappings);
}
return $this->entityMappings;
}
/**
* {@inheritdoc}
*/
function getEntityTypeForTokenType($token_type, $fallback = FALSE) {
if (empty($this->entityMappings)) {
$this->getEntityTypeMappings();
}
$return = array_search($token_type, $this->entityMappings);
return $return !== FALSE ? $return : ($fallback ? $token_type : FALSE);
}
/**
* {@inheritdoc}
*/
function getTokenTypeForEntityType($entity_type, $fallback = FALSE) {
if (empty($this->entityMappings)) {
$this->getEntityTypeMappings();
}
return isset($this->entityMappings[$entity_type]) ? $this->entityMappings[$entity_type] : ($fallback ? $entity_type : FALSE);
}
/**
* {@inheritdoc}
*/
public function resetInfo() {
$this->entityMappings = NULL;
}
}
<?php
namespace Drupal\token;
interface TokenEntityMapperInterface {
/**
* Return an array of entity type to token type mappings.
*
* @return array
* An array of mappings with entity type mapping to token type.
*/
public function getEntityTypeMappings();
/**
* Return the entity type of a particular token type.
*
* @param string $token_type
* The token type for which the mapping is returned.
* @param bool $fallback
* (optional) Defaults to FALSE. If true, the same $value is returned in
* case the mapping was not found.
*
* @return string
* The entity type of the token type specified.
*
* @see token_entity_info_alter()
* @see http://drupal.org/node/737726
*/
function getEntityTypeForTokenType($token_type, $fallback = FALSE);
/**
* Return the token type of a particular entity type.
*
* @param string $entity_type
* The entity type for which the mapping is returned.
* @param bool $fallback
* (optional) Defaults to FALSE. If true, the same $value is returned in
* case the mapping was not found.
*
* @return string
* The token type of the entity type specified.
*
* @see token_entity_info_alter()
* @see http://drupal.org/node/737726
*/
function getTokenTypeForEntityType($entity_type, $fallback = FALSE);
/**
* Resets metadata describing token and entity mappings.
*/
public function resetInfo();
}
<?php
namespace Drupal\token;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Render\Element;
class TokenFieldRender implements TrustedCallbackInterface {
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['preRender'];
}
/**
* Pre-render callback for field output used with tokens.
*/
public static function preRender($elements) {
// Remove the field theme hook, attachments, and JavaScript states.
unset($elements['#theme']);
unset($elements['#states']);
unset($elements['#attached']);
// Prevent multi-value fields from appearing smooshed together by appending
// a join suffix to all but the last value.
$deltas = Element::getVisibleChildren($elements);
$count = count($deltas);
if ($count > 1) {
$join = isset($elements['#token_options']['join']) ? $elements['#token_options']['join'] : ", ";
foreach ($deltas as $index => $delta) {
// Do not add a suffix to the last item.
if ($index < ($count - 1)) {
$elements[$delta] += ['#suffix' => $join];
}
}
}
return $elements;
}
}
<?php
namespace Drupal\token;
interface TokenInterface {
/**
* Returns metadata describing supported token types.
*
* @param $token_type
* The token type for which the metadata is required.
*
* @return array[]
* An array of token type information from hook_token_info() for the
* specified token type.
*
* @see hook_token_info()
* @see hook_token_info_alter()
*/
public function getTypeInfo($token_type);
/**
* Returns metadata describing supported a token.
*
* @param $token_type
* The token type for which the metadata is required.
* @param $token
* The token name for which the metadata is required.
*
* @return array[]
* An array of information from hook_token_info() for the specified token.
*
* @see hook_token_info()
* @see hook_token_info_alter()
*/
public function getTokenInfo($token_type, $token);
/**
* Get a list of token types that can be used without any context (global).
*
* @return array[]
* An array of global token types.
*/
public function getGlobalTokenTypes();
/**
* Validate an array of tokens based on their token type.
*
* @param string $type
* The type of tokens to validate (e.g. 'node', etc.)
* @param string[] $tokens
* A keyed array of tokens, and their original raw form in the source text.
*
* @return string[]
* An array with the invalid tokens in their original raw forms.
*/
function getInvalidTokens($type, $tokens);
/**
* Validate tokens in raw text based on possible contexts.
*
* @param string|string[] $value
* A string with the raw text containing the raw tokens, or an array of
* tokens from token_scan().
* @param string[] $valid_types
* An array of token types that will be used when token replacement is
* performed.
*
* @return string[]
* An array with the invalid tokens in their original raw forms.
*/
public function getInvalidTokensByContext($value, array $valid_types = []);
}
<?php
namespace Drupal\token;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
/**
* Replace core's token service with our own.
*/
class TokenServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
$definition = $container->getDefinition('token');
$definition->setClass('\Drupal\token\Token');
}
}
<?php
namespace Drupal\token;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\StringTranslation\StringTranslationTrait;
class TreeBuilder implements TreeBuilderInterface {
use StringTranslationTrait;
/**
* @var \Drupal\token\Token
*/
protected $tokenService;
/**
* @var \Drupal\token\TokenEntityMapperInterface
*/
protected $entityMapper;
/**
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheBackend;
/**
* Cache already built trees.
*
* @var array
*/
protected $builtTrees;
public function __construct(TokenInterface $token_service, TokenEntityMapperInterface $entity_mapper, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
$this->tokenService = $token_service;
$this->entityMapper = $entity_mapper;
$this->cacheBackend = $cache_backend;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function buildRenderable(array $token_types, array $options = []) {
// Set default options.
$options += [
'global_types' => TRUE,
'click_insert' => TRUE,
'show_restricted' => FALSE,
'show_nested' => FALSE,
'recursion_limit' => 3,
];
$info = $this->tokenService->getInfo();
if ($options['global_types']) {
$token_types = array_merge($token_types, $this->tokenService->getGlobalTokenTypes());
}
$element = [
/*'#cache' => [
'cid' => 'tree-rendered:' . hash('sha256', serialize(['token_types' => $token_types, 'global_types' => NULL] + $variables)),
'tags' => [Token::TOKEN_INFO_CACHE_TAG],
],*/
];
// @todo Find a way to use the render cache for this.
$tree_options = [
'flat' => TRUE,
'restricted' => $options['show_restricted'],
'nested' => $options['show_nested'],
'depth' => $options['recursion_limit'],
];
$token_tree = [];
foreach ($info['types'] as $type => $type_info) {
if (!in_array($type, $token_types)) {
continue;
}
$token_tree[$type] = $type_info;
$token_tree[$type]['tokens'] = $this->buildTree($type, $tree_options);
}
$element += [
'#type' => 'token_tree_table',
'#token_tree' => $token_tree,
'#show_restricted' => $options['show_restricted'],
'#show_nested' => $options['show_nested'],
'#click_insert' => $options['click_insert'],
'#columns' => ['name', 'token', 'description'],
'#empty' => $this->t('No tokens available'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function buildAllRenderable(array $options = []) {
$info = $this->tokenService->getInfo();
$token_types = array_keys($info['types']);
// Disable merging in global types as we will be adding in all token types
// explicitly. There is no difference in leaving this set to TRUE except for
// an additional method call which is unnecessary.
$options['global_types'] = FALSE;
return $this->buildRenderable($token_types, $options);
}
/**
* {@inheritdoc}
*/
public function buildTree($token_type, array $options = []) {
$options += [
'restricted' => FALSE,
'depth' => 4,
'data' => [],
'values' => FALSE,
'flat' => FALSE,
];
// Do not allow past the maximum token information depth.
$options['depth'] = min($options['depth'], static::MAX_DEPTH);
// If $token_type is an entity, make sure we are using the actual token type.
if ($entity_token_type = $this->entityMapper->getTokenTypeForEntityType($token_type)) {
$token_type = $entity_token_type;
}
$langcode = $this->languageManager->getCurrentLanguage()->getId();
$tree_cid = "token_tree:{$token_type}:{$langcode}:{$options['depth']}";
// If we do not have this base tree in the static cache, check the cache
// otherwise generate and store it in the cache.
if (!isset($this->builtTrees[$tree_cid])) {
if ($cache = $this->cacheBackend->get($tree_cid)) {
$this->builtTrees[$tree_cid] = $cache->data;
}
else {
$options['parents'] = [];
$this->builtTrees[$tree_cid] = $this->getTokenData($token_type, $options);
$this->cacheBackend->set($tree_cid, $this->builtTrees[$tree_cid], Cache::PERMANENT, [Token::TOKEN_INFO_CACHE_TAG]);
}
}
$tree = $this->builtTrees[$tree_cid];
// If the user has requested a flat tree, convert it.
if (!empty($options['flat'])) {
$tree = $this->flattenTree($tree);
}
// Fill in token values.
if (!empty($options['values'])) {
$token_values = [];
foreach ($tree as $token => $token_info) {
if (!empty($token_info['dynamic']) || !empty($token_info['restricted'])) {
continue;
}
elseif (!isset($token_info['value'])) {
$token_values[$token_info['token']] = $token;
}
}
if (!empty($token_values)) {
$token_values = $this->tokenService->generate($token_type, $token_values, $options['data'], [], new BubbleableMetadata());
foreach ($token_values as $token => $replacement) {
$tree[$token]['value'] = $replacement;
}
}
}
return $tree;
}
/**
* {@inheritdoc}
*/
public function flattenTree(array $tree) {
$result = [];
foreach ($tree as $token => $token_info) {
$result[$token] = $token_info;
if (isset($token_info['children']) && is_array($token_info['children'])) {
$result += $this->flattenTree($token_info['children']);
}
}
return $result;
}
/**
* Generate a token tree.
*
* @param string $token_type
* The token type.
* @param array $options
* An associative array of additional options. See documentation for
* TreeBuilderInterface::buildTree() for more information.
*
* @return array
* The token data for the specified $token_type.
*
* @internal
*/
protected function getTokenData($token_type, array $options) {
$options += [
'parents' => [],
];
$info = $this->tokenService->getInfo();
if ($options['depth'] <= 0 || !isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
return [];
}
$tree = [];
foreach ($info['tokens'][$token_type] as $token => $token_info) {
// Build the raw token string.
$token_parents = $options['parents'];
if (empty($token_parents)) {
// If the parents array is currently empty, assume the token type is its
// parent.
$token_parents[] = $token_type;
}
// The 'entity' token will be repeated on nested entity reference fields.
elseif ($token !== 'entity' && in_array($token, array_slice($token_parents, 1), TRUE)) {
// Prevent duplicate recursive tokens. For example, this will prevent
// the tree from generating the following tokens or deeper:
// [comment:parent:parent]
// [comment:parent:root:parent]
continue;
}
$token_parents[] = $token;
if (!empty($token_info['dynamic'])) {
$token_parents[] = '?';
}
$raw_token = '[' . implode(':', $token_parents) . ']';
$tree[$raw_token] = $token_info;
$tree[$raw_token]['raw token'] = $raw_token;
// Add the token's real name (leave out the base token type).
$tree[$raw_token]['token'] = implode(':', array_slice($token_parents, 1));
// Add the token's parent as its raw token value.
if (!empty($options['parents'])) {
$tree[$raw_token]['parent'] = '[' . implode(':', $options['parents']) . ']';
}
// Fetch the child tokens.
if (!empty($token_info['type'])) {
$child_options = $options;
$child_options['depth']--;
$child_options['parents'] = $token_parents;
$tree[$raw_token]['children'] = $this->getTokenData($token_info['type'], $child_options);
}
}
return $tree;
}
}
<?php
namespace Drupal\token;
interface TreeBuilderInterface {
/**
* The maximum depth for token tree recursion.
*/
const MAX_DEPTH = 9;
/**
* Build a tree array of tokens used for themeing or information.
*
* @param string $token_type
* The token type.
* @param array $options
* (optional) An associative array of additional options, with the following
* elements:
* - 'flat' (defaults to FALSE): Set to true to generate a flat list of
* token information. Otherwise, child tokens will be inside the
* 'children' parameter of a token.
* - 'restricted' (defaults to FALSE): Set to true to how restricted tokens.
* - 'depth' (defaults to 4): Maximum number of token levels to recurse.
*
* @return array
* The token information constructed in a tree or flat list form depending
* on $options['flat'].
*/
public function buildTree($token_type, array $options = []);
/**
* Flatten a token tree.
*
* @param array $tree
* The tree array as returned by TreeBuilderInterface::buildTree().
*
* @return array
* The flattened version of the tree.
*/
public function flattenTree(array $tree);
/**
* Build a render array with token tree built as per specified options.
*
* @param array $token_types
* An array containing token types that should be shown in the tree.
* @param array $options
* (optional) An associative array to control which tokens are shown and
* how. The properties available are:
* - 'global_types' (defaults to TRUE): Show all global token types along
* with the specified types.
* - 'click_insert' (defaults to TRUE): Include classes and caption to show
* allow inserting tokens in fields by clicking on them.
* - 'show_restricted' (defaults to FALSE): Show restricted tokens in the
* tree.
* - 'show_nested' (defaults to FALSE): If this token is nested and should
* therefor not show on the token browser as a top level token.
* - 'recursion_limit' (defaults to 3): Only show tokens up to the specified
* depth.
*
* @return array
* Render array for the token tree.
*/
public function buildRenderable(array $token_types, array $options = []);
/**
* Build a render array with token tree containing all possible tokens.
*
* @param array $options
* (optional) An associative array to control which tokens are shown and
* how. The properties available are: See
* \Drupal\token\TreeBuilderInterface::buildRenderable() for details.
*
* @return array
* Render array for the token tree.
*/
public function buildAllRenderable(array $options = []);
}
{#
/**
* @file
* Default theme implementation for the token tree link.
*
* Available variables:
* - url: The URL to the token tree page.
* - text: The text to be displayed in the link.
* - attributes: Attributes for the anchor tag.
* - link: The complete link.
*
* @see template_preprocess_token_tree_link()
*
* @ingroup themeable
*/
#}
{% if link -%}
{{ link }}
{%- endif %}
id: token_module_test
label: 'Token test'
status: true
langcode: en
locked: true
pattern: Y
<?php
namespace Drupal\token_module_test\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Drupal\token\TokenInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Controller for token module test.
*/
class TokenTreeBrowseController extends ControllerBase {
/**
* Service to retrieve token information.
*
* @var \Drupal\token\TokenInterface
*/
protected $token;
/**
* The construct method.
*
* @param \Drupal\token\TokenInterface $token
* The token.
*/
public function __construct(TokenInterface $token) {
$this->token = $token;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('token')
);
}
/**
* Page callback to output a link.
*/
public function outputLink(Request $request) {
$build['tree']['#theme'] = 'token_tree_link';
$build['tokenarea'] = [
'#markup' => $this->token->replace('[current-page:title]'),
'#type' => 'markup',
];
return $build;
}
/**
* Title callback for the page outputting a link.
*
* We are using a title callback instead of directly defining the title in the
* routing YML file. This is so that we could return an array instead of a
* simple string. This allows us to test if [current-page:title] works with
* render arrays and other objects as titles.
*/
public function getTitle() {
return [
'#type' => 'markup',
'#markup' => 'Available Tokens',
];
}
}
langcode: en
status: true
dependencies:
module:
- user
id: token_views_test
label: token_views_test
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: some
options:
items_per_page: 5
offset: 0
style:
type: default
row:
type: fields
options:
default_field_elements: true
inline: { }
separator: ''
hide_empty: false
fields:
name:
id: name
table: users_field_data
field: name
entity_type: user
entity_field: name
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: user_name
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
filters: { }
sorts: { }
title: token_views_test
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
filter_groups:
operator: AND
groups: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
tags: { }
block_1:
display_plugin: block
id: block_1
display_title: Block
position: 1
display_options:
display_extenders: { }
type: module
name: Token Module Test
description: Testing module for token functionality.
package: Testing
core_version_requirement: ^8.7.7 || ^9
hidden: TRUE
# Information added by Drupal.org packaging script on 2020-12-18
version: '8.x-1.9'
project: 'token'
datestamp: 1608284868
<?php
/**
* @file
* Helper module for token tests.
*/
use Drupal\node\NodeInterface;
/**
* Implements hook_page_attachments().
*/
function token_module_test_page_attachments() {
if ($debug = \Drupal::state()->get('token_page_tokens', [])) {
$debug += ['tokens' => [], 'data' => [], 'options' => []];
foreach (array_keys($debug['tokens']) as $token) {
$debug['values'][$token] = \Drupal::token()->replace($token, $debug['data'], $debug['options']);
}
\Drupal::state()->set('token_page_tokens', $debug);
}
}
/**
* Implements hook_ENTITY_TYPE_presave for Node entities.
*/
function token_module_test_node_presave(NodeInterface $node) {
// Transform tokens in the body.
// @see \Drupal\token\Tests\TokenMenuTest::testMenuTokens()
if ($node->hasField('body')) {
$node->body->value = \Drupal::token()
->replace($node->body->value, ['node' => $node]);
}
}
token_module_test.browse:
path: '/token_module_test/browse'
defaults:
_controller: '\Drupal\token_module_test\Controller\TokenTreeBrowseController::outputLink'
_title_callback: '\Drupal\token_module_test\Controller\TokenTreeBrowseController::getTitle'
requirements:
_access: 'TRUE'
<?php
/**
* Implements hook_token_info()
*/
function token_module_test_token_info() {
$info['tokens']['node']['colons:in:name'] = [
'name' => t('A test token with colons in the name'),
'description' => NULL,
];
return $info;
}
langcode: de
status: true
dependencies:
module:
- image
- user
id: user.user.default
targetEntityType: user
bundle: user
mode: default
content:
account:
weight: -10
user_picture:
type: image_image
settings:
progress_indicator: throbber
preview_image_style: thumbnail
third_party_settings: { }
weight: -1
timezone:
weight: 6
hidden: { }
id: user.user.user_picture
status: true
langcode: en
entity_type: user
bundle: user
field_name: user_picture
label: Picture
description: 'Your virtual face or picture.'
required: false
default_value: { }
default_value_function: ''
settings:
file_extensions: 'png gif jpg jpeg'
file_directory: pictures
max_filesize: '30 KB'
alt_field: false
title_field: false
max_resolution: 85x85
min_resolution: ''
default_image:
uuid: null
alt: ''
title: ''
width: null
height: null
alt_field_required: false
title_field_required: false
field_type: image
dependencies:
config:
- field.storage.user.user_picture
id: user.user_picture
status: true
langcode: en
field_name: user_picture
entity_type: user
type: image
settings:
uri_scheme: public
default_image:
uuid: null
alt: ''
title: ''
width: null
height: null
module: image
locked: false
cardinality: 1
translatable: false
indexes:
target_id:
- target_id
dependencies:
module:
- image
- user
type: module
name: Token User picture
description: Testing module that provides user pictures field.
package: Testing
core_version_requirement: ^8.7.7 || ^9
hidden: TRUE
dependencies:
- drupal:image
# Information added by Drupal.org packaging script on 2020-12-18
version: '8.x-1.9'
project: 'token'
datestamp: 1608284868
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
/**
* Tests block tokens.
*
* @group token
*/
class TokenBlockTest extends TokenTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'node', 'views', 'block_content'];
/**
* {@inheritdoc}
*/
public function setUp($modules = []) {
parent::setUp();
$this->admin_user = $this->drupalCreateUser(['access content', 'administer blocks']);
$this->drupalLogin($this->admin_user);
}
public function testBlockTitleTokens() {
$label = 'tokenblock';
$bundle = BlockContentType::create([
'id' => $label,
'label' => $label,
'revision' => FALSE
]);
$bundle->save();
$block_content = BlockContent::create([
'type' => $label,
'label' => '[current-page:title] block title',
'info' => 'Test token title block',
'body[value]' => 'This is the test token title block.',
]);
$block_content->save();
$block = $this->drupalPlaceBlock('block_content:' . $block_content->uuid(), [
'label' => '[user:name]',
]);
$this->drupalGet($block->toUrl());
// Ensure that the link to available tokens is present and correctly
// positioned.
$this->assertSession()->linkExists('Browse available tokens.');
$this->assertText('This field supports tokens. Browse available tokens.');
$this->drupalPostForm(NULL, [], 'Save block');
// Ensure token validation is working on the block.
$this->assertText('Title is using the following invalid tokens: [user:name].');
// Create the block for real now with a valid title.
$settings = $block->get('settings');
$settings['label'] = '[current-page:title] block title';
$block->set('settings', $settings);
$block->save();
// Ensure that tokens are not double-escaped when output as a block title.
$this->drupalCreateContentType(['type' => 'page']);
$node = $this->drupalCreateNode(['title' => "Site's first node"]);
$this->drupalGet('node/' . $node->id());
// The apostraphe should only be escaped once.
$this->assertSession()->responseContains("Site&#039;s first node block title");
}
}
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\Core\Url;
/**
* Test the [current-page:*] tokens.
*
* @group token
*/
class TokenCurrentPageTest extends TokenTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node'];
function testCurrentPageTokens() {
// Cache clear is necessary because the frontpage was already cached by an
// initial request.
$this->rebuildAll();
$tokens = [
'[current-page:title]' => 'Log in',
'[current-page:url]' => Url::fromRoute('user.login', [], ['absolute' => TRUE])->toString(),
'[current-page:url:absolute]' => Url::fromRoute('user.login', [], ['absolute' => TRUE])->toString(),
'[current-page:url:relative]' => Url::fromRoute('user.login')->toString(),
'[current-page:url:path]' => '/user/login',
'[current-page:url:args:value:0]' => 'user',
'[current-page:url:args:value:1]' => 'login',
'[current-page:url:args:value:2]' => NULL,
'[current-page:url:unaliased]' => Url::fromRoute('user.login', [], ['absolute' => TRUE, 'alias' => TRUE])->toString(),
'[current-page:page-number]' => 1,
'[current-page:query:foo]' => NULL,
'[current-page:query:bar]' => NULL,
// Deprecated tokens
'[current-page:arg:0]' => 'user',
'[current-page:arg:1]' => 'login',
'[current-page:arg:2]' => NULL,
];
$this->assertPageTokens('user/login', $tokens);
$this->drupalCreateContentType(['type' => 'page']);
$node = $this->drupalCreateNode(['title' => 'Node title', 'path' => ['alias' => '/node-alias']]);
$tokens = [
'[current-page:title]' => 'Node title',
'[current-page:url]' => $node->toUrl('canonical', ['absolute' => TRUE])->toString(),
'[current-page:url:absolute]' => $node->toUrl('canonical', ['absolute' => TRUE])->toString(),
'[current-page:url:relative]' => $node->toUrl()->toString(),
'[current-page:url:alias]' => '/node-alias',
'[current-page:url:args:value:0]' => 'node-alias',
'[current-page:url:args:value:1]' => NULL,
'[current-page:url:unaliased]' => $node->toUrl('canonical', ['absolute' => TRUE, 'alias' => TRUE])->toString(),
'[current-page:url:unaliased:args:value:0]' => 'node',
'[current-page:url:unaliased:args:value:1]' => $node->id(),
'[current-page:url:unaliased:args:value:2]' => NULL,
'[current-page:page-number]' => 1,
'[current-page:query:foo]' => 'bar',
'[current-page:query:bar]' => NULL,
// Deprecated tokens
'[current-page:arg:0]' => 'node',
'[current-page:arg:1]' => 1,
'[current-page:arg:2]' => NULL,
];
$this->assertPageTokens("/node/{$node->id()}", $tokens, [], ['url_options' => ['query' => ['foo' => 'bar']]]);
}
}
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\Tests\menu_ui\Functional\MenuUiContentModerationTest;
/**
* Tests Menu UI and Content Moderation integration.
*
* @group token
*/
class TokenMenuUiContentModerationTest extends MenuUiContentModerationTest {
/**
* {@inheritdoc}
*/
public static $modules = ['token'];
}
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Helper test class with some added functions for testing.
*/
abstract class TokenTestBase extends BrowserTestBase {
use TokenTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['path', 'token', 'token_module_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
/**
* Helper test trait with some added functions for testing.
*/
trait TokenTestTrait {
use PathAliasTestTrait;
function assertToken($type, array $data, $token, $expected, array $options = []) {
return $this->assertTokens($type, $data, [$token => $expected], $options);
}
function assertTokens($type, array $data, array $tokens, array $options = []) {
$input = $this->mapTokenNames($type, array_keys($tokens));
$bubbleable_metadata = new BubbleableMetadata();
$replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata);
foreach ($tokens as $name => $expected) {
$token = $input[$name];
if (!isset($expected)) {
$this->assertArrayNotHasKey($token, $replacements, t("Token value for @token was not generated.", ['@token' => $token]));
}
elseif (!isset($replacements[$token])) {
$this->fail(t("Token value for @token was not generated.", ['@type' => $type, '@token' => $token]));
}
elseif (!empty($options['regex'])) {
$this->assertEquals(1, preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", ['@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected]));
}
else {
$this->assertEquals($expected, $replacements[$token], t("Token value for @token was '@actual', expected value '@expected'.", ['@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected]));
}
}
return $replacements;
}
function mapTokenNames($type, array $tokens = []) {
$return = [];
foreach ($tokens as $token) {
$return[$token] = "[$type:$token]";
}
return $return;
}
function assertNoTokens($type, array $data, array $tokens, array $options = []) {
$input = $this->mapTokenNames($type, $tokens);
$bubbleable_metadata = new BubbleableMetadata();
$replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata);
foreach ($tokens as $name) {
$token = $input[$name];
$this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", ['@type' => $type, '@token' => $token]));
}
}
function saveAlias($source, $alias, $language = Language::LANGCODE_NOT_SPECIFIED) {
return $this->createPathAlias($source, $alias, $language);
}
function saveEntityAlias($entity_type, EntityInterface $entity, $alias, $language = Language::LANGCODE_NOT_SPECIFIED) {
$uri = $entity->toUrl()->toArray();
return $this->saveAlias($uri['path'], $alias, $language);
}
/**
* Make a page request and test for token generation.
*/
function assertPageTokens($url, array $tokens, array $data = [], array $options = []) {
if (empty($tokens)) {
return TRUE;
}
$token_page_tokens = [
'tokens' => $tokens,
'data' => $data,
'options' => $options,
];
\Drupal::state()->set('token_page_tokens', $token_page_tokens);
$options += ['url_options' => []];
$this->drupalGet($url, $options['url_options']);
$this->refreshVariables();
$result = \Drupal::state()->get('token_page_tokens', []);
if (!isset($result['values']) || !is_array($result['values'])) {
return $this->fail('Failed to generate tokens.');
}
foreach ($tokens as $token => $expected) {
if (!isset($expected)) {
$this->assertTrue(!isset($result['values'][$token]) || $result['values'][$token] === $token, t("Token value for @token was not generated.", ['@token' => $token]));
}
elseif (!isset($result['values'][$token])) {
$this->fail(t('Failed to generate token @token.', ['@token' => $token]));
}
else {
$this->assertSame($result['values'][$token], (string) $expected, t("Token value for @token was '@actual', expected value '@expected'.", ['@token' => $token, '@actual' => $result['values'][$token], '@expected' => $expected]));
}
}
}
}
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\Core\Url;
/**
* Tests url tokens.
*
* @group token
*/
class TokenURLTest extends TokenTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->saveAlias('/node/1', '/first-node');
}
function testURLTokens() {
$url = new Url('entity.node.canonical', ['node' => 1]);
$tokens = [
'absolute' => $url->setAbsolute()->toString(),
'relative' => $url->setAbsolute(FALSE)->toString(),
'path' => '/first-node',
'brief' => preg_replace(['!^https?://!', '!/$!'], '', $url->setAbsolute()->toString()),
'args:value:0' => 'first-node',
'args:value:1' => NULL,
'args:value:N' => NULL,
'unaliased' => $url->setAbsolute()->setOption('alias', TRUE)->toString(),
'unaliased:relative' => $url->setAbsolute(FALSE)->setOption('alias', TRUE)->toString(),
'unaliased:path' => '/node/1',
'unaliased:brief' => preg_replace(['!^https?://!', '!/$!'], '', $url->setAbsolute()->setOption('alias', TRUE)->toString()),
'unaliased:args:value:0' => 'node',
'unaliased:args:value:1' => '1',
'unaliased:args:value:2' => NULL,
// Deprecated tokens.
'alias' => '/first-node',
];
$this->assertTokens('url', ['url' => new Url('entity.node.canonical', ['node' => 1])], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests user tokens.
*
* @group token
*/
class TokenUserTest extends TokenTestBase {
use TestFileCreationTrait;
/**
* The user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account = NULL;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['token_user_picture'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->account = $this->drupalCreateUser([
'administer users',
'administer account settings',
'access content',
]);
$this->drupalLogin($this->account);
}
/**
* Tests the user releated tokens.
*/
public function testUserTokens() {
// Enable user pictures.
\Drupal::state()->set('user_pictures', 1);
\Drupal::state()->set('user_picture_file_size', '');
// Set up the pictures directory.
$picture_path = 'public://' . \Drupal::state()->get('user_picture_path', 'pictures');
if (!\Drupal::service('file_system')->prepareDirectory($picture_path, FileSystemInterface::CREATE_DIRECTORY)) {
$this->fail('Could not create directory ' . $picture_path . '.');
}
// Add a user picture to the account.
$image = current($this->getTestFiles('image'));
$edit = ['files[user_picture_0]' => \Drupal::service('file_system')->realpath($image->uri)];
$this->drupalPostForm('user/' . $this->account->id() . '/edit', $edit, 'Save');
$storage = \Drupal::entityTypeManager()->getStorage('user');
// Load actual user data from database.
$storage->resetCache();
$this->account = $storage->load($this->account->id());
$this->assertNotEmpty($this->account->user_picture->target_id, 'User picture uploaded.');
$picture = [
'#theme' => 'user_picture',
'#account' => $this->account,
];
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$user_tokens = [
'picture' => $renderer->renderPlain($picture),
'picture:fid' => $this->account->user_picture->target_id,
'picture:size-raw' => 125,
'ip-address' => NULL,
'roles' => implode(', ', $this->account->getRoles()),
];
$this->assertTokens('user', ['user' => $this->account], $user_tokens);
// Remove the simpletest-created user role.
$roles = $this->account->getRoles();
$this->account->removeRole(end($roles));
$this->account->save();
// Remove the user picture field and reload the user.
FieldStorageConfig::loadByName('user', 'user_picture')->delete();
$storage->resetCache();
$this->account = $storage->load($this->account->id());
$user_tokens = [
'picture' => NULL,
'picture:fid' => NULL,
'ip-address' => NULL,
'roles' => 'authenticated',
'roles:keys' => AccountInterface::AUTHENTICATED_ROLE,
];
$this->assertTokens('user', ['user' => $this->account], $user_tokens);
// The ip address token should work for the current user token type.
$tokens = [
'ip-address' => \Drupal::request()->getClientIp(),
];
$this->assertTokens('current-user', [], $tokens);
$anonymous = new AnonymousUserSession();
$tokens = [
'roles' => 'anonymous',
'roles:keys' => AccountInterface::ANONYMOUS_ROLE,
];
$this->assertTokens('user', ['user' => $anonymous], $tokens);
}
/**
* Test user account settings.
*/
public function testUserAccountSettings() {
$this->drupalGet('admin/config/people/accounts');
$this->assertText('The list of available tokens that can be used in e-mails is provided below.');
$this->assertSession()->linkExists('Browse available tokens.');
$this->assertSession()->linkByHrefExists('token/tree');
}
}
<?php
namespace Drupal\Tests\token\Functional\Tree;
use Drupal\Component\Serialization\Json;
use Drupal\Tests\token\Functional\TokenTestBase;
/**
* Test token autocomplete.
*
* @group token
*/
class AutocompleteTest extends TokenTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node'];
/**
* Tests autocomplete for node tokens.
*/
public function testNodeAutocomplete() {
$url_prefix = "token/autocomplete/node/";
$url = $url_prefix . 'Title of [nod';
$response = Json::decode($this->drupalGet($url, [
'query' => [
'_format' => 'json',
],
]));
$this->assertArrayHasKey('[node:nid]', $response);
$this->assertArrayHasKey('[node:author]', $response);
$this->assertArrayHasKey('[node:url]', $response);
$this->assertArrayHasKey('[node:url:', $response);
$url = $url_prefix . 'Title of [node:url:';
$response = Json::decode($this->drupalGet($url, [
'query' => [
'_format' => 'json',
],
]));
$this->assertArrayHasKey('[node:url:path]', $response);
$this->assertArrayHasKey('[node:url:absolute]', $response);
}
/**
* Tests autocomplete for user tokens.
*/
public function testUserAutocomplete() {
$url_prefix = "token/autocomplete/user/";
$url = $url_prefix . 'Name of the [us';
$response = Json::decode($this->drupalGet($url, [
'query' => [
'_format' => 'json',
],
]));
$this->assertArrayHasKey('[user:uid]', $response);
$this->assertArrayHasKey('[user:original]', $response);
$this->assertArrayHasKey('[user:url]', $response);
$this->assertArrayHasKey('[user:url:', $response);
$url = $url_prefix . 'Title of [user:original:';
$response = Json::decode($this->drupalGet($url, [
'query' => [
'_format' => 'json',
],
]));
$this->assertArrayHasKey('[user:original:uid]', $response);
}
}
<?php
namespace Drupal\Tests\token\Functional\Tree;
use Drupal\Tests\token\Functional\TokenTestBase;
/**
* Tests token tree on help page.
*
* @group token
*/
class HelpPageTest extends TokenTestBase {
use TokenTreeTestTrait;
/**
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['help'];
public function setUp() {
parent::setUp();
$this->account = $this->drupalCreateUser(['access administration pages']);
$this->drupalLogin($this->account);
}
/**
* Tests the token browser on the token help page.
*/
public function testHelpPageTree() {
$this->drupalGet('admin/help/token');
$this->assertText('The list of the currently available tokens on this site are shown below.');
$this->assertTokenGroup('Current date');
$this->assertTokenGroup('Site information');
$this->assertTokenInTree('[current-date:html_date]', 'current-date');
$this->assertTokenInTree('[current-date:html_week]', 'current-date');
$this->assertTokenInTree('[date:html_date]', 'date');
$this->assertTokenInTree('[date:html_week]', 'date');
$this->assertTokenInTree('[current-user:account-name]', 'current-user');
$this->assertTokenInTree('[user:account-name]', 'user');
$this->assertTokenInTree('[current-page:url:unaliased]', 'current-page--url');
$this->assertTokenInTree('[current-page:url:unaliased:args]', 'current-page--url--unaliased');
$this->assertTokenInTree('[user:original:account-name]', 'user--original');
// Assert some of the restricted tokens to ensure they are shown.
$this->assertTokenInTree('[user:one-time-login-url]', 'user');
$this->assertTokenInTree('[user:original:cancel-url]', 'user--original');
// The Array token is marked as nested, so it should not show up as a top
// level token, only nested under another token. For instance, user:roles
// is of type Array and tokens of type Array have 'nested' setting true.
$this->assertTokenNotGroup('Array');
$this->assertTokenNotGroup('user:roles');
$this->assertTokenInTree('[user:roles]', 'user');
}
}
<?php
namespace Drupal\Tests\token\Functional\Tree;
use Behat\Mink\Element\NodeElement;
/**
* Helper trait to assert tokens in token tree browser.
*/
trait TokenTreeTestTrait {
/**
* Get an array of token groups from the last retrieved page.
*
* @return array
* Array of token group names.
*/
protected function getTokenGroups() {
$groups = $this->xpath('//tr[contains(@class, "token-group")]/td[1]');
return array_map(function (NodeElement $item) {
return (string) $item->getText();
}, $groups);
}
/**
* Check to see if the specified token group is present in the token browser.
*
* @param string $token_group
* The name of the token group.
* @param string $message
* (optional) A message to display with the assertion.
*/
protected function assertTokenGroup($token_group, $message = '') {
$groups = $this->getTokenGroups();
if (!$message) {
$message = "Token group $token_group found.";
}
$this->assertContains($token_group, $groups, $message);
}
/**
* Check to see if the specified token group is not present in the token
* browser.
*
* @param string $token_group
* The name of the token group.
* @param string $message
* (optional) A message to display with the assertion.
*/
protected function assertTokenNotGroup($token_group, $message = '') {
$groups = $this->getTokenGroups();
if (!$message) {
$message = "Token group $token_group not found.";
}
$this->assertNotContains($token_group, $groups, $message);
}
/**
* Check to see if the specified token is present in the token browser.
*
* @param $token
* The token name with the surrounding square brackets [].
* @param string $parent
* (optional) The parent CSS identifier of this token.
* @param string $message
* (optional) A message to display with the assertion.
*/
protected function assertTokenInTree($token, $parent = '', $message = '') {
$xpath = $this->getXpathForTokenInTree($token, $parent);
if (!$message) {
$message = "Token $token found.";
}
$this->assertCount(1, $this->xpath($xpath), $message);
}
/**
* Check to see if the specified token is present in the token browser.
*
* @param $token
* The token name with the surrounding square brackets [].
* @param string $parent
* (optional) The parent CSS identifier of this token.
* @param string $message
* (optional) A message to display with the assertion.
*/
protected function assertTokenNotInTree($token, $parent = '', $message = '') {
$xpath = $this->getXpathForTokenInTree($token, $parent);
if (!$message) {
$message = "Token $token not found.";
}
$this->assertCount(0, $this->xpath($xpath), $message);
}
/**
* Get xpath to check for token in tree.
*
* @param $token
* The token name with the surrounding square brackets [].
* @param string $parent
* (optional) The parent CSS identifier of this token.
*
* @return string
* The xpath to check for the token and parent.
*/
protected function getXpathForTokenInTree($token, $parent = '') {
$xpath = "//tr";
if ($parent) {
$xpath .= '[@data-tt-parent-id="token-' . $parent . '"]';
}
$xpath .= '/td[contains(@class, "token-key") and text() = "' . $token . '"]';
return $xpath;
}
}
<?php
namespace Drupal\Tests\token\Functional\Tree;
use Drupal\Component\Serialization\Json;
use Drupal\Tests\token\Functional\TokenTestBase;
/**
* Tests token tree page.
*
* @group token
*/
class TreeTest extends TokenTestBase {
use TokenTreeTestTrait;
/**
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node'];
public function setUp() {
parent::setUp();
$this->account = $this->drupalCreateUser(['administer account settings']);
$this->drupalLogin($this->account);
}
/**
* Test various tokens that are possible on the site.
*/
public function testAllTokens() {
$this->drupalGet($this->getTokenTreeUrl(['token_types' => 'all']));
$this->assertTokenGroup('Current date');
$this->assertTokenGroup('Site information');
$this->assertTokenInTree('[current-date:html_date]', 'current-date');
$this->assertTokenInTree('[current-date:html_week]', 'current-date');
$this->assertTokenInTree('[date:html_date]', 'date');
$this->assertTokenInTree('[date:html_week]', 'date');
$this->assertTokenInTree('[current-user:account-name]', 'current-user');
$this->assertTokenInTree('[user:account-name]', 'user');
$this->assertTokenInTree('[current-page:url:unaliased]', 'current-page--url');
$this->assertTokenInTree('[current-page:url:unaliased:args]', 'current-page--url--unaliased');
$this->assertTokenInTree('[user:original:account-name]', 'user--original');
}
/**
* Test various tokens that are possible on the site.
*/
public function testGlobalTokens() {
$this->drupalGet($this->getTokenTreeUrl());
$this->assertTokenGroup('Current date');
$this->assertTokenGroup('Site information');
// Assert that non-global tokens are not listed.
$this->assertTokenNotInTree('[user:account-name]', 'user');
$this->assertTokenNotInTree('[user:original:account-name]', 'user--original');
// Assert some of the global tokens, just to be sure.
$this->assertTokenInTree('[current-date:html_date]', 'current-date');
$this->assertTokenInTree('[current-date:html_week]', 'current-date');
$this->assertTokenInTree('[current-user:account-name]', 'current-user');
$this->assertTokenInTree('[current-page:url:unaliased]', 'current-page--url');
$this->assertTokenInTree('[current-page:url:unaliased:args]', 'current-page--url--unaliased');
}
/**
* Tests if the token browser displays the user tokens.
*/
public function testUserTokens() {
$this->drupalGet($this->getTokenTreeUrl(['token_types' => ['user']]));
$this->assertTokenGroup('Users');
$this->assertTokenInTree('[user:account-name]', 'user');
$this->assertTokenInTree('[user:original:account-name]', 'user--original');
// Assert some of the restricted tokens to ensure they are not shown.
$this->assertTokenNotInTree('[user:one-time-login-url]', 'user');
$this->assertTokenNotInTree('[user:original:cancel-url]', 'user--original');
// Request with show_restricted set to TRUE to show restricted tokens and
// check for them.
$this->drupalGet($this->getTokenTreeUrl(['token_types' => ['user'], 'show_restricted' => TRUE]));
$this->assertEquals('MISS', $this->drupalGetHeader('x-drupal-dynamic-cache'), 'Cache was not hit');
$this->assertTokenInTree('[user:one-time-login-url]', 'user');
$this->assertTokenInTree('[user:original:cancel-url]', 'user--original');
}
/**
* Tests if the token browser displays the node tokens.
*/
public function testNodeTokens() {
$this->drupalGet($this->getTokenTreeUrl(['token_types' => ['node']]));
$this->assertTokenGroup('Nodes');
$this->assertTokenInTree('[node:body]', 'node');
$this->assertTokenInTree('[node:author:original:account-name]', 'node--author--original');
}
/**
* Get the URL for the token tree based on the specified options.
*
* The token tree route's URL requires CSRF and cannot be generated in the
* test code. The CSRF token generated using the test runner's session is
* different from the session inside the test environment. This is why the
* link has to be generated inside the environment.
*
* This function calls a page in token_module_test module which generates the
* link and the token. This then replaces the options query parameter with the
* specified options.
*
* The page also uses a title callback to set title to a render array, which
* allows us to test if [current-page:title] works properly.
*
* @param array $options
* The options for the token tree browser.
*
* @return string
* The complete URL of the token tree browser with the CSRF token.
*/
protected function getTokenTreeUrl($options = []) {
$this->drupalGet('token_module_test/browse');
$this->assertSession()->titleEquals('Available Tokens | Drupal');
$links = $this->xpath('//a[contains(@href, :href)]/@href', [':href' => 'token/tree']);
$link = $this->getAbsoluteUrl(current($links)->getText());
if (!empty($options)) {
$options = Json::encode($options);
$link = str_replace('options=%5B%5D', 'options=' . urlencode($options), $link);
}
return $link;
}
}
<?php
namespace Drupal\Tests\token\Functional;
use Drupal\block\Entity\Block;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
/**
* Tests URL tokens.
*
* @group token
*/
class UrlTest extends BrowserTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* The first testing node.
*
* @var \Drupal\node\NodeInterface
*/
protected $node1;
/**
* The second testing node.
*
* @var \Drupal\node\NodeInterface
*/
protected $node2;
/**
* Modules to install.
*
* @var string[]
*/
public static $modules = ['node', 'token', 'block'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$node_type = NodeType::create(['type' => 'article', 'name' => 'Article']);
$node_type->save();
$this->node1 = Node::create([
'type' => 'article',
'title' => 'Test Node 1',
]);
$this->node1->save();
$this->node2 = Node::create([
'type' => 'article',
'title' => 'Test Node 2',
]);
$this->node2->save();
}
/**
* Creates a block with token for title and tests cache contexts.
*
* @throws \Behat\Mink\Exception\ElementHtmlException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function testBlockUrlTokenReplacement() {
$node1_url = $this->node1->toUrl();
$node2_url = $this->node2->toUrl();
// Using a @dataprovider causes repeated database installations and takes a
// very long time.
$tests = [];
$tests[] = [
'token' => 'prefix_[current-page:url:path]_suffix',
'expected1' => 'prefix_/' . $node1_url->getInternalPath() . '_suffix',
'expected2' => 'prefix_/' . $node2_url->getInternalPath() . '_suffix',
// A path can only be generated from a routed path.
'expected3' => 'prefix_/_suffix',
];
$tests[] = [
'token' => 'prefix_[current-page:url]_suffix',
'expected1' => 'prefix_' . $node1_url->setAbsolute()->toString() . '_suffix',
'expected2' => 'prefix_' . $node2_url->setAbsolute()->toString() . '_suffix',
'expected3' => 'prefix_' . $this->getAbsoluteUrl('does-not-exist') . '_suffix',
];
// Place a standard block and use a token in the label.
$edit = [
'id' => 'token_url_test_block',
'label' => 'label',
'label_display' => TRUE,
];
$this->placeBlock('system_powered_by_block', $edit);
$block = Block::load('token_url_test_block');
$assert_session = $this->assertSession();
foreach ($tests as $test) {
// Set the block label.
$block->getPlugin()->setConfigurationValue('label', $test['token']);
$block->save();
// Go to the first node page and test that the token is correct.
$this->drupalGet($node1_url);
$assert_session->elementContains('css', '#block-token-url-test-block', $test['expected1']);
// Go to the second node page and check that the block title has changed.
$this->drupalGet($node2_url);
$assert_session->elementContains('css', '#block-token-url-test-block', $test['expected2']);
// Test the current page url on a 404 page.
$this->drupalGet('does-not-exist');
$assert_session->statusCodeEquals(404);
$assert_session->elementContains('css', '#block-token-url-test-block', $test['expected3']);
}
// Can't do this test in the for loop above, it's too different.
$block->getPlugin()->setConfigurationValue('label', 'prefix_[current-page:query:unicorns]_suffix');
$block->save();
// Test the parameter token.
$this->drupalGet($node1_url->setOption('query', ['unicorns' => 'fluffy']));
$this->assertCacheContext('url.query_args');
$assert_session->elementContains('css', '#block-token-url-test-block', 'prefix_fluffy_suffix');
// Change the parameter on the same page.
$this->drupalGet($node1_url->setOption('query', ['unicorns' => 'dead']));
$assert_session->elementContains('css', '#block-token-url-test-block', 'prefix_dead_suffix');
}
}
<?php
namespace Drupal\Tests\token\Kernel;
/**
* Tests array tokens.
*
* @group token
*/
class ArrayTest extends KernelTestBase {
function testArrayTokens() {
// Test a simple array.
$array = [0 => 'a', 1 => 'b', 2 => 'c', 4 => 'd'];
$tokens = [
'first' => 'a',
'last' => 'd',
'value:0' => 'a',
'value:2' => 'c',
'count' => 4,
'keys' => '0, 1, 2, 4',
'keys:value:3' => '4',
'keys:join' => '0124',
'reversed' => 'd, c, b, a',
'reversed:keys' => '4, 2, 1, 0',
'join:/' => 'a/b/c/d',
'join' => 'abcd',
'join:, ' => 'a, b, c, d',
'join: ' => 'a b c d',
];
$this->assertTokens('array', ['array' => $array], $tokens);
// Test a mixed simple and render array.
// 2 => c, 0 => a, 4 => d, 1 => b
$array = [
'#property' => 'value',
0 => 'a',
1 => ['#markup' => 'b', '#weight' => 0.01],
2 => ['#markup' => 'c', '#weight' => -10],
4 => ['#markup' => 'd', '#weight' => 0],
];
$tokens = [
'first' => 'c',
'last' => 'b',
'value:0' => 'a',
'value:2' => 'c',
'count' => 4,
'keys' => '2, 0, 4, 1',
'keys:value:3' => '1',
'keys:join' => '2041',
'reversed' => 'b, d, a, c',
'reversed:keys' => '1, 4, 0, 2',
'join:/' => 'c/a/d/b',
'join' => 'cadb',
'join:, ' => 'c, a, d, b',
'join: ' => 'c a d b',
];
$this->assertTokens('array', ['array' => $array], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\node\Entity\Node;
use Drupal\Core\Url;
/**
* Test the book tokens.
*
* @group token
*/
class BookTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'field', 'filter', 'text', 'node', 'book'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installSchema('book', ['book']);
$this->installSchema('node', ['node_access']);
$this->installConfig(['node', 'book', 'field']);
}
function testBookTokens() {
$book = Node::create([
'type' => 'book',
'title' => 'Book Main Page',
'book' => ['bid' => 'new'],
]);
$book->save();
$page1 = Node::create([
'type' => 'book',
'title' => '1st Page',
'book' => ['bid' => $book->id(), 'pid' => $book->id()],
]);
$page1->save();
$page2 = Node::create([
'type' => 'book',
'title' => '2nd Page',
'book' => ['bid' => $book->id(), 'pid' => $page1->id()],
]);
$page2->save();
$book_title = $book->getTitle();
$tokens = [
'nid' => $book->id(),
'title' => $book_title,
'book:title' => $book_title,
'book:root' => $book_title,
'book:root:nid' => $book->id(),
'book:root:title' => $book_title,
'book:root:url' => Url::fromRoute('entity.node.canonical', ['node' => $book->id()], ['absolute' => TRUE])->toString(),
'book:root:content-type' => 'Book page',
'book:parent' => null,
'book:parents' => null,
];
$this->assertTokens('node', ['node' => $book], $tokens);
$tokens = [
'nid' => $page1->id(),
'title' => $page1->getTitle(),
'book:title' => $book_title,
'book:root' => $book_title,
'book:root:nid' => $book->id(),
'book:root:title' => $book_title,
'book:root:url' => Url::fromRoute('entity.node.canonical', ['node' => $book->id()], ['absolute' => TRUE])->toString(),
'book:root:content-type' => 'Book page',
'book:parent:nid' => $book->id(),
'book:parent:title' => $book_title,
'book:parent:url' => Url::fromRoute('entity.node.canonical', ['node' => $book->id()], ['absolute' => TRUE])->toString(),
'book:parents:count' => 1,
'book:parents:join:/' => $book_title,
];
$this->assertTokens('node', ['node' => $page1], $tokens);
$tokens = [
'nid' => $page2->id(),
'title' => $page2->getTitle(),
'book:title' => $book_title,
'book:root' => $book_title,
'book:root:nid' => $book->id(),
'book:root:title' => $book_title,
'book:root:url' => Url::fromRoute('entity.node.canonical', ['node' => $book->id()], ['absolute' => TRUE])->toString(),
'book:root:content-type' => 'Book page',
'book:parent:nid' => $page1->id(),
'book:parent:title' => $page1->getTitle(),
'book:parent:url' => Url::fromRoute('entity.node.canonical', ['node' => $page1->id()], ['absolute' => TRUE])->toString(),
'book:parents:count' => 2,
'book:parents:join:/' => $book_title . '/' . $page1->getTitle(),
];
$this->assertTokens('node', ['node' => $page2], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Core\Url;
/**
* Tests comment tokens.
*
* @group token
*/
class CommentTest extends KernelTestBase {
use CommentTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'comment', 'field', 'text', 'entity_reference'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('comment');
$this->installSchema('comment', ['comment_entity_statistics']);
$node_type = NodeType::create(['type' => 'page', 'name' => 'Page']);
$node_type->save();
$this->installConfig(['comment']);
$this->addDefaultCommentField('node', 'page');
}
function testCommentTokens() {
$node = Node::create([
'type' => 'page',
'title' => $this->randomMachineName()
]);
$node->save();
$parent_comment = Comment::create([
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'name' => 'anonymous user',
'mail' => 'anonymous@example.com',
'subject' => $this->randomMachineName(),
'body' => $this->randomMachineName(),
]);
$parent_comment->save();
// Fix http://example.com/index.php/comment/1 fails 'url:path' test.
$parent_comment_path = $parent_comment->toUrl()->toString();
$tokens = [
'url' => $parent_comment->toUrl('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->setAbsolute()->toString(),
'url:absolute' => $parent_comment->toUrl('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->setAbsolute()->toString(),
'url:relative' => $parent_comment->toUrl('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->toString(),
'url:path' => $parent_comment_path,
'parent:url:absolute' => NULL,
];
$this->assertTokens('comment', ['comment' => $parent_comment], $tokens);
$comment = Comment::create([
'entity_id' => $node->id(),
'pid' => $parent_comment->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'name' => 'anonymous user',
'mail' => 'anonymous@example.com',
'subject' => $this->randomMachineName(),
'body' => $this->randomMachineName(),
]);
$comment->save();
// Fix http://example.com/index.php/comment/1 fails 'url:path' test.
$comment_path = Url::fromRoute('entity.comment.canonical', ['comment' => $comment->id()])->toString();
$tokens = [
'url' => $comment->toUrl('canonical', ['fragment' => "comment-{$comment->id()}"])->setAbsolute()->toString(),
'url:absolute' => $comment->toUrl('canonical', ['fragment' => "comment-{$comment->id()}"])->setAbsolute()->toString(),
'url:relative' => $comment->toUrl('canonical', ['fragment' => "comment-{$comment->id()}"])->toString(),
'url:path' => $comment_path,
'parent:url:absolute' => $parent_comment->toUrl('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->setAbsolute()->toString(),
];
$this->assertTokens('comment', ['comment' => $comment], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
/**
* Tests date tokens.
*
* @group token
*/
class DateTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['system', 'token_module_test']);
}
function testDateTokens() {
$tokens = [
'token_module_test' => '1984',
'invalid_format' => NULL,
];
$this->assertTokens('date', ['date' => 453859200], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\VocabularyInterface;
/**
* Tests entity tokens.
*
* @group token
*/
class EntityTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'taxonomy', 'text'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Create the default tags vocabulary.
$vocabulary = Vocabulary::create([
'name' => 'Tags',
'vid' => 'tags',
]);
$vocabulary->save();
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->vocab = $vocabulary;
}
function testEntityMapping() {
/** @var \Drupal\token\TokenEntityMapperInterface $mapper */
$mapper = \Drupal::service('token.entity_mapper');
$this->assertSame('node', $mapper->getEntityTypeForTokenType('node'));
$this->assertSame('taxonomy_term', $mapper->getEntityTypeForTokenType('term'));
$this->assertSame('taxonomy_vocabulary', $mapper->getEntityTypeForTokenType('vocabulary'));
$this->assertSame(FALSE, $mapper->getEntityTypeForTokenType('invalid'));
$this->assertSame('invalid', $mapper->getEntityTypeForTokenType('invalid', TRUE));
$this->assertSame('node', $mapper->getTokenTypeForEntityType('node'));
$this->assertSame('term', $mapper->getTokenTypeForEntityType('taxonomy_term'));
$this->assertSame('vocabulary', $mapper->getTokenTypeForEntityType('taxonomy_vocabulary'));
$this->assertSame(FALSE, $mapper->getTokenTypeForEntityType('invalid'));
$this->assertSame('invalid', $mapper->getTokenTypeForEntityType('invalid', TRUE));
// Test that when we send the mis-matched entity type into
// Drupal\Core\Utility\Token::replace() that we still get the tokens
// replaced.
$vocabulary = Vocabulary::load('tags');
$term = $this->addTerm($vocabulary);
$this->assertSame($vocabulary->label(), \Drupal::token()->replace('[vocabulary:name]', ['taxonomy_vocabulary' => $vocabulary]));
$this->assertSame($term->label() . $vocabulary->label(), \Drupal::token()->replace('[term:name][term:vocabulary:name]', ['taxonomy_term' => $term]));
}
function addTerm(VocabularyInterface $vocabulary, array $term = []) {
$term += [
'name' => mb_strtolower($this->randomMachineName(5)),
'vid' => $vocabulary->id(),
];
$term = Term::create($term);
$term->save();
return $term;
}
/**
* Test the [entity:original:*] tokens.
*/
function testEntityOriginal() {
$node = Node::create(['type' => 'page', 'title' => 'Original title']);
$node->save();
$tokens = [
'nid' => $node->id(),
'title' => 'Original title',
'original' => NULL,
'original:nid' => NULL,
];
$this->assertTokens('node', ['node' => $node], $tokens);
// Emulate the original entity property that would be available from
// node_save() and change the title for the node.
$node->original = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($node->id());
$node->title = 'New title';
$tokens = [
'nid' => $node->id(),
'title' => 'New title',
'original' => 'Original title',
'original:nid' => $node->id(),
];
$this->assertTokens('node', ['node' => $node], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\file\Entity\File;
/**
* Tests file tokens.
*
* @group token
*/
class FileTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['file'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->installEntitySchema('file');
}
function testFileTokens() {
// Create a test file object.
$file = File::create([
'fid' => 1,
'filename' => 'test.png',
'filesize' => 100,
'uri' => 'public://images/test.png',
'filemime' => 'image/png',
]);
$tokens = [
'basename' => 'test.png',
'extension' => 'png',
'size-raw' => 100,
];
$this->assertTokens('file', ['file' => $file], $tokens);
// Test a file with no extension and a fake name.
$file->filename = 'Test PNG image';
$file->uri = 'public://images/test';
$tokens = [
'basename' => 'test',
'extension' => '',
'size-raw' => 100,
];
$this->assertTokens('file', ['file' => $file], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\KernelTests\KernelTestBase as BaseKernelTestBase;
use Drupal\Tests\token\Functional\TokenTestTrait;
/**
* Helper test class with some added functions for testing.
*/
abstract class KernelTestBase extends BaseKernelTestBase {
use TokenTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['path', 'token', 'token_module_test', 'system', 'user', 'path_alias'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('path_alias');
\Drupal::service('router.builder')->rebuild();
$this->installConfig(['system']);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Core\Url;
/**
* Test the node and content type tokens.
*
* @group token
*/
class NodeTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'field', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$node_type = NodeType::create([
'type' => 'page',
'name' => 'Basic page',
'description' => "Use <em>basic pages</em> for your static content, such as an 'About us' page.",
]);
$node_type->save();
$node_type = NodeType::create([
'type' => 'article',
'name' => 'Article',
'description' => "Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.",
]);
$node_type->save();
}
function testNodeTokens() {
$page = Node::create([
'type' => 'page',
'title' => 'Source Title',
'revision_log' => $this->randomMachineName(),
'path' => ['alias' => '/content/source-node']
]);
$page->save();
$tokens = [
'log' => $page->revision_log->value,
'url:path' => '/content/source-node',
'url:absolute' => Url::fromRoute('entity.node.canonical', ['node' => $page->id()], ['absolute' => TRUE])->toString(),
'url:relative' => Url::fromRoute('entity.node.canonical', ['node' => $page->id()], ['absolute' => FALSE])->toString(),
'url:unaliased:path' => "/node/{$page->id()}",
'content-type' => 'Basic page',
'content-type:name' => 'Basic page',
'content-type:machine-name' => 'page',
'content-type:description' => "Use <em>basic pages</em> for your static content, such as an 'About us' page.",
'content-type:node-count' => 1,
'content-type:edit-url' => Url::fromRoute('entity.node_type.edit_form', ['node_type' => 'page'], ['absolute' => TRUE])->toString(),
'source:title' => 'Source Title',
// Deprecated tokens.
'type' => 'page',
'type-name' => 'Basic page',
'url:alias' => '/content/source-node',
];
$this->assertTokens('node', ['node' => $page], $tokens);
$article = Node::create([
'type' => 'article',
'title' => 'Source Title',
]);
$article->save();
$tokens = [
'log' => '',
'url:path' => "/node/{$article->id()}",
'url:absolute' => Url::fromRoute('entity.node.canonical', ['node' => $article->id()], ['absolute' => TRUE])->toString(),
'url:relative' => Url::fromRoute('entity.node.canonical', ['node' => $article->id()], ['absolute' => FALSE])->toString(),
'url:unaliased:path' => "/node/{$article->id()}",
'content-type' => 'Article',
'content-type:name' => 'Article',
'content-type:machine-name' => 'article',
'content-type:description' => "Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.",
'content-type:node-count' => 1,
'content-type:edit-url' => Url::fromRoute('entity.node_type.edit_form', ['node_type' => 'article'], ['absolute' => TRUE])->toString(),
'source:title' => 'Source Title',
// Deprecated tokens.
'type' => 'article',
'type-name' => 'Article',
'url:alias' => "/node/{$article->id()}",
];
$this->assertTokens('node', ['node' => $article], $tokens);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
/**
* Tests random tokens.
*
* @group token
*/
class RandomTest extends KernelTestBase {
function testRandomTokens() {
$tokens = [
'number' => '[0-9]{1,}',
'hash:md5' => '[0-9a-f]{32}',
'hash:sha1' => '[0-9a-f]{40}',
'hash:sha256' => '[0-9a-f]{64}',
'hash:invalid-algo' => NULL,
];
$first_set = $this->assertTokens('random', [], $tokens, ['regex' => TRUE]);
$second_set = $this->assertTokens('random', [], $tokens, ['regex' => TRUE]);
foreach ($first_set as $token => $value) {
$this->assertNotSame($first_set[$token], $second_set[$token]);
}
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Core\Url;
/**
* Tests taxonomy tokens.
*
* @group token
*/
class TaxonomyTest extends KernelTestBase {
protected $vocab;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['taxonomy', 'text', 'language'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
// Create the default tags vocabulary.
$vocabulary = Vocabulary::create([
'name' => 'Tags',
'vid' => 'tags',
]);
$vocabulary->save();
$this->vocab = $vocabulary;
}
/**
* Test the additional taxonomy term tokens.
*/
function testTaxonomyTokens() {
$root_term = $this->addTerm($this->vocab, ['name' => 'Root term', 'path' => ['alias' => '/root-term']]);
$tokens = [
'url' => Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $root_term->id()], ['absolute' => TRUE])->toString(),
'url:absolute' => Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $root_term->id()], ['absolute' => TRUE])->toString(),
'url:relative' => Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $root_term->id()], ['absolute' => FALSE])->toString(),
'url:path' => '/root-term',
'url:unaliased:path' => "/taxonomy/term/{$root_term->id()}",
'edit-url' => Url::fromRoute('entity.taxonomy_term.edit_form', ['taxonomy_term' => $root_term->id()], ['absolute' => TRUE])->toString(),
'parents' => NULL,
'parents:count' => NULL,
'parents:keys' => NULL,
'root' => NULL,
// Deprecated tokens
'url:alias' => '/root-term',
];
$this->assertTokens('term', ['term' => $root_term], $tokens);
$parent_term = $this->addTerm($this->vocab, ['name' => 'Parent term', 'parent' => $root_term->id()]);
$tokens = [
'url' => Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $parent_term->id()], ['absolute' => TRUE])->toString(),
'url:absolute' => Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $parent_term->id()], ['absolute' => TRUE])->toString(),
'url:relative' => Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $parent_term->id()], ['absolute' => FALSE])->toString(),
'url:path' => "/taxonomy/term/{$parent_term->id()}",
'url:unaliased:path' => "/taxonomy/term/{$parent_term->id()}",
'edit-url' => Url::fromRoute('entity.taxonomy_term.edit_form', ['taxonomy_term' => $parent_term->id()], ['absolute' => TRUE])->toString(),
'parents' => 'Root term',
'parents:count' => 1,
'parents:keys' => $root_term->id(),
'root' => $root_term->label(),
'root:tid' => $root_term->id(),
// Deprecated tokens
'url:alias' => "/taxonomy/term/{$parent_term->id()}",
];
$this->assertTokens('term', ['term' => $parent_term], $tokens);
$term = $this->addTerm($this->vocab, ['name' => 'Test term', 'parent' => $parent_term->id()]);
$tokens = [
'parents' => 'Root term, Parent term',
'parents:count' => 2,
'parents:keys' => implode(', ', [$root_term->id(), $parent_term->id()]),
];
$this->assertTokens('term', ['term' => $term], $tokens);
}
/**
* Test the additional vocabulary tokens.
*/
function testVocabularyTokens() {
$vocabulary = $this->vocab;
$tokens = [
'machine-name' => 'tags',
'edit-url' => Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], ['absolute' => TRUE])->toString(),
];
$this->assertTokens('vocabulary', ['vocabulary' => $vocabulary], $tokens);
}
function addVocabulary(array $vocabulary = []) {
$vocabulary += [
'name' => mb_strtolower($this->randomMachineName(5)),
'nodes' => ['article' => 'article'],
];
$vocabulary = Vocabulary::create($vocabulary)->save();
return $vocabulary;
}
function addTerm($vocabulary, array $term = []) {
$term += [
'name' => mb_strtolower($this->randomMachineName(5)),
'vid' => $vocabulary->id(),
];
$term = Term::create($term);
$term->save();
return $term;
}
/**
* Test the multilingual terms.
*/
function testMultilingualTerms() {
// Add a second language.
$language = ConfigurableLanguage::createFromLangcode('de');
$language->save();
// Create an english parent term and add a german translation for it.
$parent_term = $this->addTerm($this->vocab, [
'name' => 'english-parent-term',
'langcode' => 'en',
]);
$parent_term->addTranslation('de', [
'name' => 'german-parent-term',
])->save();
// Create a term related to the parent term.
$child_term = $this->addTerm($this->vocab, [
'name' => 'english-child-term',
'langcode' => 'en',
'parent' => $parent_term->id(),
]);
$child_term->addTranslation('de', [
'name' => 'german-child-term',
])->save();
// Expect the parent term to be in the specified language.
$this->assertTokens('term', ['term' => $child_term], ['parents' => 'german-parent-term'], ['langcode' => 'de']);
$this->assertTokens('term', ['term' => $child_term], ['root' => 'german-parent-term'], ['langcode' => 'de']);
}
}
<?php
namespace Drupal\Tests\token\Kernel;
/**
* Test basic, low-level token functions.
*
* @group token
*/
class UnitTest extends KernelTestBase {
/**
* @var \Drupal\token\Token
*/
protected $tokenService;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['file', 'node'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->tokenService = \Drupal::token();
}
/**
* Test invalid tokens.
*/
public function testGetInvalidTokens() {
$tests = [];
$tests[] = [
'valid tokens' => [
'[node:title]',
'[node:created:short]',
'[node:created:custom:invalid]',
'[node:created:custom:mm-YYYY]',
'[node:colons:in:name]',
'[site:name]',
'[site:slogan]',
'[current-date:short]',
'[current-user:uid]',
'[current-user:ip-address]',
],
'invalid tokens' => [
'[node:title:invalid]',
'[node:created:invalid]',
'[node:created:short:invalid]',
'[node:colons:in:name:invalid]',
'[invalid:title]',
'[site:invalid]',
'[user:ip-address]',
'[user:uid]',
'[comment:cid]',
// Deprecated tokens
'[node:tnid]',
'[node:type]',
'[node:type-name]',
'[date:short]',
],
'types' => ['node'],
];
$tests[] = [
'valid tokens' => [
'[node:title]',
'[node:created:short]',
'[node:created:custom:invalid]',
'[node:created:custom:mm-YYYY]',
'[node:colons:in:name]',
'[site:name]',
'[site:slogan]',
'[user:uid]',
'[current-date:short]',
'[current-user:uid]',
],
'invalid tokens' => [
'[node:title:invalid]',
'[node:created:invalid]',
'[node:created:short:invalid]',
'[node:colons:in:name:invalid]',
'[invalid:title]',
'[site:invalid]',
'[user:ip-address]',
'[comment:cid]',
// Deprecated tokens
'[node:tnid]',
'[node:type]',
'[node:type-name]',
],
'types' => ['all'],
];
foreach ($tests as $test) {
$tokens = array_merge($test['valid tokens'], $test['invalid tokens']);
shuffle($tokens);
$invalid_tokens = $this->tokenService->getInvalidTokensByContext(implode(' ', $tokens), $test['types']);
sort($invalid_tokens);
sort($test['invalid tokens']);
$this->assertEquals($test['invalid tokens'], $invalid_tokens, 'Invalid tokens detected properly: ' . implode(', ', $invalid_tokens));
}
}
/**
* Test that tokens are generated only for content entities.
*/
public function testContentEntityOnlyTokens() {
// Verify that type and token info for a config entity is not generated.
$this->assertNull($this->tokenService->getTokenInfo('user_role', 'original'));
$this->assertNull($this->tokenService->getTokenInfo('user_role', 'url'));
$this->assertNull($this->tokenService->getTypeInfo('user_role'));
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
/**
* Test generic url token replacements.
*
* @group token
*/
class UrlTest extends KernelTestBase {
/**
* The token service.
*
* @var \Drupal\Core\Utility\Token
*/
protected $token;
/**
* The current request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The current route match.
*
* @var \Drupal\Core\Routing\CurrentRouteMatch
*/
protected $currentRouteMatch;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->token = $this->container->get('token');
$this->requestStack = $this->container->get('request_stack');
$this->currentRouteMatch = $this->container->get('current_route_match');
}
/**
* Test the url token replacements for current requests.
*
* The method ::expectedCurrentRequestUrlResults() is not declared
* as a regular data provider, because it might use services from
* the global Drupal container, which is not initialized yet during
* the invocation of data providers.
*/
public function testCurrentRequestUrls() {
foreach ($this->expectedCurrentRequestUrlResults() as $data_set) {
list ($request, $text, $data, $options, $expected_output) = $data_set;
// Set the request as the current one.
$this->requestStack->pop();
$this->requestStack->push($request);
$this->currentRouteMatch->resetRouteMatch();
$this->assertEquals($expected_output, $this->token->replace($text, $data, $options));
}
}
/**
* Provides a list of results to expect for ::testRequestUrls().
*
* Each data set of this array holds the following order:
* - The request object to test for.
* - The input text as string.
* - The token data as array.
* - Further options for the token replacement as array.
* - The output to expect after token replacement.
*
* @return array
* The list of results to expect.
*/
public function expectedCurrentRequestUrlResults() {
return [
[Request::createFromGlobals(), '[current-page:url]', [], [], Url::createFromRequest(Request::createFromGlobals())->setAbsolute()->toString()],
[Request::create('/should-not-exist'), '[current-page:url:path]', [], [], '/'],
[Request::create('/https://drupal.org/'), '[current-page:url:absolute]', [], [], '[current-page:url:absolute]'],
[Request::create('/https://drupal.org/'), '[current-page:url:absolute]', [], ['clear' => TRUE], ''],
];
}
}
<?php
namespace Drupal\Tests\token\Kernel;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
/**
* Test the views tokens.
*
* @group token
*/
class ViewsTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['views', 'block'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['token_views_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
ViewTestData::createTestViews(get_class($this), ['token_module_test']);
}
/**
* Tests path token replacements generated from a view without a path.
*/
public function testTokenReplacementNoPath() {
$token_handler = \Drupal::token();
$view = Views::getView('token_views_test');
$view->setDisplay('block_1');
$view->execute();
$this->assertSame('', $token_handler->replace('[view:url]', ['view' => $view]), 'Token [view:url] is empty for views without path.');
}
}
<?php
/**
* @file
* Drush integration for the Token module.
*/
/**
* Implements hook_drush_cache_clear().
*/
function token_drush_cache_clear(&$types) {
$types['token'] = 'drush_token_cache_clear_token_info';
}
/**
* Clear caches internal to Token module.
*/
function drush_token_cache_clear_token_info() {
token_clear_cache();
}
type: module
name: Token
description: Provides a user interface for the Token API and some missing core tokens.
core_version_requirement: ^8.8 || ^9
# Information added by Drupal.org packaging script on 2020-12-18
version: '8.x-1.9'
project: 'token'
datestamp: 1608284868
jquery.treeTable:
remote: 'http://plugins.jquery.com/treetable/'
version: 3.2.0
license:
name: MIT
url: https://github.com/ludo/jquery-treetable/blob/3.2.0/MIT-LICENSE.txt
gpl-compatible: true
js:
js/jquery.treetable.js: {}
css:
component:
css/jquery.treetable.css: {}
css/token.treetable.theme.css: {}
dependencies:
- core/jquery
token:
version: VERSION
js:
js/token.js: {}
css:
component:
css/token.css: {}
dependencies:
- core/jquery
- core/drupal
token.devel_entities:
class: \Drupal\Core\Menu\LocalTaskDefault
deriver: \Drupal\token\Plugin\Derivative\DevelLocalTask
<?php
/**
* @file
* User page callbacks for the token module.
*/
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\DiffArray;
use Drupal\Core\Link;
use Drupal\Core\Url;
/**
* Theme a link to a token tree shown as a dialog.
*/
function template_preprocess_token_tree_link(&$variables) {
if (empty($variables['text'])) {
$variables['text'] = t('Browse available tokens.');
}
$variables['#attached']['library'][] = 'core/drupal.dialog.ajax';
$variables['#attached']['library'][] = 'token/token';
$variables['options']['attributes']['class'][] = 'token-dialog';
$variables['options']['attributes']['class'][] = 'use-ajax';
$tree_variables = [
'token_types' => [],
'global_types' => TRUE,
'click_insert' => TRUE,
'show_restricted' => FALSE,
'show_nested' => FALSE,
'recursion_limit' => 3,
];
$query_options = array_intersect_key($variables, $tree_variables);
$query_options = DiffArray::diffAssocRecursive($query_options, $tree_variables);
if (!isset($variables['options']['query']['options'])) {
$variables['options']['query']['options'] = [];
}
$variables['options']['query']['options'] += $query_options;
// Because PHP converts query strings with arrays into a different syntax on
// the next request, the options have to be encoded with JSON in the query
// string so that we can reliably decode it for token comparison.
$variables['options']['query']['options'] = Json::encode($variables['options']['query']['options']);
// Set the token tree to open in a separate window.
$variables['options']['attributes'] += [
'data-dialog-type' => 'dialog',
'data-dialog-options' => json_encode([
'dialogClass' => 'token-tree-dialog',
'width' => 600,
'height' => 400,
'position' => ['my' => 'right bottom', 'at' => 'right-10 bottom-10'],
'draggable' => TRUE,
'autoResize' => FALSE,
]),
];
$variables['link'] = Link::createFromRoute($variables['text'], 'token.tree', [], $variables['options'])->toRenderable();
$variables['url'] = new Url('token.tree', [], $variables['options']);
$variables['attributes'] = $variables['options']['attributes'];
}
token.tree:
path: '/token/tree'
defaults:
_controller: '\Drupal\token\Controller\TokenTreeController::outputTree'
requirements:
_csrf_token: 'TRUE'
token.autocomplete:
path: '/token/autocomplete/{token_type}/{filter}'
defaults:
_controller: '\Drupal\token\Controller\TokenAutocompleteController::autocomplete'
requirements:
_access: 'TRUE'
token.flush_cache:
path: '/token/flush-cache'
defaults:
_controller: '\Drupal\token\Controller\TokenCacheController::flush'
requirements:
_permission: 'flush caches'
_csrf_token: 'TRUE'
_module_dependencies: 'admin_menu'
services:
token.entity_mapper:
class: Drupal\token\TokenEntityMapper
arguments: ['@entity_type.manager', '@module_handler']
token.tree_builder:
class: Drupal\token\TreeBuilder
arguments: ['@token', '@token.entity_mapper', '@cache.data', '@language_manager']
token.route_subscriber:
class: Drupal\token\Routing\RouteSubscriber
arguments: ['@entity_type.manager', '@module_handler']
tags:
- { name: event_subscriber }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment