diff --git a/tests/src/Functional/TokenCurrentPageTest.php b/tests/src/Functional/TokenCurrentPageTest.php index f161386..fe0f6fe 100644 --- a/tests/src/Functional/TokenCurrentPageTest.php +++ b/tests/src/Functional/TokenCurrentPageTest.php @@ -2,7 +2,9 @@ namespace Drupal\Tests\token\Functional; +use Drupal\block\Entity\Block; use Drupal\Core\Url; +use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait; /** * Test the [current-page:*] tokens. @@ -11,12 +13,14 @@ use Drupal\Core\Url; */ class TokenCurrentPageTest extends TokenTestBase { + use TaxonomyTestTrait; + /** * Modules to enable. * * @var array */ - public static $modules = ['node']; + public static $modules = ['node', 'taxonomy', 'block']; function testCurrentPageTokens() { // Cache clear is necessary because the frontpage was already cached by an @@ -70,4 +74,97 @@ class TokenCurrentPageTest extends TokenTestBase { ]; $this->assertPageTokens("/node/{$node->id()}", $tokens, [], ['url_options' => ['query' => ['foo' => 'bar']]]); } + + /* + * Test tokens like [current-page:node:nid]. + */ + public function testCurrentPageObjectTokens() { + // We are especially interested in testing caching. + // Imitate strategy of UrlTest::testBlockUrlTokenReplacement(). + $this->drupalCreateContentType(['type' => 'page']); + // Put the first node in a variable for later manipulation. + $node1 = $this->drupalCreateNode(['title' => 'Node the First']); + $this->drupalCreateNode(['title' => 'Node the Second']); + $vocab = $this->createVocabulary(); + $this->createTerm($vocab, ['name' => 'Term the First']); + $this->createTerm($vocab, ['name' => 'Term the Second']); + + // 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'); + + $tests = []; + // Chained node token. + $tests[] = [ + 'token' => 'prefix_[current-page:node:title]_suffix', + 'node1' => 'prefix_Node the First_suffix', + 'node2' => 'prefix_Node the Second_suffix', + 'term1' => 'prefix_[current-page:node:title]_suffix', + 'term2' => 'prefix_[current-page:node:title]_suffix', + 'node1_new_title' => 'New Title', + 'node1_new_expected' => 'prefix_New Title_suffix', + ]; + // Chained taxonomy_term token. + $tests[] = [ + 'token' => 'prefix_[current-page:taxonomy_term:tid]_suffix', + 'node1' => 'prefix_[current-page:taxonomy_term:tid]_suffix', + 'node2' => 'prefix_[current-page:taxonomy_term:tid]_suffix', + 'term1' => 'prefix_1_suffix', + 'term2' => 'prefix_2_suffix', + ]; + // Show that 'term' token does not work. + // The current-page:object token does not use the token mapper service. + $tests[] = [ + 'token' => 'prefix_[current-page:term:tid]_suffix', + 'node1' => 'prefix_[current-page:term:tid]_suffix', + 'node2' => 'prefix_[current-page:term:tid]_suffix', + 'term1' => 'prefix_[current-page:term:tid]_suffix', + 'term2' => 'prefix_[current-page:term:tid]_suffix', + ]; + // Unchained node token. + $tests[] = [ + 'token' => 'prefix_[current-page:node]_suffix', + 'node1' => 'prefix_Node the First_suffix', + 'node2' => 'prefix_Node the Second_suffix', + 'term1' => 'prefix_[current-page:node]_suffix', + 'term2' => 'prefix_[current-page:node]_suffix', + 'node1_new_title' => 'Updated Title', + 'node1_new_expected' => 'prefix_Updated Title_suffix', + ]; + + $assert_session = $this->assertSession(); + foreach ($tests as $test) { + // Set the block label. + $block->getPlugin()->setConfigurationValue('label', $test['token']); + $block->save(); + + // Then visit each entity, testing cache context. + $this->drupalGet('node/1'); + $assert_session->elementContains('css', '#block-token-url-test-block', $test['node1']); + + $this->drupalGet('node/2'); + $assert_session->elementContains('css', '#block-token-url-test-block', $test['node2']); + + $this->drupalGet('taxonomy/term/1'); + $assert_session->elementContains('css', '#block-token-url-test-block', $test['term1']); + + $this->drupalGet('taxonomy/term/2'); + $assert_session->elementContains('css', '#block-token-url-test-block', $test['term2']); + + if (isset($test['node1_new_title'])) { + // Update node1 and revisit, testing cache tags. + $node1->set('title', $test['node1_new_title'])->save(); + $this->drupalGet('node/1'); + $assert_session->elementContains('css', '#block-token-url-test-block', $test['node1_new_expected']); + // Change to to original title. + $node1->set('title', 'Node the First')->save(); + } + } + } + } diff --git a/token.tokens.inc b/token.tokens.inc index 563f5e7..02e4774 100755 --- a/token.tokens.inc +++ b/token.tokens.inc @@ -302,7 +302,7 @@ function token_token_info() { } $info['tokens']['current-page'][$entity_type_id] = [ - 'name' => t('The current %type', ['%type' => $entity_type_id]), + 'name' => t('The current %type', ['%type' => $entity_type->getLabel()]), 'description' => t("The current page object if that's a %type", ['%type' => $entity_type_id]), 'type' => \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id), ]; @@ -807,45 +807,42 @@ function token_tokens($type, array $tokens, array $data, array $options, Bubblea } $replacements[$original] = (int) $page + 1; break; + default: - /* - * This is the case that can handle the current-page:object token. - * We need to start by adding the url.path cache context. We need - * to do this regardless of whether or not the object actually exists. - * Otherwise, we could imagine a block that is first rendered on a - * custom route for which there is no current-page:object. If we then - * viewed a node that has this same block, we could see a cached version - * of the block instead of seeing a new rendering of the block that has the - * current-page:object token replaced with the new current page's node. - */ - $bubbleable_metadata->addCacheContexts(['url.path']); + // This is the case that handles the current-page:object token. + // Things like [current-page:node:field_foo]. + + // Parse token to determine entity type. $entity_type_manager = \Drupal::entityTypeManager(); $parts = explode(':', $name); $entity_type = $parts[0]; - $entities = $entity_type_manager->getDefinitions(); - - if (!isset($entities[$entity_type])) { + if (!$entity_type_manager->hasDefinition($entity_type)) { break; } - /** @var \Drupal\Core\Entity\EntityInterface $entity */ - $entity = $request->attributes->get($entity_type); + // Entity type is valid. Add url.path cache context. We need to + // do this regardless of whether or not the entity in question + // exists for the current page. + $bubbleable_metadata->addCacheContexts(['url.path']); - // Load the entity object if only entity ID was retrieved. - if (is_numeric($entity)) { - $entity = $entity_type_manager->getStorage($entity_type)->load($entity); + // Load entity if it exists. + $entity_id = \Drupal::routeMatch()->getRawParameter($entity_type); + if ($entity_id) { + $entity = $entity_type_manager->getStorage($entity_type)->load($entity_id); } - - if (!is_object($entity)) { + if (!isset($entity)) { break; } // No child properties, so load the entity label. + // For example [current-page:node]. if ($name == $entity_type) { $label = $entity->label(); $replacements[$original] = $label; + $bubbleable_metadata->addCacheableDependency($entity); } // Load child properties via recursive tokens. + // For example [current-page:node:nid]. else { $entity_tokens = \Drupal::token()->findWithPrefix($tokens, $entity_type); $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type);