From a9ce260cfa0c9f26e2541b781f1b5d5dd6e8e1da Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Tue, 1 Apr 2025 17:59:47 +0200 Subject: [PATCH 1/4] applied patch --- core/core.services.yml | 1 + .../CacheContextOptimizableInterface.php | 39 ++++++ .../Cache/Context/CacheContextsManager.php | 18 ++- .../Cache/Context/TimeZoneCacheContext.php | 40 ++++++- .../Cache/Context/UserRolesCacheContext.php | 16 ++- .../BlockContentTranslationUITest.php | 1 - .../Functional/CommentTranslationUITest.php | 1 - .../CommentDefaultFormatterCacheTagsTest.php | 2 + .../tests/src/Functional/HistoryTest.php | 3 +- .../MenuLinkContentTranslationUITest.php | 2 +- .../tests/src/Functional/MenuUiNodeTest.php | 3 +- .../NodeCacheTagsNoTimezoneTest.php | 45 +++++++ .../src/Functional/Routing/RouterTest.php | 6 +- .../Functional/System/AccessDeniedTest.php | 6 +- .../Functional/System/PageNotFoundTest.php | 6 +- .../src/Functional/Views/AccessRoleTest.php | 19 +-- .../Context/CacheContextsManagerTest.php | 113 ++++++++++++++++++ 17 files changed, 297 insertions(+), 24 deletions(-) create mode 100644 core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php create mode 100644 core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php diff --git a/core/core.services.yml b/core/core.services.yml index 55d8d2ebc984..eb6a576d257e 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -205,6 +205,7 @@ services: - { name: cache.context} cache_context.timezone: class: Drupal\Core\Cache\Context\TimeZoneCacheContext + arguments: ['@config.factory'] tags: - { name: cache.context} diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php b/core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php new file mode 100644 index 000000000000..c1eeecc25db7 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\Core\Cache\Context; + +/** + * Allows cache contexts to influence how cache contexts are optimized. + */ +interface CacheContextOptimizableInterface { + + /** + * Returns whether a cache context value has variations. + * + * For example, the timezone cache context can define itself as global + * if a site does not have configurable timezones and the language contexts + * are global on a site that is not multilingual. + * + * @return bool + * TRUE if the context has more than one possible variations. + */ + public function hasVariations(); + + /** + * Returns parent contexts for this context. + * + * By default, parents are derived based on the cache context name, user is + * a parent of user.permissions. This allows e.g. the user.roles context to + * expand on this and indicate that user.permissions is also a valid + * parent/replacement. + * + * Note that if implementing this, it is necessary to also include parents of + * the parent contexts, e.g. user.roles must provide both user.permissions and + * user. + * + * @return array + * A list of parents cache contexts. + */ + public function getParentContexts(); + +} diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php index 91d1b3ab9e99..7a7b9979882a 100644 --- a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php +++ b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php @@ -172,6 +172,22 @@ public function optimizeTokens(array $context_tokens) { [$context_id, $parameter] = explode(':', $context_token); } + // Cache contexts can explicitly provide a list of parents or indicate + // that they are global. + // @todo Also support to optimize them away if a parent of those is + // provided. + $service = $this->getService($context_id); + if ($service instanceof CacheContextOptimizableInterface) { + if (!$service->hasVariations()) { + continue; + } + + if (array_intersect($context_tokens, $service->getParentContexts())) { + // If there is at least one parent context available, skip this one. + continue; + } + } + // Context tokens without: // - a period means they don't have a parent // - a colon means they're not a specific value of a cache context @@ -181,7 +197,7 @@ public function optimizeTokens(array $context_tokens) { } // Check cacheability. If the context defines a max-age of 0, then it // can not be optimized away. Pass the parameter along if we have one. - elseif ($this->getService($context_id)->getCacheableMetadata($parameter)->getCacheMaxAge() === 0) { + elseif ($service->getCacheableMetadata($parameter)->getCacheMaxAge() === 0) { $optimized_content_tokens[] = $context_token; } // The context token has a period or a colon. Iterate over all ancestor diff --git a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php index 318817264cc7..4cddaa09f330 100644 --- a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Cache\Context; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Config\ConfigFactoryInterface; /** * Defines the TimeZoneCacheContext service, for "per time zone" caching. @@ -11,7 +12,24 @@ * * @see \Drupal\Core\Session\AccountProxy::setAccount() */ -class TimeZoneCacheContext implements CacheContextInterface { +class TimeZoneCacheContext implements CacheContextInterface, CacheContextOptimizableInterface { + + /** + * The config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * Constructor for the TimeZoneCacheContext object. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory + * The config factory. + */ + public function __construct(ConfigFactoryInterface $configFactory) { + $this->configFactory = $configFactory; + } /** * {@inheritdoc} @@ -33,7 +51,25 @@ public function getContext() { * {@inheritdoc} */ public function getCacheableMetadata() { - return new CacheableMetadata(); + $cacheability_metadata = new CacheableMetadata(); + $cacheability_metadata->addCacheableDependency($this->configFactory->get('system.date')); + return $cacheability_metadata; + } + + /** + * {@inheritdoc} + */ + public function hasVariations() { + // The timezone context can not have different values if the site does not + // use configurable timezones. + return (bool) $this->configFactory->get('system.date')->get('timezone.user.configurable'); + } + + /** + * {@inheritdoc} + */ + public function getParentContexts() { + return []; } } diff --git a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php index cc978750c4da..18080d06d55a 100644 --- a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php @@ -14,7 +14,7 @@ * Calculated cache context ID: 'user.roles:%role', e.g. 'user.roles:anonymous' * (to vary by the presence/absence of a specific role). */ -class UserRolesCacheContext extends UserCacheContextBase implements CalculatedCacheContextInterface { +class UserRolesCacheContext extends UserCacheContextBase implements CalculatedCacheContextInterface, CacheContextOptimizableInterface { /** * {@inheritdoc} @@ -41,4 +41,18 @@ public function getCacheableMetadata($role = NULL) { return (new CacheableMetadata())->setCacheTags(['user:' . $this->user->id()]); } + /** + * {@inheritdoc} + */ + public function hasVariations() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getParentContexts() { + return ['user', 'user.permissions']; + } + } diff --git a/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php b/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php index 6266b71f1cb0..99b081edc801 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php @@ -40,7 +40,6 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase { 'url.path', 'url.query_args', 'user.permissions', - 'user.roles:authenticated', ]; /** diff --git a/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php b/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php index 4d6db8c4bfd2..1cef130d13bf 100644 --- a/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php +++ b/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php @@ -49,7 +49,6 @@ class CommentTranslationUITest extends ContentTranslationUITestBase { 'url.query_args.pagers:0', 'url.site', 'user.permissions', - 'user.roles', ]; /** diff --git a/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php index 699df300f067..ef175911e7b3 100644 --- a/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php @@ -91,6 +91,7 @@ public function testCacheTags(): void { 'config:field.field.entity_test.entity_test.comment', 'config:field.storage.comment.comment_body', 'config:user.settings', + 'user:' . \Drupal::currentUser()->id(), ]; $this->assertEqualsCanonicalizing($expected_cache_tags, $build['#cache']['tags']); @@ -134,6 +135,7 @@ public function testCacheTags(): void { 'user_view', 'CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form', 'user:' . $user->id(), + 'user:' . \Drupal::currentUser()->id(), 'config:core.entity_form_display.comment.comment.default', 'config:field.field.comment.comment.comment_body', 'config:field.field.entity_test.entity_test.comment', diff --git a/core/modules/history/tests/src/Functional/HistoryTest.php b/core/modules/history/tests/src/Functional/HistoryTest.php index 25dcfb42e31c..325d7adba9dd 100644 --- a/core/modules/history/tests/src/Functional/HistoryTest.php +++ b/core/modules/history/tests/src/Functional/HistoryTest.php @@ -121,7 +121,8 @@ public function testHistory(): void { // View the node. $this->drupalGet('node/' . $nid); - $this->assertCacheContext('user.roles:authenticated'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); // JavaScript present to record the node read. $settings = $this->getDrupalSettings(); $libraries = explode(',', $settings['ajaxPageState']['libraries']); diff --git a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php index c8f12118c2d0..8e20d0329145 100644 --- a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php +++ b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php @@ -17,7 +17,7 @@ class MenuLinkContentTranslationUITest extends ContentTranslationUITestBase { /** * {@inheritdoc} */ - protected $defaultCacheContexts = ['languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user.permissions', 'user.roles:authenticated']; + protected $defaultCacheContexts = ['languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user.permissions']; /** * {@inheritdoc} diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php index 69b11f376d03..ce1226ada23a 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php @@ -81,7 +81,8 @@ public function testMenuNodeFormWidget(): void { // item" options in menu_ui_form_node_type_form_alter(). The "log out" link // adds the "user.roles:authenticated" cache context. $this->drupalGet('admin/structure/types/manage/page'); - $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.roles:authenticated'); + // @todo: Does this test still make sense like this? + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions'); // Assert the description of "Available menus" checkboxes field. $this->assertSession()->pageTextContains('Content of this type can be placed in the selected menus.'); diff --git a/core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php b/core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php new file mode 100644 index 000000000000..786bd214fab9 --- /dev/null +++ b/core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php @@ -0,0 +1,45 @@ +<?php + +namespace Drupal\Tests\node\Functional; + +use Drupal\Core\Entity\EntityInterface; + +/** + * Tests the Node entity's cache tags. + * + * @group node + */ +class NodeCacheTagsNoTimezoneTest extends NodeCacheTagsTest { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + // Disable configurable timezones, which should result in the timezone cache + // context *not* being added. + $this->config('system.date') + ->set('timezone.user.configurable', FALSE) + ->save(); + } + + /** + * {@inheritdoc} + */ + protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) { + return []; + } + + /** + * {@inheritdoc} + * + * Each node must have an author. + */ + protected function getAdditionalCacheTagsForEntity(EntityInterface $node) { + // Because timezone is optimized away, the additional system.date cache tag + // is added. + return ['user:' . $node->getOwnerId(), 'user_view', 'config:system.date']; + } + +} diff --git a/core/modules/system/tests/src/Functional/Routing/RouterTest.php b/core/modules/system/tests/src/Functional/Routing/RouterTest.php index 97fa05f2f99e..ada68743a60d 100644 --- a/core/modules/system/tests/src/Functional/Routing/RouterTest.php +++ b/core/modules/system/tests/src/Functional/Routing/RouterTest.php @@ -75,10 +75,8 @@ public function testFinishResponseSubscriber(): void { $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Max-Age', '60'); // 2. controller result: render array, per-role cacheable route access. $this->drupalGet('router_test/test19'); - $expected_cache_contexts = Cache::mergeContexts($renderer_required_cache_contexts, [ - 'url', - 'user.roles', - ]); + // @todo: Does this test still make sense like this? + $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Contexts', implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url']))); sort($expected_cache_contexts); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Contexts', implode(' ', $expected_cache_contexts)); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Tags', 'config:user.role.anonymous foo http_response rendered'); diff --git a/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php b/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php index 0163c594a068..efe9c9e11dff 100644 --- a/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php +++ b/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php @@ -145,14 +145,16 @@ public function testAccessDeniedCustomPageWithAccessDenied(): void { $this->assertSession()->pageTextContains('You are not authorized to access this page.'); $this->assertSession()->statusCodeEquals(403); // Verify the access cacheability metadata for custom 403 is bubbled. - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); $this->drupalLogin($this->adminUser); $this->drupalGet('/system-test/always-denied'); $this->assertSession()->pageTextContains('Admin-only 4xx response'); $this->assertSession()->statusCodeEquals(403); // Verify the access cacheability metadata for custom 403 is bubbled. - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); } } diff --git a/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php b/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php index 7fe74aafcc54..0b45793f3d92 100644 --- a/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php +++ b/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php @@ -91,7 +91,8 @@ public function testPageNotFoundCustomPageWithAccessDenied(): void { $this->assertSession()->pageTextContains('The requested page could not be found.'); $this->assertSession()->statusCodeEquals(404); // Verify the access cacheability metadata for custom 404 is bubbled. - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); $this->drupalLogin($this->adminUser); $this->drupalGet('/this-path-does-not-exist'); @@ -99,7 +100,8 @@ public function testPageNotFoundCustomPageWithAccessDenied(): void { $this->assertSession()->pageTextNotContains('The requested page could not be found.'); $this->assertSession()->statusCodeEquals(404); // Verify the access cacheability metadata for custom 404 is bubbled. - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); } } diff --git a/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php b/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php index 58a5b0d346b4..dad4832186b0 100644 --- a/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php +++ b/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php @@ -70,12 +70,14 @@ public function testAccessRole(): void { $this->drupalLogin($this->webUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(403); - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); $this->drupalLogin($this->normalUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(200); - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); // Test allowing multiple roles. $view = Views::getView('test_access_role')->storage; @@ -99,15 +101,18 @@ public function testAccessRole(): void { $this->drupalLogin($this->webUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(403); - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); $this->drupalLogout(); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(200); - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); $this->drupalLogin($this->normalUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(200); - $this->assertCacheContext('user.roles'); + // @todo: Does this test still make sense like this? + $this->assertCacheContext('user.permissions'); } /** @@ -133,8 +138,8 @@ public function testRenderCaching(): void { $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default'); $account_switcher->switchTo($this->normalUser); $result = $renderer->renderInIsolation($build); - $this->assertContains('user.roles', $build['#cache']['contexts']); - $this->assertEquals(['config:views.view.test_access_role'], $build['#cache']['tags']); + $this->assertContains('user.permissions', $build['#cache']['contexts']); + $this->assertEquals(['config:views.view.test_access_role', 'user:' . $this->normalUser->id()], $build['#cache']['tags']); $this->assertEquals(Cache::PERMANENT, $build['#cache']['max-age']); $this->assertNotSame('', $result); diff --git a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php index f76e8cd64743..6c3147de0af6 100644 --- a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php @@ -5,6 +5,7 @@ namespace Drupal\Tests\Core\Cache\Context; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\Context\CacheContextOptimizableInterface; use Drupal\Core\Cache\Context\CacheContextsManager; use Drupal\Core\Cache\Context\CacheContextInterface; use Drupal\Core\Cache\Context\CalculatedCacheContextInterface; @@ -57,6 +58,16 @@ public function testOptimizeTokens(array $context_tokens, array $optimized_conte Container::EXCEPTION_ON_INVALID_REFERENCE, new NoOptimizeCacheContext(), ], + [ + 'cache_context.x.non-standard', + Container::EXCEPTION_ON_INVALID_REFERENCE, + new NonStandardOptimizeCacheContext(), + ], + [ + 'cache_context.x.global', + Container::EXCEPTION_ON_INVALID_REFERENCE, + new GlobalOptimizeCacheContext(), + ], ]); $cache_contexts_manager = new CacheContextsManager($container, $this->getContextsFixture()); @@ -96,6 +107,24 @@ public static function providerTestOptimizeTokens() { [['a.b.c:foo', 'a'], ['a']], [['a.b.c:foo', 'a.b.c'], ['a.b.c']], + // Non-standard parents. + [['a.b', 'x.non-standard'], ['a.b']], + // Calculated cache contexts are supported as well but there is no + // argument granularity. + [['a.b', 'x.non-standard:argument'], ['a.b']], + [['a', 'a.b', 'x.non-standard'], ['a']], + // Should contexts with explicit parents still respect the default logic? + // Not doing that would allow to deprecate the somewhat strange max-age 0 + // check in favor of returning an empty list? + [['x', 'x.non-standard'], ['x']], + [['a.b.c', 'x.non-standard'], ['a.b.c', 'x.non-standard']], + // @todo Support this as well? + // [['a', 'non-standard'], ['a']], + + // Contexts that return TRUE in isGlobal() are always optimized away. + [['a', 'x.global'], ['a']], + [['x.global'], []], + // max-age 0 is treated as non-optimizable. [['a.b.no-optimize', 'a.b', 'a'], ['a.b.no-optimize', 'a']], ]; @@ -333,3 +362,87 @@ public function getCacheableMetadata() { } } + +/** + * Optimizable context class with a non-default parent. + */ +class NonStandardOptimizeCacheContext implements CacheContextInterface, CacheContextOptimizableInterface { + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return 'Non-Standard'; + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return 'non_standard'; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($parameter = NULL) { + return new CacheableMetadata(); + } + + /** + * {@inheritdoc} + */ + public function hasVariations() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getParentContexts() { + return ['a.b']; + } + +} + +/** + * A global cache context that can always be optimized away. + */ +class GlobalOptimizeCacheContext implements CacheContextInterface, CacheContextOptimizableInterface { + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return 'Non-Standard'; + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return 'non_standard'; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($parameter = NULL) { + return new CacheableMetadata(); + } + + /** + * {@inheritdoc} + */ + public function hasVariations() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getParentContexts() { + return []; + } + +} -- GitLab From 2510acbfeaa845d3c521053f750acd142e64616c Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Tue, 1 Apr 2025 18:02:26 +0200 Subject: [PATCH 2/4] remove roles/permissions changes --- .../Cache/Context/UserRolesCacheContext.php | 16 +--------------- .../BlockContentTranslationUITest.php | 1 + .../Functional/CommentTranslationUITest.php | 1 + .../CommentDefaultFormatterCacheTagsTest.php | 2 -- .../tests/src/Functional/HistoryTest.php | 3 +-- .../MenuLinkContentTranslationUITest.php | 2 +- .../tests/src/Functional/MenuUiNodeTest.php | 3 +-- .../src/Functional/Routing/RouterTest.php | 6 ++++-- .../Functional/System/AccessDeniedTest.php | 6 ++---- .../Functional/System/PageNotFoundTest.php | 6 ++---- .../src/Functional/Views/AccessRoleTest.php | 19 +++++++------------ 11 files changed, 21 insertions(+), 44 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php index 18080d06d55a..cc978750c4da 100644 --- a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php @@ -14,7 +14,7 @@ * Calculated cache context ID: 'user.roles:%role', e.g. 'user.roles:anonymous' * (to vary by the presence/absence of a specific role). */ -class UserRolesCacheContext extends UserCacheContextBase implements CalculatedCacheContextInterface, CacheContextOptimizableInterface { +class UserRolesCacheContext extends UserCacheContextBase implements CalculatedCacheContextInterface { /** * {@inheritdoc} @@ -41,18 +41,4 @@ public function getCacheableMetadata($role = NULL) { return (new CacheableMetadata())->setCacheTags(['user:' . $this->user->id()]); } - /** - * {@inheritdoc} - */ - public function hasVariations() { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getParentContexts() { - return ['user', 'user.permissions']; - } - } diff --git a/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php b/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php index 99b081edc801..6266b71f1cb0 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php @@ -40,6 +40,7 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase { 'url.path', 'url.query_args', 'user.permissions', + 'user.roles:authenticated', ]; /** diff --git a/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php b/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php index 1cef130d13bf..4d6db8c4bfd2 100644 --- a/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php +++ b/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php @@ -49,6 +49,7 @@ class CommentTranslationUITest extends ContentTranslationUITestBase { 'url.query_args.pagers:0', 'url.site', 'user.permissions', + 'user.roles', ]; /** diff --git a/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php index ef175911e7b3..699df300f067 100644 --- a/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/tests/src/Kernel/CommentDefaultFormatterCacheTagsTest.php @@ -91,7 +91,6 @@ public function testCacheTags(): void { 'config:field.field.entity_test.entity_test.comment', 'config:field.storage.comment.comment_body', 'config:user.settings', - 'user:' . \Drupal::currentUser()->id(), ]; $this->assertEqualsCanonicalizing($expected_cache_tags, $build['#cache']['tags']); @@ -135,7 +134,6 @@ public function testCacheTags(): void { 'user_view', 'CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form', 'user:' . $user->id(), - 'user:' . \Drupal::currentUser()->id(), 'config:core.entity_form_display.comment.comment.default', 'config:field.field.comment.comment.comment_body', 'config:field.field.entity_test.entity_test.comment', diff --git a/core/modules/history/tests/src/Functional/HistoryTest.php b/core/modules/history/tests/src/Functional/HistoryTest.php index 325d7adba9dd..25dcfb42e31c 100644 --- a/core/modules/history/tests/src/Functional/HistoryTest.php +++ b/core/modules/history/tests/src/Functional/HistoryTest.php @@ -121,8 +121,7 @@ public function testHistory(): void { // View the node. $this->drupalGet('node/' . $nid); - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles:authenticated'); // JavaScript present to record the node read. $settings = $this->getDrupalSettings(); $libraries = explode(',', $settings['ajaxPageState']['libraries']); diff --git a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php index 8e20d0329145..c8f12118c2d0 100644 --- a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php +++ b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php @@ -17,7 +17,7 @@ class MenuLinkContentTranslationUITest extends ContentTranslationUITestBase { /** * {@inheritdoc} */ - protected $defaultCacheContexts = ['languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user.permissions']; + protected $defaultCacheContexts = ['languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user.permissions', 'user.roles:authenticated']; /** * {@inheritdoc} diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php index ce1226ada23a..69b11f376d03 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php @@ -81,8 +81,7 @@ public function testMenuNodeFormWidget(): void { // item" options in menu_ui_form_node_type_form_alter(). The "log out" link // adds the "user.roles:authenticated" cache context. $this->drupalGet('admin/structure/types/manage/page'); - // @todo: Does this test still make sense like this? - $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions'); + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.roles:authenticated'); // Assert the description of "Available menus" checkboxes field. $this->assertSession()->pageTextContains('Content of this type can be placed in the selected menus.'); diff --git a/core/modules/system/tests/src/Functional/Routing/RouterTest.php b/core/modules/system/tests/src/Functional/Routing/RouterTest.php index ada68743a60d..97fa05f2f99e 100644 --- a/core/modules/system/tests/src/Functional/Routing/RouterTest.php +++ b/core/modules/system/tests/src/Functional/Routing/RouterTest.php @@ -75,8 +75,10 @@ public function testFinishResponseSubscriber(): void { $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Max-Age', '60'); // 2. controller result: render array, per-role cacheable route access. $this->drupalGet('router_test/test19'); - // @todo: Does this test still make sense like this? - $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Contexts', implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url']))); + $expected_cache_contexts = Cache::mergeContexts($renderer_required_cache_contexts, [ + 'url', + 'user.roles', + ]); sort($expected_cache_contexts); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Contexts', implode(' ', $expected_cache_contexts)); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Tags', 'config:user.role.anonymous foo http_response rendered'); diff --git a/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php b/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php index efe9c9e11dff..0163c594a068 100644 --- a/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php +++ b/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php @@ -145,16 +145,14 @@ public function testAccessDeniedCustomPageWithAccessDenied(): void { $this->assertSession()->pageTextContains('You are not authorized to access this page.'); $this->assertSession()->statusCodeEquals(403); // Verify the access cacheability metadata for custom 403 is bubbled. - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); $this->drupalLogin($this->adminUser); $this->drupalGet('/system-test/always-denied'); $this->assertSession()->pageTextContains('Admin-only 4xx response'); $this->assertSession()->statusCodeEquals(403); // Verify the access cacheability metadata for custom 403 is bubbled. - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); } } diff --git a/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php b/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php index 0b45793f3d92..7fe74aafcc54 100644 --- a/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php +++ b/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php @@ -91,8 +91,7 @@ public function testPageNotFoundCustomPageWithAccessDenied(): void { $this->assertSession()->pageTextContains('The requested page could not be found.'); $this->assertSession()->statusCodeEquals(404); // Verify the access cacheability metadata for custom 404 is bubbled. - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); $this->drupalLogin($this->adminUser); $this->drupalGet('/this-path-does-not-exist'); @@ -100,8 +99,7 @@ public function testPageNotFoundCustomPageWithAccessDenied(): void { $this->assertSession()->pageTextNotContains('The requested page could not be found.'); $this->assertSession()->statusCodeEquals(404); // Verify the access cacheability metadata for custom 404 is bubbled. - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); } } diff --git a/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php b/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php index dad4832186b0..58a5b0d346b4 100644 --- a/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php +++ b/core/modules/user/tests/src/Functional/Views/AccessRoleTest.php @@ -70,14 +70,12 @@ public function testAccessRole(): void { $this->drupalLogin($this->webUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(403); - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); $this->drupalLogin($this->normalUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(200); - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); // Test allowing multiple roles. $view = Views::getView('test_access_role')->storage; @@ -101,18 +99,15 @@ public function testAccessRole(): void { $this->drupalLogin($this->webUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(403); - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); $this->drupalLogout(); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(200); - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); $this->drupalLogin($this->normalUser); $this->drupalGet('test-role'); $this->assertSession()->statusCodeEquals(200); - // @todo: Does this test still make sense like this? - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user.roles'); } /** @@ -138,8 +133,8 @@ public function testRenderCaching(): void { $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default'); $account_switcher->switchTo($this->normalUser); $result = $renderer->renderInIsolation($build); - $this->assertContains('user.permissions', $build['#cache']['contexts']); - $this->assertEquals(['config:views.view.test_access_role', 'user:' . $this->normalUser->id()], $build['#cache']['tags']); + $this->assertContains('user.roles', $build['#cache']['contexts']); + $this->assertEquals(['config:views.view.test_access_role'], $build['#cache']['tags']); $this->assertEquals(Cache::PERMANENT, $build['#cache']['max-age']); $this->assertNotSame('', $result); -- GitLab From a0051eff5d87eb73543674f42dc859ef0e5406f0 Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Tue, 1 Apr 2025 21:28:01 +0200 Subject: [PATCH 3/4] phpcs --- .../tests/src/Functional/NodeCacheTagsNoTimezoneTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php b/core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php index 786bd214fab9..c92a32c0fddc 100644 --- a/core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php +++ b/core/modules/node/tests/src/Functional/NodeCacheTagsNoTimezoneTest.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\Tests\node\Functional; use Drupal\Core\Entity\EntityInterface; @@ -27,7 +29,7 @@ protected function setUp(): void { /** * {@inheritdoc} */ - protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) { + protected function getAdditionalCacheContextsForEntity(EntityInterface $entity): array { return []; } @@ -36,7 +38,7 @@ protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) * * Each node must have an author. */ - protected function getAdditionalCacheTagsForEntity(EntityInterface $node) { + protected function getAdditionalCacheTagsForEntity(EntityInterface $node): array { // Because timezone is optimized away, the additional system.date cache tag // is added. return ['user:' . $node->getOwnerId(), 'user_view', 'config:system.date']; -- GitLab From ef908a365099c5335ae247cf9b9a22dfead3ef1b Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Tue, 8 Apr 2025 21:47:49 +0200 Subject: [PATCH 4/4] return type --- .../Cache/Context/CacheContextOptimizableInterface.php | 4 ++-- .../Drupal/Core/Cache/Context/TimeZoneCacheContext.php | 4 ++-- .../Tests/Core/Cache/Context/CacheContextsManagerTest.php | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php b/core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php index c1eeecc25db7..f7abf0944448 100644 --- a/core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php +++ b/core/lib/Drupal/Core/Cache/Context/CacheContextOptimizableInterface.php @@ -17,7 +17,7 @@ interface CacheContextOptimizableInterface { * @return bool * TRUE if the context has more than one possible variations. */ - public function hasVariations(); + public function hasVariations(): bool; /** * Returns parent contexts for this context. @@ -34,6 +34,6 @@ public function hasVariations(); * @return array * A list of parents cache contexts. */ - public function getParentContexts(); + public function getParentContexts(): array; } diff --git a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php index 4cddaa09f330..b24fc09e3260 100644 --- a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php @@ -59,7 +59,7 @@ public function getCacheableMetadata() { /** * {@inheritdoc} */ - public function hasVariations() { + public function hasVariations(): bool { // The timezone context can not have different values if the site does not // use configurable timezones. return (bool) $this->configFactory->get('system.date')->get('timezone.user.configurable'); @@ -68,7 +68,7 @@ public function hasVariations() { /** * {@inheritdoc} */ - public function getParentContexts() { + public function getParentContexts(): array { return []; } diff --git a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php index 6c3147de0af6..178cde88b7c5 100644 --- a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php @@ -392,14 +392,14 @@ public function getCacheableMetadata($parameter = NULL) { /** * {@inheritdoc} */ - public function hasVariations() { + public function hasVariations(): bool { return TRUE; } /** * {@inheritdoc} */ - public function getParentContexts() { + public function getParentContexts(): array { return ['a.b']; } @@ -434,14 +434,14 @@ public function getCacheableMetadata($parameter = NULL) { /** * {@inheritdoc} */ - public function hasVariations() { + public function hasVariations(): bool { return FALSE; } /** * {@inheritdoc} */ - public function getParentContexts() { + public function getParentContexts(): array { return []; } -- GitLab