diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc
index 30bd1c6b95..aa89277b99 100644
--- a/core/modules/node/node.tokens.inc
+++ b/core/modules/node/node.tokens.inc
@@ -6,7 +6,6 @@
*/
use Drupal\Core\Datetime\Entity\DateFormat;
-use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\user\Entity\User;
@@ -93,13 +92,13 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
$langcode = $options['langcode'];
}
else {
- $langcode = LanguageInterface::LANGCODE_DEFAULT;
+ $langcode = NULL;
}
$replacements = [];
if ($type == 'node' && !empty($data['node'])) {
/** @var \Drupal\node\NodeInterface $node */
- $node = $data['node'];
+ $node = \Drupal::service('entity.repository')->getTranslationFromContext($data['node'], $langcode, ['operation' => 'node_tokens']);
foreach ($tokens as $name => $original) {
switch ($name) {
@@ -127,8 +126,7 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
case 'body':
case 'summary':
- $translation = \Drupal::service('entity.repository')->getTranslationFromContext($node, $langcode, ['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];
// If the summary was requested and is not empty, use it.
if ($name == 'summary' && !empty($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] = \Drupal::service('date.formatter')->format($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] = \Drupal::service('date.formatter')->format($node->getChangedTime(), 'medium', '', NULL, $langcode);
+ $replacements[$original] = $date_formatted;
break;
}
}
diff --git a/core/modules/taxonomy/taxonomy.tokens.inc b/core/modules/taxonomy/taxonomy.tokens.inc
index 0f3679d5fc..61b3910d92 100644
--- a/core/modules/taxonomy/taxonomy.tokens.inc
+++ b/core/modules/taxonomy/taxonomy.tokens.inc
@@ -95,8 +95,12 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
$token_service = \Drupal::token();
$replacements = [];
+
+ $langcode = isset($options['langcode']) ? $options['langcode'] : NULL;
+
if ($type == 'term' && !empty($data['term'])) {
- $term = $data['term'];
+ /** @var \Drupal\taxonomy\TermInterface $term */
+ $term = \Drupal::service('entity.repository')->getTranslationFromContext($data['term'], $langcode, ['operation' => 'term_tokens']);
foreach ($tokens as $name => $original) {
switch ($name) {
@@ -109,9 +113,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
break;
case 'description':
- // "processed" returns a \Drupal\Component\Render\MarkupInterface via
- // check_markup().
- $replacements[$original] = $term->description->processed;
+ $replacements[$original] = $term->getDescription();
break;
case 'url':
@@ -127,6 +129,7 @@ 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] = $vocabulary->label();
@@ -135,7 +138,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
case 'parent':
$taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
if ($parents = $taxonomy_storage->loadParents($term->id())) {
- $parent = array_pop($parents);
+ $parent = \Drupal::service('entity.repository')->getTranslationFromContext(array_pop($parents), $langcode, ['operation' => 'term_tokens']);
$bubbleable_metadata->addCacheableDependency($parent);
$replacements[$original] = $parent->getName();
}
@@ -158,6 +161,11 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
}
elseif ($type == 'vocabulary' && !empty($data['vocabulary'])) {
+ /** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */
+ $language_manager = \Drupal::languageManager();
+ $language = $language_manager->getLanguage($langcode);
+ $original_language = $language_manager->getConfigOverrideLanguage();
+ $language_manager->setConfigOverrideLanguage($language);
$vocabulary = $data['vocabulary'];
foreach ($tokens as $name => $original) {
@@ -190,6 +198,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
break;
}
}
+ $language_manager->setConfigOverrideLanguage($original_language);
}
return $replacements;
diff --git a/core/modules/taxonomy/tests/src/Functional/TokenReplaceTest.php b/core/modules/taxonomy/tests/src/Functional/TokenReplaceTest.php
index b1ece05835..4876853033 100644
--- a/core/modules/taxonomy/tests/src/Functional/TokenReplaceTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/TokenReplaceTest.php
@@ -5,6 +5,7 @@
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Tests\Traits\Core\AssertTokenReplacementTrait;
/**
* Generates text using placeholders for dummy content to check taxonomy token
@@ -14,6 +15,8 @@
*/
class TokenReplaceTest extends TaxonomyTestBase {
+ use AssertTokenReplacementTrait;
+
/**
* The vocabulary used for creating terms.
*
@@ -33,13 +36,23 @@ class TokenReplaceTest extends TaxonomyTestBase {
*/
protected $defaultTheme = 'stark';
+ /**
+ * Token service.
+ *
+ * @var \Drupal\Core\Utility\Token
+ */
+ protected $tokenService;
+
+ /**
+ * {@inheritdoc}
+ */
protected function setUp(): void {
parent::setUp();
$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 = [
@@ -68,12 +81,11 @@ protected function setUp(): void {
* Creates some terms and a node, then tests the tokens generated from them.
*/
public 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 = [];
@@ -91,13 +103,18 @@ public function testTaxonomyTokenReplacement() {
$tests = [];
$tests['[term:tid]'] = $term1->id();
$tests['[term:name]'] = $term1->getName();
- $tests['[term:description]'] = $term1->description->processed;
+ $tests['[term:description]'] = $term1->getDescription();
$tests['[term:url]'] = $term1->toUrl('canonical', ['absolute' => TRUE])->toString();
$tests['[term:node-count]'] = 0;
+ $tests['[term:parent]'] = '[term:parent]';
$tests['[term:parent:name]'] = '[term:parent:name]';
+ $tests['[term:parent:url]'] = '[term:parent:url]';
$tests['[term:vocabulary:name]'] = $this->vocabulary->label();
$tests['[term:vocabulary]'] = $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.');
+
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1);
$metadata_tests = [];
@@ -105,38 +122,68 @@ public function testTaxonomyTokenReplacement() {
$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;
+ $metadata_tests['[term:parent:url]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
$metadata_tests['[term:vocabulary]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
- foreach ($tests as $input => $expected) {
- $bubbleable_metadata = new BubbleableMetadata();
- $output = $token_service->replace($input, ['term' => $term1], ['langcode' => $language_interface->getId()], $bubbleable_metadata);
- $this->assertEqual($expected, $output, new FormattableMarkup('Sanitized taxonomy term token %token replaced.', ['%token' => $input]));
- $this->assertEqual($metadata_tests[$input], $bubbleable_metadata);
- }
+ $data = ['term' => $term1];
+ $options = ['langcode' => $language_interface->getId()];
+ $msg = 'Taxonomy term 1 token %token replaced with %output which is equal to %expected';
+ $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
// Generate and test sanitized tokens for term2.
$tests = [];
$tests['[term:tid]'] = $term2->id();
$tests['[term:name]'] = $term2->getName();
$tests['[term:description]'] = $term2->description->processed;
+ $tests['[term:description]'] = $term2->getDescription();
$tests['[term:url]'] = $term2->toUrl('canonical', ['absolute' => TRUE])->toString();
$tests['[term:node-count]'] = 1;
+ $tests['[term:vocabulary]'] = $this->vocabulary->label();
+ $tests['[term:parent]'] = $term1->getName();
+ $tests['[term:parent:tid]'] = $term1->id();
$tests['[term:parent:name]'] = $term1->getName();
+ $tests['[term:parent:description]'] = $term1->getDescription();
$tests['[term:parent:url]'] = $term1->toUrl('canonical', ['absolute' => TRUE])->toString();
+ $tests['[term:parent:node-count]'] = 0;
$tests['[term:parent:parent:name]'] = '[term:parent:parent:name]';
$tests['[term:vocabulary:name]'] = $this->vocabulary->label();
+ $tests['[term:parent:vocabulary]'] = $this->vocabulary->label();
+ $tests['[term:parent:vocabulary:name]'] = $this->vocabulary->label();
// Test to make sure that we generated something for each token.
$this->assertNotContains(0, array_map('strlen', $tests), 'No empty tokens generated.');
- foreach ($tests as $input => $expected) {
- $output = $token_service->replace($input, ['term' => $term2], ['langcode' => $language_interface->getId()]);
- $this->assertEqual($expected, $output, new FormattableMarkup('Sanitized taxonomy term token %token replaced.', ['%token' => $input]));
- }
+ $base_bubbleable_metadata = BubbleableMetadata::createFromObject($term2);
+ $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;
+ $bubbleable_metadata = clone $base_bubbleable_metadata;
+ $bubbleable_metadata = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
+ $metadata_tests['[term:vocabulary]'] = $bubbleable_metadata;
+ $metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata;
+ $base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1)->addCacheTags($term2->getCacheTags());
+ $metadata_tests['[term:parent]'] = $base_bubbleable_metadata;
+ $metadata_tests['[term:parent:tid]'] = $base_bubbleable_metadata;
+ $metadata_tests['[term:parent:name]'] = $base_bubbleable_metadata;
+ $metadata_tests['[term:parent:description]'] = $base_bubbleable_metadata;
+ $metadata_tests['[term:parent:url]'] = $base_bubbleable_metadata;
+ $metadata_tests['[term:parent:node-count]'] = $base_bubbleable_metadata;
+ $metadata_tests['[term:parent:parent:name]'] = $base_bubbleable_metadata;
+ $bubbleable_metadata = clone $base_bubbleable_metadata;
+ $bubbleable_metadata = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
+ $metadata_tests['[term:parent:vocabulary:name]'] = $bubbleable_metadata;
+ $metadata_tests['[term:parent:vocabulary]'] = $bubbleable_metadata;
+
+ $data = ['term' => $term2];
+ $msg = 'Taxonomy term 2 token %token replaced with %output which is equal to %expected';
+ $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
// Generate and test sanitized tokens.
$tests = [];
@@ -149,10 +196,18 @@ public function testTaxonomyTokenReplacement() {
// Test to make sure that we generated something for each token.
$this->assertNotContains(0, array_map('strlen', $tests), 'No empty tokens generated.');
- foreach ($tests as $input => $expected) {
- $output = $token_service->replace($input, ['vocabulary' => $this->vocabulary], ['langcode' => $language_interface->getId()]);
- $this->assertEqual($expected, $output, new FormattableMarkup('Sanitized taxonomy vocabulary token %token replaced.', ['%token' => $input]));
- }
+ $base_bubbleable_metadata = BubbleableMetadata::createFromObject($this->vocabulary);
+
+ $metadata_tests = [];
+ $metadata_tests['[vocabulary:vid]'] = $base_bubbleable_metadata;
+ $metadata_tests['[vocabulary:name]'] = $base_bubbleable_metadata;
+ $metadata_tests['[vocabulary:description]'] = $base_bubbleable_metadata;
+ $metadata_tests['[vocabulary:node-count]'] = $base_bubbleable_metadata;
+ $metadata_tests['[vocabulary:term-count]'] = $base_bubbleable_metadata;
+
+ $data = ['vocabulary' => $this->vocabulary];
+ $msg = 'Taxonomy vocabulary token %token replaced with %output which is equal to %expected';
+ $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
}
}
diff --git a/core/modules/taxonomy/tests/src/Traits/TaxonomyTestTrait.php b/core/modules/taxonomy/tests/src/Traits/TaxonomyTestTrait.php
index de4dce2449..8a783420ce 100644
--- a/core/modules/taxonomy/tests/src/Traits/TaxonomyTestTrait.php
+++ b/core/modules/taxonomy/tests/src/Traits/TaxonomyTestTrait.php
@@ -15,11 +15,15 @@
/**
* 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
- * A vocabulary used for testing.
+ * The new taxonomy vocabulary object.
+ * @throws \Drupal\Core\Entity\EntityStorageException
*/
- public function createVocabulary() {
- $vocabulary = Vocabulary::create([
+ public function createVocabulary(array $values = []) {
+ $vocabulary = Vocabulary::create($values + [
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
'vid' => mb_strtolower($this->randomMachineName()),
diff --git a/core/tests/Drupal/Tests/Traits/Core/AssertTokenReplacementTrait.php b/core/tests/Drupal/Tests/Traits/Core/AssertTokenReplacementTrait.php
new file mode 100644
index 0000000000..78a6a43539
--- /dev/null
+++ b/core/tests/Drupal/Tests/Traits/Core/AssertTokenReplacementTrait.php
@@ -0,0 +1,81 @@
+ $expected) {
+ $bubbleable_metadata = new BubbleableMetadata();
+ $output = \Drupal::token()->replace($token, $data, $options, $bubbleable_metadata);
+ $this->assertEqual($output, new HtmlEscapedText($expected), new FormattableMarkup($message, [
+ '%token' => $token,
+ '%output' => $output,
+ '%expected' => $expected,
+ ]));
+
+ $this->assertEqual($metadata_tests[$token], $bubbleable_metadata);
+ }
+ }
+
+ /**
+ * 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
+ * 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
+ */
+ protected function assertTokenReplacement(array $tests, array $data, array $options, $message) {
+ foreach ($tests as $token => $expected) {
+ $output = \Drupal::token()->replace($token, $data, $options);
+ $expected = ($expected instanceof MarkupInterface) ? $expected : new HtmlEscapedText($expected);
+ $this->assertEqual($output, $expected, new FormattableMarkup($message, [
+ '%token' => $token,
+ '%output' => $output,
+ '%expected' => $expected,
+ ]));
+ }
+ }
+
+}