diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc
index 3294044..3946d3d 100644
--- a/core/modules/node/node.tokens.inc
+++ b/core/modules/node/node.tokens.inc
@@ -7,7 +7,6 @@
use Drupal\Component\Utility\Html;
use Drupal\Core\Datetime\Entity\DateFormat;
-use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\user\Entity\User;
@@ -94,15 +93,16 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
$langcode = $options['langcode'];
}
else {
- $langcode = LanguageInterface::LANGCODE_DEFAULT;
+ $langcode = NULL;
}
$sanitize = !empty($options['sanitize']);
- $replacements = array();
+ $replacements = [];
if ($type == 'node' && !empty($data['node'])) {
/** @var \Drupal\node\NodeInterface $node */
- $node = $data['node'];
+ $node = \Drupal::entityManager()
+ ->getTranslationFromContext($data['node'], $langcode, ['operation' => 'node_tokens']);
foreach ($tokens as $name => $original) {
switch ($name) {
@@ -130,10 +130,8 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
case 'body':
case 'summary':
- $translation = \Drupal::entityManager()->getTranslationFromContext($node, $langcode, array('operation' => 'node_tokens'));
- if ($translation->hasField('body') && ($items = $translation->get('body')) && !$items->isEmpty()) {
+ if ($node->hasField('body') && ($items = $node->get('body')) && !$items->isEmpty()) {
$item = $items[0];
- $field_definition = \Drupal::entityManager()->getFieldDefinitions('node', $node->bundle())['body'];
// If the summary was requested and is not empty, use it.
if ($name == 'summary' && !empty($item->summary)) {
$output = $sanitize ? $item->summary_processed : $item->summary;
@@ -183,15 +181,14 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
break;
case 'created':
- $date_format = DateFormat::load('medium');
- $bubbleable_metadata->addCacheableDependency($date_format);
- $replacements[$original] = format_date($node->getCreatedTime(), 'medium', '', NULL, $langcode);
- break;
-
case 'changed':
+ /** @var \Drupal\Core\Datetime\Entity\DateFormat $date_format */
$date_format = DateFormat::load('medium');
+ $date_raw = ($name === 'created' ? $node->getCreatedTime() : $node->getChangedTime());
+ $date_formatted = \Drupal::service('date.formatter')
+ ->format($date_raw, $date_format->id(), '', NULL, $langcode);
$bubbleable_metadata->addCacheableDependency($date_format);
- $replacements[$original] = format_date($node->getChangedTime(), 'medium', '', NULL, $langcode);
+ $replacements[$original] = $sanitize ? Html::escape($date_formatted) : $date_formatted;
break;
}
}
diff --git a/core/modules/node/src/Tests/NodeTokenLanguageTest.php b/core/modules/node/src/Tests/NodeTokenLanguageTest.php
new file mode 100644
index 0000000..b3a5e8b
--- /dev/null
+++ b/core/modules/node/src/Tests/NodeTokenLanguageTest.php
@@ -0,0 +1,165 @@
+drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
+
+ // Setup user.
+ $admin_user = $this->drupalCreateUser([
+ 'administer languages',
+ 'administer content types',
+ 'access administration pages',
+ 'create page content',
+ 'edit own page content'
+ ]);
+ $this->drupalLogin($admin_user);
+
+ // Add a new language.
+ ConfigurableLanguage::createFromLangcode('it')->save();
+
+ // Enable URL language detection and selection.
+ $edit = ['language_interface[enabled][language-url]' => '1'];
+ $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
+
+ // Set "Basic page" content type to use multilingual support.
+ $edit = ['language_configuration[language_alterable]' => TRUE];
+ $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
+ $this->assertRaw(t('The content type %type has been updated.', ['%type' => 'Basic page']), 'Basic page content type has been updated.');
+
+ // Make node body translatable.
+ $field_storage = FieldStorageConfig::loadByName('node', 'body');
+ $field_storage->setTranslatable(TRUE);
+ $field_storage->save();
+ }
+
+ /**
+ * Test if node tokes are multilingual.
+ */
+ public function testMultilingualNodeTokens() {
+ // Setup users.
+ foreach (['en', 'it'] as $langcode) {
+ $this->users[$langcode] = $this->drupalCreateUser([
+ 'create page content',
+ 'edit own page content'
+ ]);
+ }
+
+ // Create English "Basic page" node.
+ $langcode = 'en';
+ $this->drupalLogin($this->users[$langcode]);
+ $fields = $this->getFieldsArray($langcode);
+
+ $node = Node::create($fields);
+ $node->save();
+
+ $this->assertTokenReplacement($node);
+
+ // Create italian translation with new field values.
+ $langcode = 'it';
+ $this->drupalLogin($this->users[$langcode]);
+ $fields = $this->getFieldsArray($langcode);
+ $node->addTranslation($langcode, $fields);
+ $translation = $node->getTranslation($langcode);
+
+ $this->assertTokenReplacement($translation);
+ }
+
+ /**
+ * Populates an array with field values for a given language.
+ *
+ * @param string $langcode
+ * The language code.
+ *
+ * @return array
+ * The fields of node entity.
+ */
+ protected function getFieldsArray($langcode) {
+ $time = REQUEST_TIME - rand(0, 1000);
+ return [
+ 'title' => $langcode . $this->randomString(8),
+ 'body' => $langcode . $this->randomString(16),
+ 'type' => 'page',
+ 'language' => $langcode,
+ 'uid' => $this->users[$langcode]->id(),
+ 'created' => $time,
+ 'changed' => $time + 100,
+ ];
+ }
+
+ /**
+ * Asserts if tokens are correctly replaced.
+ *
+ * @param \Drupal\node\Entity\Node $node
+ * The node for which to test token replacement.
+ */
+ protected function assertTokenReplacement(Node $node) {
+ // Perform unsanitized replacement for easy comparison.
+ $options = ['langcode' => $node->language()->getId(), 'sanitize' => FALSE];
+
+ $tokens = [
+ '[node:title]' => [
+ 'expected' => $node->title->value,
+ 'actual' => \Drupal::token()->replace('[node:title]', ['node' => $node], $options)
+ ],
+ '[node:body]' => [
+ 'expected' => $node->body->value,
+ 'actual' => $token_replacement = \Drupal::token()->replace('[node:body]', ['node' => $node], $options)
+ ],
+ '[node:author]' => [
+ 'expected' => $node->getOwner()->getUsername(),
+ 'actual' => \Drupal::token()->replace('[node:author]', ['node' => $node], $options)
+ ],
+ '[node:created]' => [
+ 'expected' => \Drupal::service('date.formatter')->format($node->created->value, 'medium', '', NULL, $options['langcode']),
+ 'actual' => \Drupal::token()->replace('[node:created]', ['node' => $node], $options)
+ ],
+ '[node:changed]' => [
+ 'expected' => \Drupal::service('date.formatter')->format($node->changed->value, 'medium', '', NULL, $options['langcode']),
+ 'actual' => \Drupal::token()->replace('[node:changed]', ['node' => $node], $options),
+ ],
+ ];
+
+ foreach ($tokens as $token => $replacements) {
+ $this->assertEqual($replacements['expected'], $replacements['actual'], $token . ' replaced successfully');
+ }
+ }
+
+}
diff --git a/core/modules/node/src/Tests/NodeTokenReplaceTest.php b/core/modules/node/src/Tests/NodeTokenReplaceTest.php
index 3ec91d9..f8bd0bd 100644
--- a/core/modules/node/src/Tests/NodeTokenReplaceTest.php
+++ b/core/modules/node/src/Tests/NodeTokenReplaceTest.php
@@ -8,12 +8,14 @@
namespace Drupal\node\Tests;
use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\node\NodeTypeInterface;
use Drupal\system\Tests\System\TokenReplaceUnitTestBase;
+use Drupal\node\Entity\NodeType;
/**
- * Generates text using placeholders for dummy content to check node token
- * replacement.
+ * Generates text using placeholders for dummy content to check node token replacement.
*
* @group node
*/
@@ -24,129 +26,209 @@ class NodeTokenReplaceTest extends TokenReplaceUnitTestBase {
*
* @var array
*/
- public static $modules = array('node', 'filter');
+ public static $modules = ['filter', 'node'];
+
+ /**
+ * The node type.
+ *
+ * @var \Drupal\node\NodeTypeInterface
+ */
+ protected $nodeType;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
- $this->installConfig(array('filter', 'node'));
+ $this->installConfig(static::$modules);
- $node_type = entity_create('node_type', array('type' => 'article', 'name' => 'Article'));
- $node_type->save();
- node_add_body_field($node_type);
+ /** @var NodeTypeInterface $node_type */
+ $this->nodeType = NodeType::create([
+ 'type' => 'article',
+ 'name' => 'My "<Article>"',
+ ]);
+ $this->nodeType->save();
+ node_add_body_field($this->nodeType);
}
/**
* Creates a node, then tests the tokens generated from it.
*/
- function testNodeTokenReplacement() {
- $url_options = array(
+ public function testNodeTokenReplacement() {
+ $url_options = [
'absolute' => TRUE,
'language' => $this->interfaceLanguage,
- );
+ ];
// Create a user and a node.
$account = $this->createUser();
/* @var $node \Drupal\node\NodeInterface */
$node = entity_create('node', array(
- 'type' => 'article',
- 'tnid' => 0,
+ 'type' => $this->nodeType->id(),
'uid' => $account->id(),
'title' => '',
- 'body' => array(array('value' => $this->randomMachineName(32), 'summary' => $this->randomMachineName(16), 'format' => 'plain_text')),
- ));
- $node->save();
-
- // Generate and test sanitized tokens.
- $tests = array();
- $tests['[node:nid]'] = $node->id();
- $tests['[node:vid]'] = $node->getRevisionId();
- $tests['[node:type]'] = 'article';
- $tests['[node:type-name]'] = 'Article';
- $tests['[node:title]'] = Html::escape($node->getTitle());
- $tests['[node:body]'] = $node->body->processed;
- $tests['[node:summary]'] = $node->body->summary_processed;
- $tests['[node:langcode]'] = Html::escape($node->language()->getId());
- $tests['[node:url]'] = $node->url('canonical', $url_options);
- $tests['[node:edit-url]'] = $node->url('edit-form', $url_options);
- $tests['[node:author]'] = Html::escape($account->getUsername());
- $tests['[node:author:uid]'] = $node->getOwnerId();
- $tests['[node:author:name]'] = Html::escape($account->getUsername());
- $tests['[node:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getCreatedTime(), array('langcode' => $this->interfaceLanguage->getId()));
- $tests['[node:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getChangedTime(), array('langcode' => $this->interfaceLanguage->getId()));
-
- $base_bubbleable_metadata = BubbleableMetadata::createFromObject($node);
-
- $metadata_tests = [];
- $metadata_tests['[node:nid]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:vid]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:type]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:type-name]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:title]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:body]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:summary]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:langcode]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:url]'] = $base_bubbleable_metadata;
- $metadata_tests['[node:edit-url]'] = $base_bubbleable_metadata;
- $bubbleable_metadata = clone $base_bubbleable_metadata;
- $metadata_tests['[node:author]'] = $bubbleable_metadata->addCacheTags(['user:1']);
- $metadata_tests['[node:author:uid]'] = $bubbleable_metadata;
- $metadata_tests['[node:author:name]'] = $bubbleable_metadata;
- $bubbleable_metadata = clone $base_bubbleable_metadata;
- $metadata_tests['[node:created:since]'] = $bubbleable_metadata->setCacheMaxAge(0);
- $metadata_tests['[node:changed:since]'] = $bubbleable_metadata;
-
- // Test to make sure that we generated something for each token.
- $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
-
- foreach ($tests as $input => $expected) {
- $bubbleable_metadata = new BubbleableMetadata();
- $output = $this->tokenService->replace($input, array('node' => $node), array('langcode' => $this->interfaceLanguage->getId()), $bubbleable_metadata);
- $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced.', array('%token' => $input)));
- $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
- }
-
- // Generate and test unsanitized tokens.
- $tests['[node:title]'] = $node->getTitle();
- $tests['[node:body]'] = $node->body->value;
- $tests['[node:summary]'] = $node->body->summary;
- $tests['[node:langcode]'] = $node->language()->getId();
- $tests['[node:author:name]'] = $account->getUsername();
-
- foreach ($tests as $input => $expected) {
- $output = $this->tokenService->replace($input, array('node' => $node), array('langcode' => $this->interfaceLanguage->getId(), 'sanitize' => FALSE));
- $this->assertEqual($output, $expected, format_string('Unsanitized node token %token replaced.', array('%token' => $input)));
- }
-
- // Repeat for a node without a summary.
- $node = entity_create('node', array(
- 'type' => 'article',
+ 'body' => [
+ [
+ 'value' => '',
+ 'summary' => '',
+ 'format' => 'plain_text',
+ ],
+ ],
+ ));
+ $node->save();
+
+ // Generate and test sanitized tokens.
+ $tests = array();
+ $tests['[node:nid]'] = $node->id();
+ $tests['[node:vid]'] = $node->getRevisionId();
+ $tests['[node:type]'] = $this->nodeType->id();
+ $tests['[node:type-name]'] = Html::escape($this->nodeType->label());
+ $tests['[node:title]'] = Html::escape($node->getTitle());
+ $tests['[node:body]'] = $node->body->processed;
+ $tests['[node:summary]'] = $node->body->summary_processed;
+ $tests['[node:langcode]'] = Html::escape($node->language()->getId());
+ $tests['[node:url]'] = $node->url('canonical', $url_options);
+ $tests['[node:edit-url]'] = $node->url('edit-form', $url_options);
+ $tests['[node:author]'] = Html::escape($account->getUsername());
+ $tests['[node:author:uid]'] = $node->getOwnerId();
+ $tests['[node:author:name]'] = Html::escape($account->getUsername());
+ $tests['[node:created:since]'] = \Drupal::service('date.formatter')
+ ->formatTimeDiffSince($node->getCreatedTime(), array('langcode' => $this->interfaceLanguage->getId()));
+ $tests['[node:changed:since]'] = \Drupal::service('date.formatter')
+ ->formatTimeDiffSince($node->getChangedTime(), array('langcode' => $this->interfaceLanguage->getId()));
+
+ $base_bubbleable_metadata = BubbleableMetadata::createFromObject($node);
+ $author_bubbleable_metadata = clone $base_bubbleable_metadata;
+ $date_bubbleable_metadata = clone $base_bubbleable_metadata;
+ $metadata_tests = [];
+ $metadata_tests['[node:nid]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:vid]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:type]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:type-name]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:title]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:body]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:summary]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:langcode]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:url]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:edit-url]'] = $base_bubbleable_metadata;
+ $metadata_tests['[node:author]'] = $author_bubbleable_metadata->addCacheTags(['user:1']);
+ $metadata_tests['[node:author:uid]'] = $author_bubbleable_metadata;
+ $metadata_tests['[node:author:name]'] = $author_bubbleable_metadata;
+ $metadata_tests['[node:created:since]'] = $date_bubbleable_metadata->setCacheMaxAge(0);
+ $metadata_tests['[node:changed:since]'] = $date_bubbleable_metadata;
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
+
+ $data = ['node' => $node];
+ $options = [
+ 'langcode' => $this->interfaceLanguage->getId(),
+ 'sanitize' => TRUE,
+ ];
+ $msg = 'Sanitized node token %token replaced with %output. Expected is %expected';
+ $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
+
+ // Generate and test unsanitized tokens.
+ $tests['[node:type-name]'] = $this->nodeType->label();
+ $tests['[node:title]'] = $node->getTitle();
+ $tests['[node:body]'] = $node->body->value;
+ $tests['[node:summary]'] = $node->body->summary;
+ $tests['[node:langcode]'] = $node->language()->getId();
+ $tests['[node:author]'] = $account->getUsername();
+ $tests['[node:author:name]'] = $account->getUsername();
+ $options['sanitize'] = FALSE;
+ $msg = 'Unsanitized node token %token replaced with %output. Expected is %expected';
+ $this->assertTokenReplacement($tests, $data, $options, $msg);
+
+ // Repeat for a node without a summary.
+ $node = entity_create('node', array(
+ 'type' => $this->nodeType->id(),
'uid' => $account->id(),
'title' => '',
- 'body' => array(array('value' => $this->randomMachineName(32), 'format' => 'plain_text')),
- ));
- $node->save();
-
- // Generate and test sanitized token - use full body as expected value.
- $tests = array();
- $tests['[node:summary]'] = $node->body->processed;
-
- // Test to make sure that we generated something for each token.
- $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated for node without a summary.');
+ 'body' => [
+ [
+ 'value' => '',
+ 'format' => 'plain_text',
+ ],
+ ],
+ ));
+ $node->save();
+
+ // Generate and test sanitized token - use full body as expected value.
+ $tests = [
+ '[node:summary]' => $node->body->processed,
+ ];
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated for node without a summary.');
+
+ $data = ['node' => $node];
+ $options['sanitize'] = TRUE;
+ $msg = 'Sanitized node token %token replaced with %output for node without a summary. Expected is %expected';
+ $this->assertTokenReplacement($tests, $data, $options, $msg);
+
+ // Generate and test unsanitized tokens.
+ $tests['[node:summary]'] = $node->body->value;
+
+ $options['sanitize'] = FALSE;
+ $msg = 'Unsanitized node token %token replaced with %output for node without a summary. Expected is %expected';
+ $this->assertTokenReplacement($tests, $data, $options, $msg);
+ }
- foreach ($tests as $input => $expected) {
- $output = $this->tokenService->replace($input, array('node' => $node), array('language' => $this->interfaceLanguage));
- $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced for node without a summary.', array('%token' => $input)));
+ /**
+ * Asserts if tokens are correctly replaced and verifies that the correct metadata has been set.
+ *
+ * @param array $tests
+ * Expected results keyed by their respective tokens.
+ * @param array $data
+ * The data to perform the replacement on. @see Token::replace().
+ * @param array $options
+ * Additional replacement options. @see Token::replace().
+ * @param $message
+ * The message to display with the assertion. Can contain replacement tokens:
+ * - %token for the token name
+ * - %output for the value returned by the token replacement service
+ * - %expected for the expected result
+ * @param array $metadata_tests
+ * The metadata to verify. Keyed by the relevant token.
+ */
+ protected function assertTokenReplacementAndCheckMetadata(array $tests, array $data, array $options, $message, array $metadata_tests) {
+ foreach ($tests as $token => $expected) {
+ $bubbleable_metadata = new BubbleableMetadata();
+ $output = $this->tokenService->replace($token, $data, $options, $bubbleable_metadata);
+ $this->assertEqual($output, $expected, SafeMarkup::format($message, [
+ '%token' => $token,
+ '%output' => $output,
+ '%expected' => $expected,
+ ]));
+
+ $this->assertEqual($bubbleable_metadata, $metadata_tests[$token]);
}
+ }
- // Generate and test unsanitized tokens.
- $tests['[node:summary]'] = $node->body->value;
-
- foreach ($tests as $input => $expected) {
- $output = $this->tokenService->replace($input, array('node' => $node), array('language' => $this->interfaceLanguage, 'sanitize' => FALSE));
- $this->assertEqual($output, $expected, format_string('Unsanitized node token %token replaced for node without a summary.', array('%token' => $input)));
+ /**
+ * Asserts if tokens are correctly replaced.
+ *
+ * @param array $tests
+ * Expected results keyed by their respective tokens.
+ * @param array $data
+ * The data to perform the replacement on. @see Token::replace().
+ * @param array $options
+ * Aditional replacement options. @see Token::replace().
+ * @param $message
+ * The message to display with the assertion. Can contain replacement tokens:
+ * - %token for the token name
+ * - %output for the value returned by the token replacement service
+ * - %expected for the expected result
+ */
+ protected function assertTokenReplacement(array $tests, array $data, array $options, $message) {
+ foreach ($tests as $token => $expected) {
+ $output = $this->tokenService->replace($token, $data, $options);
+ $this->assertEqual($output, $expected, SafeMarkup::format($message, [
+ '%token' => $token,
+ '%output' => $output,
+ '%expected' => $expected,
+ ]));
}
}
diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php b/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php
index 34971db..c7c4f93 100644
--- a/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php
+++ b/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php
@@ -18,10 +18,16 @@
/**
* Returns a new vocabulary with random properties.
+ *
+ * @param array $values
+ * (optional) An array of values to set, keyed by property name.
+ *
+ * @return \Drupal\taxonomy\VocabularyInterface
+ * The new taxonomy vocabulary object.
*/
- function createVocabulary() {
+ function createVocabulary(array $values = array()) {
// Create a vocabulary.
- $vocabulary = entity_create('taxonomy_vocabulary', array(
+ $vocabulary = entity_create('taxonomy_vocabulary', $values + array(
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
'vid' => Unicode::strtolower($this->randomMachineName()),
diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTokenLanguageTest.php b/core/modules/taxonomy/src/Tests/TaxonomyTokenLanguageTest.php
new file mode 100644
index 0000000..cc23d4d
--- /dev/null
+++ b/core/modules/taxonomy/src/Tests/TaxonomyTokenLanguageTest.php
@@ -0,0 +1,121 @@
+drupalLogin($this->drupalCreateUser(['administer taxonomy']));
+
+ // Create a vocabulary to which the terms will be assigned.
+ $this->vocabulary = $this->createVocabulary();
+
+ // Add a new language.
+ ConfigurableLanguage::createFromLangcode('it')->save();
+ }
+
+ /**
+ * Test if taxonomy token are multilingual.
+ */
+ public function testMultilingualTaxonomyTokens() {
+ // Configure the vocabulary to not hide the language selector.
+ $edit = [
+ 'default_language[language_alterable]' => TRUE,
+ ];
+ $this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
+
+ // Create Term.
+ $langcode = 'en';
+ $fields = $this->getFieldsArray($langcode);
+ $term = $this->createTerm($this->vocabulary, $fields);
+
+ $this->verifyTokensCorrectlyReplaced($term);
+
+ $langcode = 'it';
+ $fields = $this->getFieldsArray($langcode);
+ $term = $term->addTranslation($langcode, $fields);
+
+ $this->verifyTokensCorrectlyReplaced($term);
+ }
+
+ /**
+ * Populates an array with field values for a given language.
+ *
+ * @param string $langcode
+ * The language code.
+ *
+ * @return array
+ * The fields of a term.
+ */
+ protected function getFieldsArray($langcode) {
+ return [
+ 'name' => $langcode . $this->randomString(8),
+ 'description' => $langcode . $this->randomString(16),
+ 'langcode' => $langcode,
+ ];
+ }
+
+ /**
+ * Verifies if tokens were correctly replaced.
+ *
+ * @param \Drupal\taxonomy\Entity\Term $term
+ * The term that needs to be checked.
+ */
+ protected function verifyTokensCorrectlyReplaced(Term $term) {
+ // Perform unsanitized replacement for easy comparison.
+ $options = ['langcode' => $term->language()->getId(), 'sanitize' => FALSE];
+
+ $tokens = [
+ '[term:name]' => [
+ 'expected' => $term->name->value,
+ 'actual' => \Drupal::token()->replace('[term:name]', ['term' => $term], $options)
+ ],
+ '[term:description]' => [
+ 'expected' => $term->description->value,
+ 'actual' => $token_replacement = \Drupal::token()->replace('[term:description]', ['term' => $term], $options)
+ ],
+ '[term:url]' => [
+ 'expected' => $term->url('canonical', array('absolute' => TRUE)),
+ 'actual' => \Drupal::token()->replace('[term:url]', ['term' => $term], $options)
+ ]
+ ];
+
+ foreach ($tokens as $token => $replacements) {
+ $this->assertEqual($replacements['expected'], $replacements['actual'], $token . ' replaced successfully');
+ }
+ }
+
+}
diff --git a/core/modules/taxonomy/src/Tests/TokenReplaceTest.php b/core/modules/taxonomy/src/Tests/TokenReplaceTest.php
index 4fcb0f1..3432009 100644
--- a/core/modules/taxonomy/src/Tests/TokenReplaceTest.php
+++ b/core/modules/taxonomy/src/Tests/TokenReplaceTest.php
@@ -8,6 +8,7 @@
namespace Drupal\taxonomy\Tests;
use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Render\BubbleableMetadata;
@@ -23,7 +24,7 @@ class TokenReplaceTest extends TaxonomyTestBase {
/**
* The vocabulary used for creating terms.
*
- * @var \Drupal\taxonomy\VocabularyInterface
+ * @var \Drupal\taxonomy\Entity\Vocabulary
*/
protected $vocabulary;
@@ -34,10 +35,22 @@ class TokenReplaceTest extends TaxonomyTestBase {
*/
protected $fieldName;
+ /**
+ * Token service.
+ *
+ * @var \Drupal\Core\Utility\Token
+ */
+ protected $tokenService;
+
protected function setUp() {
parent::setUp();
+
+ $this->tokenService = \Drupal::token();
+
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
- $this->vocabulary = $this->createVocabulary();
+ $this->vocabulary = $this->createVocabulary([
+ 'name' => 'V1 "<name>"',
+ ]);
$this->fieldName = 'taxonomy_' . $this->vocabulary->id();
$handler_settings = array(
@@ -64,23 +77,28 @@ protected function setUp() {
* Creates some terms and a node, then tests the tokens generated from them.
*/
function testTaxonomyTokenReplacement() {
- $token_service = \Drupal::token();
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
- // Create two taxonomy terms.
- $term1 = $this->createTerm($this->vocabulary);
- $term2 = $this->createTerm($this->vocabulary);
+ // Create two taxonomy terms with unsafe names.
+ $term1 = $this->createTerm($this->vocabulary, [
+ 'name' => 'T1 ',
+ ]);
+ $term2 = $this->createTerm($this->vocabulary, [
+ 'name' => 'T2 "<name>"',
+ ]);
// Edit $term2, setting $term1 as parent.
- $edit = array();
- $edit['name[0][value]'] = '';
- $edit['parent[]'] = array($term1->id());
+ $edit = [
+ 'name[0][value]' => '',
+ 'parent[]' => [$term1->id()],
+ ];
$this->drupalPostForm('taxonomy/term/' . $term2->id() . '/edit', $edit, t('Save'));
// Create node with term2.
- $edit = array();
- $node = $this->drupalCreateNode(array('type' => 'article'));
- $edit[$this->fieldName . '[]'] = $term2->id();
+ $node = $this->drupalCreateNode(['type' => 'article']);
+ $edit = [
+ $this->fieldName . '[]' => $term2->id(),
+ ];
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
// Generate and test sanitized tokens for term1.
@@ -90,29 +108,34 @@ function testTaxonomyTokenReplacement() {
$tests['[term:description]'] = $term1->description->processed;
$tests['[term:url]'] = $term1->url('canonical', array('absolute' => TRUE));
$tests['[term:node-count]'] = 0;
+ $tests['[term:parent]'] = '[term:parent]';
$tests['[term:parent:name]'] = '[term:parent:name]';
- $tests['[term:vocabulary:name]'] = Html::escape($this->vocabulary->label());
+ $tests['[term:parent:url]'] = '[term:parent:url]';
$tests['[term:vocabulary]'] = Html::escape($this->vocabulary->label());
+ $tests['[term:vocabulary:name]'] = Html::escape($this->vocabulary->label());
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1);
- $metadata_tests = array();
+ $bubbleable_metadata = clone $base_bubbleable_metadata;
+ $metadata_tests = [];
$metadata_tests['[term:tid]'] = $base_bubbleable_metadata;
$metadata_tests['[term:name]'] = $base_bubbleable_metadata;
$metadata_tests['[term:description]'] = $base_bubbleable_metadata;
$metadata_tests['[term:url]'] = $base_bubbleable_metadata;
$metadata_tests['[term:node-count]'] = $base_bubbleable_metadata;
+ $metadata_tests['[term:parent]'] = $base_bubbleable_metadata;
$metadata_tests['[term:parent:name]'] = $base_bubbleable_metadata;
- $bubbleable_metadata = clone $base_bubbleable_metadata;
- $metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
+ $metadata_tests['[term:parent:url]'] = $base_bubbleable_metadata;
$metadata_tests['[term:vocabulary]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
+ $metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
- foreach ($tests as $input => $expected) {
- $bubbleable_metadata = new BubbleableMetadata();
- $output = $token_service->replace($input, array('term' => $term1), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
- $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
- $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
- }
+ $data = ['term' => $term1];
+ $options = [
+ 'langcode' => $language_interface->getId(),
+ 'sanitize' => TRUE,
+ ];
+ $msg = 'Sanitized taxonomy term 1 token %token replaced with %output. Expected is %expected';
+ $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
// Generate and test sanitized tokens for term2.
$tests = array();
@@ -121,29 +144,32 @@ function testTaxonomyTokenReplacement() {
$tests['[term:description]'] = $term2->description->processed;
$tests['[term:url]'] = $term2->url('canonical', array('absolute' => TRUE));
$tests['[term:node-count]'] = 1;
+ $tests['[term:parent]'] = Html::escape($term1->getName());
$tests['[term:parent:name]'] = Html::escape($term1->getName());
$tests['[term:parent:url]'] = $term1->url('canonical', array('absolute' => TRUE));
$tests['[term:parent:parent:name]'] = '[term:parent:parent:name]';
+ $tests['[term:vocabulary]'] = Html::escape($this->vocabulary->label());
$tests['[term:vocabulary:name]'] = Html::escape($this->vocabulary->label());
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
- foreach ($tests as $input => $expected) {
- $output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId()));
- $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
- }
+ $data = ['term' => $term2];
+ $options['sanitize'] = TRUE;
+ $msg = 'Sanitized taxonomy term 2 token %token replaced with %output. Expected is %expected';
+ $this->assertTokenReplacement($tests, $data, $options, $msg);
// Generate and test unsanitized tokens.
$tests['[term:name]'] = $term2->getName();
$tests['[term:description]'] = $term2->getDescription();
+ $tests['[term:parent]'] = $term1->getName();
$tests['[term:parent:name]'] = $term1->getName();
+ $tests['[term:vocabulary]'] = $this->vocabulary->label();
$tests['[term:vocabulary:name]'] = $this->vocabulary->label();
- foreach ($tests as $input => $expected) {
- $output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
- $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy term token %token replaced.', array('%token' => $input)));
- }
+ $options['sanitize'] = FALSE;
+ $msg = 'Unsanitized taxonomy term 2 token %token replaced with %output. Expected is %expected';
+ $this->assertTokenReplacement($tests, $data, $options, $msg);
// Generate and test sanitized tokens.
$tests = array();
@@ -156,18 +182,75 @@ function testTaxonomyTokenReplacement() {
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
- foreach ($tests as $input => $expected) {
- $output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId()));
- $this->assertEqual($output, $expected, format_string('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
- }
+ $data = ['vocabulary' => $this->vocabulary];
+ $options['sanitize'] = TRUE;
+ $msg = 'Sanitized taxonomy vocabulary token %token replaced with %output. Expected is %expected';
+ $this->assertTokenReplacement($tests, $data, $options, $msg);
// Generate and test unsanitized tokens.
$tests['[vocabulary:name]'] = $this->vocabulary->label();
$tests['[vocabulary:description]'] = $this->vocabulary->getDescription();
- foreach ($tests as $input => $expected) {
- $output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
- $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
+ $options['sanitize'] = FALSE;
+ $msg = 'Unsanitized taxonomy vocabulary token %token replaced with %output. Expected is %expected.';
+ $this->assertTokenReplacement($tests, $data, $options, $msg);
+ }
+
+ /**
+ * Asserts if tokens are correctly replaced and verifies that the correct
+ * metadata has been set.
+ *
+ * @param array $tests
+ * Expected results keyed by their respective tokens.
+ * @param array $data
+ * The data to perform the replacement on. @see Token::replace().
+ * @param array $options
+ * Aditional replacement options. @see Token::replace().
+ * @param $message
+ * The message to display with the assertion. Can contain replacement tokens:
+ * - %token for the token name
+ * - %output for the value returned by the token replacement service
+ * - %expected for the expected result
+ * @param array $metadata_tests
+ * The metadata to verify. Keyed by the relevant token.
+ */
+ protected function assertTokenReplacementAndCheckMetadata(array $tests, array $data, array $options, $message, array $metadata_tests) {
+ foreach ($tests as $token => $expected) {
+ $bubbleable_metadata = new BubbleableMetadata();
+ $output = $this->tokenService->replace($token, $data, $options, $bubbleable_metadata);
+ $this->assertEqual($output, $expected, SafeMarkup::format($message, [
+ '%token' => $token,
+ '%output' => $output,
+ '%expected' => $expected,
+ ]));
+
+ $this->assertEqual($bubbleable_metadata, $metadata_tests[$token]);
+ }
+ }
+
+ /**
+ * Asserts if tokens are correctly replaced.
+ *
+ * @param array $tests
+ * Expected results keyed by their respective tokens.
+ * @param array $data
+ * The data to perform the replacement on. @see Token::replace().
+ * @param array $options
+ * Aditional replacement options. @see Token::replace().
+ * @param $message
+ * The message to display with the assertion. Can contain replacement tokens:
+ * - %token for the token name
+ * - %output for the value returned by the token replacement service
+ * - %expected for the expected result
+ */
+ protected function assertTokenReplacement(array $tests, array $data, array $options, $message) {
+ foreach ($tests as $token => $expected) {
+ $output = $this->tokenService->replace($token, $data, $options);
+ $this->assertEqual($output, $expected, SafeMarkup::format($message, [
+ '%token' => $token,
+ '%output' => $output,
+ '%expected' => $expected,
+ ]));
}
}
}
diff --git a/core/modules/taxonomy/taxonomy.tokens.inc b/core/modules/taxonomy/taxonomy.tokens.inc
index daf690b..73d0a5c 100644
--- a/core/modules/taxonomy/taxonomy.tokens.inc
+++ b/core/modules/taxonomy/taxonomy.tokens.inc
@@ -94,13 +94,32 @@ function taxonomy_token_info() {
* Implements hook_tokens().
*/
function taxonomy_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
+ // Return if the $type is not supported or the required data is missing.
+ if (($type != 'term' && $type != 'vocabulary')
+ || ($type == 'term' && empty($data['term']))
+ || ($type == 'vocabulary' && empty($data['vocabulary']))
+ ) {
+ return [];
+ }
+
+ $replacements = [];
$token_service = \Drupal::token();
- $replacements = array();
+ $url_options = ['absolute' => TRUE];
+ if (isset($options['langcode'])) {
+ $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
+ $langcode = $options['langcode'];
+ }
+ else {
+ $langcode = NULL;
+ }
$sanitize = !empty($options['sanitize']);
+
+ /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */
$taxonomy_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
- if ($type == 'term' && !empty($data['term'])) {
- $term = $data['term'];
+ if ($type == 'term') {
+ /** @var \Drupal\taxonomy\TermInterface $term */
+ $term = \Drupal::entityManager()->getTranslationFromContext($data['term'], $langcode, ['operation' => 'term_tokens']);
foreach ($tokens as $name => $original) {
switch ($name) {
@@ -117,7 +136,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
break;
case 'url':
- $replacements[$original] = $term->url('canonical', array('absolute' => TRUE));
+ $replacements[$original] = $term->url('canonical', $url_options);
break;
case 'node-count':
@@ -129,16 +148,17 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
break;
case 'vocabulary':
+ /** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */
$vocabulary = Vocabulary::load($term->bundle());
$bubbleable_metadata->addCacheableDependency($vocabulary);
- $replacements[$original] = Html::escape($vocabulary->label());
+ $replacements[$original] = $sanitize ? Html::escape($vocabulary->label()) : $vocabulary->label();
break;
case 'parent':
if ($parents = $taxonomy_storage->loadParents($term->id())) {
- $parent = array_pop($parents);
- $bubbleable_metadata->addCacheableDependency($parent);
- $replacements[$original] = Html::escape($parent->getName());
+ $term_first_parent = \Drupal::entityManager()->getTranslationFromContext(array_pop($parents), $langcode, ['operation' => 'term_tokens']);
+ $bubbleable_metadata->addCacheableDependency($term_first_parent);
+ $replacements[$original] = $sanitize ? Html::escape($term_first_parent->getName()) : $term_first_parent->getName();
}
break;
}
@@ -149,14 +169,16 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
$replacements += $token_service->generate('vocabulary', $vocabulary_tokens, array('vocabulary' => $vocabulary), $options, $bubbleable_metadata);
}
- if (($vocabulary_tokens = $token_service->findWithPrefix($tokens, 'parent')) && $parents = $taxonomy_storage->loadParents($term->id())) {
- $parent = array_pop($parents);
- $replacements += $token_service->generate('term', $vocabulary_tokens, array('term' => $parent), $options, $bubbleable_metadata);
+ if (($term_parent_tokens = $token_service->findWithPrefix($tokens, 'parent'))
+ && $term_parents = $taxonomy_storage->loadParents($term->id())
+ ) {
+ $term_first_parent = array_pop($term_parents);
+ $replacements += $token_service->generate('term', $term_parent_tokens, array('term' => $term_first_parent), $options, $bubbleable_metadata);
}
}
-
- elseif ($type == 'vocabulary' && !empty($data['vocabulary'])) {
- $vocabulary = $data['vocabulary'];
+ elseif ($type == 'vocabulary') {
+ /** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */
+ $vocabulary = \Drupal::entityManager()->getTranslationFromContext($data['vocabulary'], $langcode, ['operation' => 'vocabulary_tokens']);
foreach ($tokens as $name => $original) {
switch ($name) {