diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php index 499deeb785dc86116a75fcc772ed2f19253d2897..1a2df9f50d7491c19f78e83dbaea5316e87bf161 100644 --- a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php +++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php @@ -101,6 +101,32 @@ public function view(EntityInterface $_entity, $view_mode = 'full') { $page['#entity_type'] = $_entity->getEntityTypeId(); $page['#' . $page['#entity_type']] = $_entity; + // Add canonical and shortlink links if the entity has a canonical + // link template and is not new. + if ($_entity->hasLinkTemplate('canonical') && !$_entity->isNew()) { + + $url = $_entity->toUrl('canonical')->setAbsolute(TRUE); + $page['#attached']['html_head_link'][] = [ + [ + 'rel' => 'canonical', + 'href' => $url->toString(), + ], + TRUE, + ]; + + // Set the non-aliased canonical path as a default shortlink. + $page['#attached']['html_head_link'][] = [ + [ + 'rel' => 'shortlink', + 'href' => $url->setOption('alias', TRUE)->toString(), + ], + TRUE, + ]; + + // Since this generates absolute URLs, it can only be cached "per site". + $page['#cache']['contexts'][] = 'url.site'; + } + return $page; } diff --git a/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php b/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php index e5d308d682d062576971abc14b2d3400a6a0fa47..c48f32b4e0476bc48fc74b55d3be34d4a9a1186e 100644 --- a/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php +++ b/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php @@ -7,6 +7,7 @@ use Drupal\comment\Entity\Comment; use Drupal\comment\Tests\CommentTestTrait; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\Tests\system\Functional\Entity\EntityWithUriCacheTagsTestBase; @@ -158,4 +159,15 @@ protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) { ]; } + /** + * {@inheritdoc} + */ + protected function getDefaultCacheContexts() { + return [ + 'languages:' . LanguageInterface::TYPE_INTERFACE, + 'theme', + 'user.permissions', + ]; + } + } diff --git a/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php b/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php index 12b993c181d5b504af39158fc620c6dd710b0cba..3f644b9a8e5e8122f8c8f3c6cc7572165a41917a 100644 --- a/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php +++ b/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php @@ -14,6 +14,17 @@ class ContentTestTranslationUITest extends ContentTranslationUITestBase { */ protected $testHTMLEscapeForAllLanguages = TRUE; + /** + * {@inheritdoc} + */ + protected $defaultCacheContexts = [ + 'languages:language_interface', + 'theme', + 'url.query_args:_wrapper_format', + 'user.permissions', + 'url.site', + ]; + /** * Modules to enable. * diff --git a/core/modules/node/src/Controller/NodeViewController.php b/core/modules/node/src/Controller/NodeViewController.php index a0787d4abccce3c16a581bffa579119cd271c976..406d17b5377cd9a3f740208f3d1ced5b90f0719b 100644 --- a/core/modules/node/src/Controller/NodeViewController.php +++ b/core/modules/node/src/Controller/NodeViewController.php @@ -63,51 +63,7 @@ public static function create(ContainerInterface $container) { * {@inheritdoc} */ public function view(EntityInterface $node, $view_mode = 'full', $langcode = NULL) { - $build = parent::view($node, $view_mode, $langcode); - - foreach ($node->uriRelationships() as $rel) { - $url = $node->toUrl($rel)->setAbsolute(TRUE); - // Add link relationships if the user is authenticated or if the anonymous - // user has access. Access checking must be done for anonymous users to - // avoid traffic to inaccessible pages from web crawlers. For - // authenticated users, showing the links in HTML head does not impact - // user experience or security, since the routes are access checked when - // visited and only visible via view source. This prevents doing - // potentially expensive and hard to cache access checks on every request. - // This means that the page will vary by user.permissions. We also rely on - // the access checking fallback to ensure the correct cacheability - // metadata if we have to check access. - if ($this->currentUser->isAuthenticated() || $url->access($this->currentUser)) { - // Set the node path as the canonical URL to prevent duplicate content. - $build['#attached']['html_head_link'][] = [ - [ - 'rel' => $rel, - 'href' => $url->toString(), - ], - TRUE, - ]; - } - - if ($rel == 'canonical') { - // Set the non-aliased canonical path as a default shortlink. - $build['#attached']['html_head_link'][] = [ - [ - 'rel' => 'shortlink', - 'href' => $url->setOption('alias', TRUE)->toString(), - ], - TRUE, - ]; - } - } - - // Since this generates absolute URLs, it can only be cached "per site". - $build['#cache']['contexts'][] = 'url.site'; - - // Given this varies by $this->currentUser->isAuthenticated(), add a cache - // context based on the anonymous role. - $build['#cache']['contexts'][] = 'user.roles:anonymous'; - - return $build; + return parent::view($node, $view_mode); } /** diff --git a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php index 4b40ae36d3e93e3b302272a78206157f3f5371a6..36f38aaa883915eb844f1d74d06fce6d93634ced 100644 --- a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php +++ b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php @@ -138,7 +138,7 @@ public function testRecentNodeBlock() { $this->assertSession()->pageTextContains($node3->label()); $this->assertSession()->pageTextContains($node4->label()); - $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user']); + $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.site', 'user']); // Enable the "Powered by Drupal" block only on article nodes. $edit = [ @@ -165,7 +165,7 @@ public function testRecentNodeBlock() { $label = $block->label(); // Check that block is not displayed on the front page. $this->assertNoText($label); - $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route']); + $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.site', 'user', 'route']); // Ensure that a page that does not have a node context can still be cached, // the front page is the user page which is already cached from the login diff --git a/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php b/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php index 191955a83e06aa3dcaaab874e6a01302e3bcc65a..cadf25444871d7b3c9e0926d9c83471a2124dcca 100644 --- a/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php +++ b/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php @@ -43,17 +43,6 @@ protected function createEntity() { return $node; } - /** - * {@inheritdoc} - */ - protected function getDefaultCacheContexts() { - $defaults = parent::getDefaultCacheContexts(); - // @see \Drupal\node\Controller\NodeViewController::view() - $defaults[] = 'url.site'; - $defaults[] = 'user.roles:anonymous'; - return $defaults; - } - /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/NodeViewTest.php b/core/modules/node/tests/src/Functional/NodeViewTest.php index 5e64db52cd42e41a9072c064bf0a69b7e40eee47..c1cd40f33c34e6493f108c2f242938f7c2ddfa0a 100644 --- a/core/modules/node/tests/src/Functional/NodeViewTest.php +++ b/core/modules/node/tests/src/Functional/NodeViewTest.php @@ -25,50 +25,11 @@ public function testHtmlHeadLinks() { $this->drupalGet($node->toUrl()); - $result = $this->xpath('//link[@rel = "canonical"]'); - $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $result[0]->getAttribute('href')); + $element = $this->assertSession()->elementExists('css', 'link[rel="canonical"]'); + $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $element->getAttribute('href')); - // Link relations are checked for access for anonymous users. - $result = $this->xpath('//link[@rel = "version-history"]'); - $this->assertEmpty($result, 'Version history not present for anonymous users without access.'); - - $result = $this->xpath('//link[@rel = "edit-form"]'); - $this->assertEmpty($result, 'Edit form not present for anonymous users without access.'); - - $this->drupalLogin($this->createUser(['access content'])); - $this->drupalGet($node->toUrl()); - - $result = $this->xpath('//link[@rel = "canonical"]'); - $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $result[0]->getAttribute('href')); - - // Link relations are present regardless of access for authenticated users. - $result = $this->xpath('//link[@rel = "version-history"]'); - $this->assertEquals($node->toUrl('version-history')->setAbsolute()->toString(), $result[0]->getAttribute('href')); - - $result = $this->xpath('//link[@rel = "edit-form"]'); - $this->assertEquals($node->toUrl('edit-form')->setAbsolute()->toString(), $result[0]->getAttribute('href')); - - // Give anonymous users access to edit the node. Do this through the UI to - // ensure caches are handled properly. - $this->drupalLogin($this->rootUser); - $edit = [ - 'anonymous[edit own ' . $node->bundle() . ' content]' => TRUE, - ]; - $this->drupalGet('admin/people/permissions'); - $this->submitForm($edit, 'Save permissions'); - $this->drupalLogout(); - - // Anonymous user's should now see the edit-form link but not the - // version-history link. - $this->drupalGet($node->toUrl()); - $result = $this->xpath('//link[@rel = "canonical"]'); - $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $result[0]->getAttribute('href')); - - $result = $this->xpath('//link[@rel = "version-history"]'); - $this->assertEmpty($result, 'Version history not present for anonymous users without access.'); - - $result = $this->xpath('//link[@rel = "edit-form"]'); - $this->assertEquals($node->toUrl('edit-form')->setAbsolute()->toString(), $result[0]->getAttribute('href')); + $element = $this->assertSession()->elementExists('css', 'link[rel="shortlink"]'); + $this->assertEquals($node->toUrl('canonical', ['alias' => TRUE])->setAbsolute()->toString(), $element->getAttribute('href')); } /** @@ -79,8 +40,7 @@ public function testLinkHeader() { $expected = [ '<' . Html::escape($node->toUrl('canonical')->setAbsolute()->toString()) . '>; rel="canonical"', - '<' . Html::escape($node->toUrl('canonical')->setAbsolute()->toString(), ['alias' => TRUE]) . '>; rel="shortlink"', - '<' . Html::escape($node->toUrl('revision')->setAbsolute()->toString()) . '>; rel="revision"', + '<' . Html::escape($node->toUrl('canonical', ['alias' => TRUE])->setAbsolute()->toString()) . '>; rel="shortlink"', ]; $this->drupalGet($node->toUrl()); diff --git a/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php b/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php index 3812010bee8860b7d99383976d7f9e8a7d9f1a6a..baad8c5309d907d726422d0611870729ec2d7d16 100644 --- a/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php +++ b/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php @@ -80,7 +80,6 @@ public function testPageCacheTags() { // These two cache contexts are added by BigPipe. 'cookies:big_pipe_nojs', 'session.exists', - 'user.roles:anonymous', 'user.roles:authenticated', ]; diff --git a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php index 53402fa890ec9868782a93179ce8a4c6d95f9e4f..7bdaa303b73b47c58ed44cb06ea357ad38fa439c 100644 --- a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php +++ b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php @@ -59,7 +59,7 @@ public function testPopularContentBlock() { $tags = Cache::mergeTags($tags, ['block_view', 'config:block_list', 'node_list', 'rendered', 'user_view']); $this->assertCacheTags($tags); $contexts = Cache::mergeContexts($node->getCacheContexts(), $block->getCacheContexts()); - $contexts = Cache::mergeContexts($contexts, ['url.query_args:_wrapper_format']); + $contexts = Cache::mergeContexts($contexts, ['url.query_args:_wrapper_format', 'url.site']); $this->assertCacheContexts($contexts); // Check if the node link is displayed. diff --git a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php index a9c2e419f4710c9f2d376fccbf07c816aa8c3d2a..af96555a7a175aff4b6f56a2a11ae39e2d429695 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php @@ -339,7 +339,7 @@ public function testReferencedEntity() { // The default cache contexts for rendered entities. $default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions']; - $entity_cache_contexts = $default_cache_contexts; + $entity_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.site']); $page_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]); // Cache tags present on every rendered page. diff --git a/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php index 83c3bf3faa17761786ec4ed0a426ac4d626a4274..7b497a491ea572047e01d988956873187147ef92 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php @@ -134,7 +134,9 @@ public function testEntityUri() { * The default cache contexts for rendered entities. */ protected function getDefaultCacheContexts() { - return ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions']; + // For url.site, see + // \Drupal\Core\Entity\Controller\EntityViewController::view(). + return ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions', 'url.site']; } } diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 501874d5bd38fb16e714884d0827ae11f16f048a..067b41b4241a189aa76bc34184e2fb1d715d840a 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -12,7 +12,6 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Drupal\taxonomy\Entity\Term; -use Drupal\taxonomy\TermInterface; /** * Implements hook_help(). @@ -57,37 +56,6 @@ function taxonomy_term_uri($term) { ]); } -/** - * Implements hook_page_attachments_alter(). - */ -function taxonomy_page_attachments_alter(array &$page) { - $route_match = \Drupal::routeMatch(); - if ($route_match->getRouteName() == 'entity.taxonomy_term.canonical' && ($term = $route_match->getParameter('taxonomy_term')) && $term instanceof TermInterface) { - foreach ($term->uriRelationships() as $rel) { - // Set the URI relationships, like canonical. - $page['#attached']['html_head_link'][] = [ - [ - 'rel' => $rel, - 'href' => $term->toUrl($rel)->toString(), - ], - TRUE, - ]; - - // Set the term path as the canonical URL to prevent duplicate content. - if ($rel == 'canonical') { - // Set the non-aliased canonical path as a default shortlink. - $page['#attached']['html_head_link'][] = [ - [ - 'rel' => 'shortlink', - 'href' => $term->toUrl($rel, ['alias' => TRUE])->toString(), - ], - TRUE, - ]; - } - } - } -} - /** * Implements hook_theme(). */ diff --git a/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php b/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php index 37c885fb2f439c66f792debae8f79ae4f25db14c..badf7be914119071bd580881c3f72befeb849cbf 100644 --- a/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php +++ b/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php @@ -20,6 +20,17 @@ class TermTranslationUITest extends ContentTranslationUITestBase { */ protected $vocabulary; + /** + * {@inheritdoc} + */ + protected $defaultCacheContexts = [ + 'languages:language_interface', + 'theme', + 'url.query_args:_wrapper_format', + 'user.permissions', + 'url.site', + ]; + /** * Modules to enable. * diff --git a/core/modules/user/tests/src/Functional/UserTranslationUITest.php b/core/modules/user/tests/src/Functional/UserTranslationUITest.php index 2c29e95c5553981c0df5b1046ad10bad9348f609..8e04b858ccde2fc85993049ebd5177cb9ed1dce5 100644 --- a/core/modules/user/tests/src/Functional/UserTranslationUITest.php +++ b/core/modules/user/tests/src/Functional/UserTranslationUITest.php @@ -18,6 +18,17 @@ class UserTranslationUITest extends ContentTranslationUITestBase { */ protected $name; + /** + * {@inheritdoc} + */ + protected $defaultCacheContexts = [ + 'languages:language_interface', + 'theme', + 'url.query_args:_wrapper_format', + 'user.permissions', + 'url.site', + ]; + /** * Modules to enable. *