Commit 99aa535d authored by catch's avatar catch

Issue #2493033 by Berdir, Wim Leers, lauriii, Fabianx, effulgentsia, dawehner,...

Issue #2493033 by Berdir, Wim Leers, lauriii, Fabianx, effulgentsia, dawehner, catch, msonnabaum, Crell, webchick: Make 'user.permissions' a required cache context
parent cbcbfc15
......@@ -2,7 +2,7 @@ parameters:
session.storage.options: {}
twig.config: {}
renderer.config:
required_cache_contexts: ['languages:language_interface', 'theme']
required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']
factory.keyvalue:
default: keyvalue.database
factory.keyvalue.expirable:
......
......@@ -119,9 +119,11 @@ function testCachePerRole() {
}
/**
* Test a cacheable block without any cache context.
* Test a cacheable block without any additional cache context.
*/
function testCacheGlobal() {
function testCachePermissions() {
// user.permissions is a required context, so a user with different
// permissions will see a different version of the block.
\Drupal::state()->set('block_test.cache_contexts', []);
$current_content = $this->randomMachineName();
......@@ -134,9 +136,12 @@ function testCacheGlobal() {
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalLogout();
$this->drupalGet('user');
$this->assertText($old_content, 'Block content served from cache.');
$this->drupalLogout();
$this->drupalGet('user');
$this->assertText($current_content, 'Block content not served from cache.');
}
/**
......
......@@ -334,11 +334,13 @@ public function testBlockCacheTags() {
'config:block_list',
'block_view',
'config:block.block.powered',
'config:user.role.anonymous',
'rendered',
);
sort($expected_cache_tags);
$keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:classy');
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:' . implode(':', $keys));
$expected_cache_tags = array(
'block_view',
'config:block.block.powered',
......@@ -373,6 +375,7 @@ public function testBlockCacheTags() {
'block_view',
'config:block.block.powered',
'config:block.block.powered-2',
'config:user.role.anonymous',
'rendered',
);
sort($expected_cache_tags);
......@@ -383,7 +386,8 @@ public function testBlockCacheTags() {
'rendered',
);
sort($expected_cache_tags);
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:classy');
$keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:' . implode(':', $keys));
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$expected_cache_tags = array(
'block_view',
......@@ -391,7 +395,8 @@ public function testBlockCacheTags() {
'rendered',
);
sort($expected_cache_tags);
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered-2:en:classy');
$keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered-2:' . implode(':', $keys));
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
// Now we should have a cache hit again.
......
......@@ -9,7 +9,7 @@
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\Context\UrlCacheContext;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
......@@ -27,7 +27,7 @@ class BlockViewBuilderTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('block', 'block_test', 'system');
public static $modules = array('block', 'block_test', 'system', 'user');
/**
* The block being tested.
......@@ -160,7 +160,7 @@ protected function verifyRenderCacheHandling() {
// Test that a cache entry is created.
$build = $this->getBlockRenderArray();
$cid = 'entity_view:block:test_block:en:core';
$cid = 'entity_view:block:test_block:' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$this->renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
......@@ -214,7 +214,7 @@ public function testBlockViewBuilderAlter() {
// Advanced: cached block, but an alter hook adds an additional cache key.
$alter_add_key = $this->randomMachineName();
\Drupal::state()->set('block_test_view_alter_cache_key', $alter_add_key);
$cid = 'entity_view:block:test_block:' . $alter_add_key . ':en:core';
$cid = 'entity_view:block:test_block:' . $alter_add_key . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$expected_keys = array_merge($default_keys, array($alter_add_key));
$build = $this->getBlockRenderArray();
$this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
......
......@@ -90,7 +90,7 @@ public function testBlock() {
// Expected keys, contexts, and tags for the block.
// @see \Drupal\block\BlockViewBuilder::viewMultiple()
$expected_block_cache_keys = ['entity_view', 'block', $block->id()];
$expected_block_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'];
$expected_block_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$expected_block_cache_tags = Cache::mergeTags(['block_view', 'rendered'], $block->getCacheTags(), $block->getPlugin()->getCacheTags());
// Expected contexts and tags for the BlockContent entity.
......
......@@ -86,11 +86,7 @@ protected function createEntity() {
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return [
// Field access for the user picture rendered as part of the node that
// this comment is created on.
'user.permissions',
];
return [];
}
/**
......
......@@ -34,7 +34,7 @@ class ViewsIntegrationTest extends ViewUnitTestBase {
*
* @var array
*/
public static $modules = array('dblog', 'dblog_test_views');
public static $modules = array('dblog', 'dblog_test_views', 'user');
/**
* {@inheritdoc}
......
......@@ -69,7 +69,7 @@ public function testOutboundPathAndRouteProcessing() {
$default_menu_cacheability = (new CacheableMetadata())
->setCacheMaxAge(Cache::PERMANENT)
->setCacheTags(['config:system.menu.tools'])
->setCacheContexts(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme']);
->setCacheContexts(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions']);
User::create(['uid' => 1, 'name' => $this->randomString()])->save();
User::create(['uid' => 2, 'name' => $this->randomString()])->save();
......
......@@ -110,7 +110,7 @@ public function testMenuBlock() {
$this->verifyPageCache($url, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($url, 'HIT', ['config:block_list', 'rendered']);
$this->verifyPageCache($url, 'HIT', ['config:block_list', 'config:user.role.anonymous', 'rendered']);
}
}
......@@ -39,7 +39,7 @@ public function testCacheContexts() {
$build = $list_builder->render();
$this->container->get('renderer')->renderRoot($build);
$this->assertEqual(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'url.query_args.pagers:0', 'user.node_grants:view'], $build['#cache']['contexts']);
$this->assertEqual(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'url.query_args.pagers:0', 'user.node_grants:view', 'user.permissions'], $build['#cache']['contexts']);
}
}
......@@ -67,6 +67,7 @@ function testPageCacheTags() {
$cache_entry = \Drupal::cache('render')->get($cid);
sort($cache_entry->tags);
$expected_tags = array(
'config:user.role.anonymous',
'pre_render',
'rendered',
'system_test_cache_tags_page',
......
......@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Cache;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Url;
/**
......@@ -99,8 +100,14 @@ protected function debugCacheTags(array $actual_tags, array $expected_tags) {
*
* @param string[] $expected_tags
* The expected tags.
* @param bool $include_default_tags
* (optional) Whether the default cache tags should be included.
*/
protected function assertCacheTags(array $expected_tags) {
protected function assertCacheTags(array $expected_tags, $include_default_tags = TRUE) {
// The anonymous role cache tag is only added if the user is anonymous.
if ($include_default_tags && \Drupal::currentUser()->isAnonymous()) {
$expected_tags = Cache::mergeTags($expected_tags, ['config:user.role.anonymous']);
}
$actual_tags = $this->getCacheHeaderValues('X-Drupal-Cache-Tags');
sort($expected_tags);
sort($actual_tags);
......@@ -115,11 +122,23 @@ protected function assertCacheTags(array $expected_tags) {
* The expected cache contexts.
* @param string $message
* (optional) A verbose message to output.
* @param bool $include_default_contexts
* (optional) Whether the default contexts should automatically be included.
*
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertCacheContexts(array $expected_contexts, $message = NULL) {
protected function assertCacheContexts(array $expected_contexts, $message = NULL, $include_default_contexts = TRUE) {
if ($include_default_contexts) {
$default_contexts = ['languages:language_interface', 'theme'];
// Add the user.permission context to the list of default contexts except
// when user is already there.
if (!in_array('user', $expected_contexts)) {
$default_contexts[] = 'user.permissions';
}
$expected_contexts = Cache::mergeContexts($expected_contexts, $default_contexts);
}
$actual_contexts = $this->getCacheHeaderValues('X-Drupal-Cache-Contexts');
sort($expected_contexts);
sort($actual_contexts);
......
......@@ -58,7 +58,7 @@ function testLinkCacheability() {
foreach ($cases as $case) {
list($title, $uri, $options, $expected_cacheability) = $case;
$expected_cacheability['contexts'] = Cache::mergeContexts($expected_cacheability['contexts'], ['languages:language_interface', 'theme']);
$expected_cacheability['contexts'] = Cache::mergeContexts($expected_cacheability['contexts'], ['languages:language_interface', 'theme', 'user.permissions']);
$link = [
'#type' => 'link',
'#title' => $title,
......
......@@ -132,6 +132,8 @@ protected static function generateStandardizedInfo($entity_type_label, $group) {
/**
* Returns the access cache contexts for the tested entity.
*
* Only list cache contexts that aren't part of the required cache contexts.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be tested, as created by createEntity().
*
......@@ -141,12 +143,14 @@ protected static function generateStandardizedInfo($entity_type_label, $group) {
* @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
return ['user.permissions'];
return [];
}
/**
* Returns the additional (non-standard) cache contexts for the tested entity.
*
* Only list cache contexts that aren't part of the required cache contexts.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be tested, as created by createEntity().
*
......@@ -331,12 +335,16 @@ public function testReferencedEntity() {
$nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', ['entity_type_id' => $entity_type]);
// The default cache contexts for rendered entities.
$default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'];
$default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$entity_cache_contexts = $default_cache_contexts;
// Cache tags present on every rendered page.
$page_cache_tags = Cache::mergeTags(
['rendered'],
// 'user.permissions' is a required cache context, and responses that vary
// by this cache context when requested by anonymous users automatically
// also get this cache tag, to ensure correct invalidation.
['config:user.role.anonymous'],
// If the block module is used, the Block page display variant is used,
// which adds the block config entity type's list cache tags.
\Drupal::moduleHandler()->moduleExists('block') ? ['config:block_list']: []
......@@ -391,9 +399,10 @@ public function testReferencedEntity() {
$cache_keys = ['entity_view', 'entity_test', $this->referencingEntity->id(), 'full'];
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
$access_cache_contexts = $this->getAccessCacheContextsForEntity($this->entity);
$additional_cache_contexts = $this->getAdditionalCacheContextsForEntity($this->referencingEntity);
$redirected_cid = NULL;
if (count($access_cache_contexts)) {
$cache_contexts = Cache::mergeContexts($entity_cache_contexts, $this->getAdditionalCacheContextsForEntity($this->referencingEntity), $access_cache_contexts);
if (count($access_cache_contexts) || count($additional_cache_contexts)) {
$cache_contexts = Cache::mergeContexts($entity_cache_contexts, $additional_cache_contexts, $access_cache_contexts);
$redirected_cid = $this->createCacheId($cache_keys, $cache_contexts);
$context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts);
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $context_metadata->getCacheTags());
......
......@@ -67,7 +67,7 @@ public function testCacheContexts() {
$build = $list_builder->render();
$this->container->get('renderer')->renderRoot($build);
$this->assertEqual(['entity_test_view_grants', 'languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'url.query_args.pagers:0'], $build['#cache']['contexts']);
$this->assertEqual(['entity_test_view_grants', 'languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'url.query_args.pagers:0', 'user.permissions'], $build['#cache']['contexts']);
}
}
......@@ -65,7 +65,7 @@ public function testEntityViewBuilderCache() {
// Get a fully built entity view render array.
$entity_test->save();
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys());
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$cid = implode(':', $cid_parts);
$bin = $build['#cache']['bin'];
......@@ -117,7 +117,7 @@ public function testEntityViewBuilderCacheWithReferences() {
// Get a fully built entity view render array for the referenced entity.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full');
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys());
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$cid_reference = implode(':', $cid_parts);
$bin_reference = $build['#cache']['bin'];
......@@ -136,7 +136,7 @@ public function testEntityViewBuilderCacheWithReferences() {
// Get a fully built entity view render array.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys());
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$cid = implode(':', $cid_parts);
$bin = $build['#cache']['bin'];
......
......@@ -32,7 +32,7 @@ public function testEntityUri() {
$view_mode = $this->selectViewMode($entity_type);
// The default cache contexts for rendered entities.
$entity_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'];
$entity_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
// Generate the standardized entity cache tags.
$cache_tag = $this->entity->getCacheTags();
......
......@@ -30,7 +30,7 @@ class RouterTest extends WebTestBase {
* Confirms that our FinishResponseSubscriber logic works properly.
*/
public function testFinishResponseSubscriber() {
$renderer_required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'];
$renderer_required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
// Confirm that the router can get to a controller.
$this->drupalGet('router_test/test1');
......@@ -47,7 +47,7 @@ public function testFinishResponseSubscriber() {
// Check expected headers from FinishResponseSubscriber.
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', $renderer_required_cache_contexts));
$this->assertEqual($headers['x-drupal-cache-tags'], 'rendered');
$this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous rendered');
// Confirm that the page wrapping is being added, so we're not getting a
// raw body returned.
$this->assertRaw('</html>', 'Page markup was found.');
......@@ -62,12 +62,12 @@ public function testFinishResponseSubscriber() {
$this->drupalGet('router_test/test18');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url'])));
$this->assertEqual($headers['x-drupal-cache-tags'], 'foo rendered');
$this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous foo rendered');
// 2. controller result: render array, per-role cacheable route access.
$this->drupalGet('router_test/test19');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url', 'user.roles'])));
$this->assertEqual($headers['x-drupal-cache-tags'], 'foo rendered');
$this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous foo rendered');
// 3. controller result: Response object, globally cacheable route access.
$this->drupalGet('router_test/test1');
$headers = $this->drupalGetHeaders();
......
......@@ -52,6 +52,7 @@ public function testRenderedTour() {
// Verify a cache hit, but also the presence of the correct cache tags.
$expected_tags = [
'config:tour.tour.tour-test',
'config:user.role.anonymous',
'rendered',
];
$this->verifyPageCache($url, 'HIT', $expected_tags);
......@@ -71,6 +72,7 @@ public function testRenderedTour() {
// Verify a cache hit.
$expected_tags = [
'config:user.role.anonymous',
'rendered',
];
$this->verifyPageCache($url, 'HIT', $expected_tags);
......
......@@ -50,7 +50,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return parent::create($container, $configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'));
return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'));
}
/**
......
......@@ -31,7 +31,7 @@ abstract class ViewUnitTestBase extends KernelTestBase {
*
* @var array
*/
public static $modules = array('system', 'views', 'views_test_config', 'views_test_data');
public static $modules = array('system', 'views', 'views_test_config', 'views_test_data', 'user');
/**
* {@inheritdoc}
......
......@@ -82,8 +82,8 @@ parameters:
# The Renderer will automatically associate these cache contexts with every
# render array, hence varying every render array by these cache contexts.
#
# @default ['languages:language_interface', 'theme']
required_cache_contexts: ['languages:language_interface', 'theme']
# @default ['languages:language_interface', 'theme', 'user.permissions']
required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']
factory.keyvalue:
{}
# Default key/value storage service to use.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment