diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc
index 30bd1c6b95..37d21ebb2d 100644
--- a/core/modules/node/node.tokens.inc
+++ b/core/modules/node/node.tokens.inc
@@ -130,23 +130,22 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
$translation = \Drupal::service('entity.repository')->getTranslationFromContext($node, $langcode, ['operation' => 'node_tokens']);
if ($translation->hasField('body') && ($items = $translation->get('body')) && !$items->isEmpty()) {
$item = $items[0];
- // If the summary was requested and is not empty, use it.
- if ($name == 'summary' && !empty($item->summary)) {
- $output = $item->summary_processed;
+ if ($name === 'body') {
+ $output = $item->processed;
}
- // Attempt to provide a suitable version of the 'body' field.
else {
- $output = $item->processed;
- // A summary was requested.
- if ($name == 'summary') {
- // Generate an optionally trimmed summary of the body field.
-
+ // If the summary was requested and is not empty, use it.
+ if (!empty($item->summary)) {
+ $output = $item->summary_processed;
+ }
+ // Else generate an optionally trimmed summary of the body field.
+ else {
// Get the 'trim_length' size used for the 'teaser' mode, if
// present, or use the default trim_length size.
$display_options = \Drupal::service('entity_display.repository')
->getViewDisplay('node', $node->getType(), 'teaser')
->getComponent('body');
- if (isset($display_options['settings']['trim_length'])) {
+ if ($display_options && ($display_options['type'] === 'text_trimmed'|| $display_options['type'] === 'text_summary_or_trimmed')) {
$length = $display_options['settings']['trim_length'];
}
else {
@@ -154,11 +153,10 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
$length = $settings['trim_length'];
}
- $output = text_summary($output, $item->format, $length);
+ $output = text_summary($item->value, $item->format, $length);
}
}
- // "processed" returns a \Drupal\Component\Render\MarkupInterface
- // via check_markup().
+
$replacements[$original] = $output;
}
break;
diff --git a/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php b/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php
index 61b8898345..02f942f660 100644
--- a/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php
+++ b/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php
@@ -7,6 +7,7 @@
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
+use Drupal\node\NodeInterface;
use Drupal\Tests\system\Kernel\Token\TokenReplaceKernelTestBase;
/**
@@ -17,6 +18,13 @@
*/
class NodeTokenReplaceTest extends TokenReplaceKernelTestBase {
+ /**
+ * User account used as a node author.
+ *
+ * @var \Drupal\user\Entity\User
+ */
+ protected $account;
+
/**
* Modules to enable.
*
@@ -34,10 +42,12 @@ protected function setUp(): void {
$node_type = NodeType::create(['type' => 'article', 'name' => 'Article']);
$node_type->save();
node_add_body_field($node_type);
+
+ $this->account = $this->createUser();
}
/**
- * Creates a node, then tests the tokens generated from it.
+ * Tests all node tokens.
*/
public function testNodeTokenReplacement() {
$url_options = [
@@ -45,13 +55,12 @@ public function testNodeTokenReplacement() {
'language' => $this->interfaceLanguage,
];
- // Create a user and a node.
- $account = $this->createUser();
+ // Create a node.
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create([
'type' => 'article',
'tnid' => 0,
- 'uid' => $account->id(),
+ 'uid' => $this->account->id(),
'title' => '',
'body' => [['value' => 'Regular NODE body for the test.', 'summary' => 'Fancy NODE summary.', 'format' => 'plain_text']],
]);
@@ -67,15 +76,14 @@ public function testNodeTokenReplacement() {
$tests['[node:body]'] = $node->body->processed;
$tests['[node:summary]'] = $node->body->summary_processed;
$tests['[node:langcode]'] = $node->language()->getId();
+
$tests['[node:url]'] = $node->toUrl('canonical', $url_options)->toString();
$tests['[node:edit-url]'] = $node->toUrl('edit-form', $url_options)->toString();
- $tests['[node:author]'] = $account->getAccountName();
+ $$tests['[node:author]'] = $this->account->getAccountName();
$tests['[node:author:uid]'] = $node->getOwnerId();
- $tests['[node:author:name]'] = $account->getAccountName();
- /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
- $date_formatter = $this->container->get('date.formatter');
- $tests['[node:created:since]'] = $date_formatter->formatTimeDiffSince($node->getCreatedTime(), ['langcode' => $this->interfaceLanguage->getId()]);
- $tests['[node:changed:since]'] = $date_formatter->formatTimeDiffSince($node->getChangedTime(), ['langcode' => $this->interfaceLanguage->getId()]);
+ $tests['[node:author:name]'] = $this->account->getAccountName();
+ $tests['[node:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getCreatedTime(), ['langcode' => $this->interfaceLanguage->getId()]);
+ $tests['[node:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getChangedTime(), ['langcode' => $this->interfaceLanguage->getId()]);
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($node);
@@ -107,27 +115,108 @@ public function testNodeTokenReplacement() {
$this->assertEquals($expected, $output, new FormattableMarkup('Node token %token replaced.', ['%token' => $input]));
$this->assertEquals($metadata_tests[$input], $bubbleable_metadata);
}
+ }
- // Repeat for a node without a summary.
+ /**
+ * Tests the [node:summary] token.
+ */
+ public function testNodeSummaryTokenReplacement() {
+ // Length of this text is 843 characters and it's longer than a default
+ // trim length setting (600).
+ $text = ' dolor sit amet, consectetur adipiscing elit. '
+ . 'Morbi diam dui, finibus et purus ac, elementum pretium augue. '
+ . 'Fusce lacus nisl, feugiat sit amet blandit sed, mattis eu ligula. Duis laoreet dui non felis maximus fringilla. '
+ . 'Maecenas tempor magna id urna dapibus, in elementum mauris consequat. '
+ . 'Praesent in urna non felis fringilla ullamcorper interdum eu risus. '
+ . 'Vestibulum id erat ultrices, varius est sed, dignissim dui. Vestibulum a dapibus nisl. '
+ . 'Maecenas vestibulum nibh a aliquet mollis. Donec ac justo eget justo interdum faucibus. '
+ . 'Aenean sit amet finibus turpis. Donec viverra vel eros eget varius. Praesent rutrum est diam. '
+ . 'Mauris at odio scelerisque, mattis mi et, tempor eros. '
+ . 'Vestibulum sollicitudin sem quis diam sollicitudin posuere. Nam finibus vestibulum suscipit. '
+ . 'Fusce tempor tincidunt lorem vitae mollis.';
+ // Create a node.
+ /** @var $node \Drupal\node\NodeInterface */
$node = Node::create([
'type' => 'article',
- 'uid' => $account->id(),
+ 'uid' => $this->account->id(),
'title' => '',
- 'body' => [['value' => 'A string that looks random like TR5c2I', 'format' => 'plain_text']],
+ 'body' => [['value' => $text, 'format' => 'plain_text']],
]);
$node->save();
- // Generate and test token - use full body as expected value.
- $tests = [];
- $tests['[node:summary]'] = $node->body->processed;
+ // Get teaser node view display.
+ $view_display = \Drupal::entityTypeManager()
+ ->getStorage('entity_view_display')
+ ->load('node.article.teaser');
+ if (!$view_display) {
+ $values = [
+ 'targetEntityType' => 'node',
+ 'bundle' => 'article',
+ 'mode' => 'teaser',
+ 'status' => TRUE,
+ ];
+ $view_display = \Drupal::entityTypeManager()
+ ->getStorage('entity_view_display')
+ ->create($values);
+ }
- // Test to make sure that we generated something for each token.
- $this->assertNotContains(0, array_map('strlen', $tests), 'No empty tokens generated for node without a summary.');
+ // Token [node:summary] should use trim settings of "Summary or trimmed"
+ // formatter of teaser view mode.
+ $view_display->setComponent('body', [
+ 'type' => 'text_summary_or_trimmed',
+ 'settings' => ['trim_length' => 160],
+ ]);
+ $view_display->save();
+ $expected_trimmed_to_160 = Html::escape(' dolor sit amet, consectetur adipiscing elit. '
+ . 'Morbi diam dui, finibus et purus ac, elementum pretium augue.');
+ $this->assertNodeSummaryTokenReplacement($node, $expected_trimmed_to_160);
+
+ // Token [node:summary] should use trim settings of "Trimmed"
+ // formatter of teaser view mode.
+ $view_display->setComponent('body', [
+ 'type' => 'text_trimmed',
+ 'settings' => ['trim_length' => 80],
+ ]);
+ $view_display->save();
+ $expected_trimmed_to_80 = Html::escape(' dolor sit amet, consectetur adipiscing elit.');
+ $this->assertNodeSummaryTokenReplacement($node, $expected_trimmed_to_80);
+
+ // Token [node:summary] should not pickup trim length setting of any other
+ // formatter rather than "Summary or trimmed" or "Trimmed", even if setting
+ // name matches.
+ // Fallbacks to default trim length setting (600).
+ $view_display->setComponent('body', [
+ 'type' => 'text_default',
+ 'settings' => ['trim_length' => 42],
+ ]);
+ $view_display->save();
+ $expected_trimmed_to_600 = Html::escape(' dolor sit amet, consectetur adipiscing elit. '
+ . 'Morbi diam dui, finibus et purus ac, elementum pretium augue. Fusce lacus nisl, feugiat sit amet blandit sed, mattis eu ligula. '
+ . 'Duis laoreet dui non felis maximus fringilla. Maecenas tempor magna id urna dapibus, in elementum mauris consequat. '
+ . 'Praesent in urna non felis fringilla ullamcorper interdum eu risus. '
+ . 'Vestibulum id erat ultrices, varius est sed, dignissim dui. Vestibulum a dapibus nisl. '
+ . 'Maecenas vestibulum nibh a aliquet mollis. Donec ac justo eget justo interdum faucibus. '
+ . 'Aenean sit amet finibus turpis.');
+ $this->assertNodeSummaryTokenReplacement($node, $expected_trimmed_to_600);
+
+ // Token [node:summary] should not pickup trim length setting if teaser
+ // view mode does not exist.
+ // Fallbacks to "Summary or trimmed" default trim length setting (600).
+ $view_display->delete();
+ $this->assertNodeSummaryTokenReplacement($node, $expected_trimmed_to_600);
+ }
- foreach ($tests as $input => $expected) {
- $output = $this->tokenService->replace($input, ['node' => $node], ['language' => $this->interfaceLanguage]);
- $this->assertEquals($expected, $output, new FormattableMarkup('Node token %token replaced for node without a summary.', ['%token' => $input]));
- }
+ /**
+ * Asserts that [node:summary] token works as expected.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * Node to assert against.
+ * @param string $expected
+ * Expected token replacement output.
+ */
+ protected function assertNodeSummaryTokenReplacement(NodeInterface $node, $expected) {
+ $output = $this->tokenService->replace('[node:summary]', ['node' => $node], ['langcode' => $this->interfaceLanguage->getId()]);
+ $this->assertEquals($output, $expected, new FormattableMarkup('Node token %token replaced.', ['%token' => '[node:summary]']));
}
}