diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php index cd2b958..6ce9173 100644 --- a/core/lib/Drupal/Core/Access/AccessResult.php +++ b/core/lib/Drupal/Core/Access/AccessResult.php @@ -319,10 +319,12 @@ public function cachePerUser() { * The entity whose cache tag to set on the access result. * * @return $this + * + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use + * ::addCacheableDependency() instead. */ public function cacheUntilEntityChanges(EntityInterface $entity) { - $this->addCacheTags($entity->getCacheTags()); - return $this; + return $this->addCacheableDependency($entity); } /** @@ -332,9 +334,33 @@ public function cacheUntilEntityChanges(EntityInterface $entity) { * The configuration object whose cache tag to set on the access result. * * @return $this + * + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use + * ::addCacheableDependency() instead. */ public function cacheUntilConfigurationChanges(ConfigBase $configuration) { - $this->addCacheTags($configuration->getCacheTags()); + return $this->addCacheableDependency($configuration); + } + + /** + * Adds a dependency on an object: merges its cacheability metadata. + * + * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $other_object + * The dependency. If the object implements CacheableDependencyInterface, + * then its cacheability metadata will be used. Otherwise, the passed in + * object must be assumed to be uncacheable, so max-age 0 is set. + * + * @return $this + */ + public function addCacheableDependency($other_object) { + if ($other_object instanceof CacheableDependencyInterface) { + $this->contexts = Cache::mergeContexts($this->contexts, $other_object->getCacheContexts()); + $this->tags = Cache::mergeTags($this->tags, $other_object->getCacheTags()); + $this->maxAge = Cache::mergeMaxAges($this->maxAge, $other_object->getCacheMaxAge()); + } + else { + $this->maxAge = 0; + } return $this; } diff --git a/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyInterface.php b/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyInterface.php index ff9c172..11e1584 100644 --- a/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyInterface.php +++ b/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyInterface.php @@ -52,4 +52,18 @@ public function addCacheTags(array $cache_tags); */ public function mergeCacheMaxAge($max_age); + /** + * Adds a dependency on an object: merges its cacheability metadata. + * + * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $other_object + * The dependency. If the object implements CacheableDependencyInterface, + * then its cacheability metadata will be used. Otherwise, the passed in + * object must be assumed to be uncacheable, so max-age 0 is set. + * + * @return $this + * + * @see \Drupal\Core\Cache\CacheableMetadata::createFromObject() + */ + public function addCacheableDependency($other_object); + } diff --git a/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyTrait.php b/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyTrait.php index 972a829..63b1d3d 100644 --- a/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyTrait.php +++ b/core/lib/Drupal/Core/Cache/RefinableCacheableDependencyTrait.php @@ -36,6 +36,22 @@ /** * {@inheritdoc} */ + public function addCacheableDependency($other_object) { + if ($other_object instanceof CacheableDependencyInterface) { + $this->addCacheContexts($other_object->getCacheContexts()); + $this->addCacheTags($other_object->getCacheTags()); + $this->mergeCacheMaxAge($other_object->getCacheMaxAge()); + } + else { + // Not a cacheable dependency, this can not be cached. + $this->maxAge = 0; + } + return $this; + } + + /** + * {@inheritdoc} + */ public function addCacheContexts(array $cache_contexts) { $this->cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts); return $this; diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php index eee76a2..1ffe7c0 100644 --- a/core/lib/Drupal/Core/Config/ConfigBase.php +++ b/core/lib/Drupal/Core/Config/ConfigBase.php @@ -10,7 +10,8 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; +use Drupal\Core\Cache\RefinableCacheableDependencyTrait; use \Drupal\Core\DependencyInjection\DependencySerializationTrait; /** @@ -28,8 +29,9 @@ * @see \Drupal\Core\Config\Config * @see \Drupal\Core\Theme\ThemeSettings */ -abstract class ConfigBase implements CacheableDependencyInterface { +abstract class ConfigBase implements RefinableCacheableDependencyInterface { use DependencySerializationTrait; + use RefinableCacheableDependencyTrait; /** * The name of the configuration object. @@ -269,21 +271,21 @@ public function merge(array $data_to_merge) { * {@inheritdoc} */ public function getCacheContexts() { - return []; + return $this->cacheContexts; } /** * {@inheritdoc} */ public function getCacheTags() { - return ['config:' . $this->name]; + return Cache::mergeTags(['config:' . $this->name], $this->cacheTags); } /** * {@inheritdoc} */ public function getCacheMaxAge() { - return Cache::PERMANENT; + return $this->cacheMaxAge; } } diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index cdfd951..6ec8a5c 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -126,6 +126,9 @@ protected function doGet($name, $immutable = TRUE) { $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]); } } + + $this->propagateConfigOverrideCacheability($cache_key, $name); + return $this->cache[$cache_key]; } } @@ -183,6 +186,9 @@ protected function doLoadMultiple(array $names, $immutable = TRUE) { $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]); } } + + $this->propagateConfigOverrideCacheability($cache_key, $name); + $list[$name] = $this->cache[$cache_key]; } } @@ -210,6 +216,20 @@ protected function loadOverrides(array $names) { } /** + * Propagates cacheability of config overrides to cached config objects. + * + * @param string $cache_key + * The key of the cached config object to update. + * @param string $name + * The name of the configuration object to construct. + */ + protected function propagateConfigOverrideCacheability($cache_key, $name) { + foreach ($this->configFactoryOverrides as $override) { + $this->cache[$cache_key]->addCacheableDependency($override->getCacheableMetadata($name)); + } + } + + /** * {@inheritdoc} */ public function reset($name = NULL) { diff --git a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php index 56dbc19..07938a0 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php @@ -58,4 +58,15 @@ public function getCacheSuffix(); */ public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION); + /** + * Gets the cacheability metadata associated with the config factory override. + * + * @param string $name + * The name of the configuration override to get metadata for. + * + * @return \Drupal\Core\Cache\CacheableMetadata + * A cacheable metadata object. + */ + public function getCacheableMetadata($name); + } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 59776aa..6fcac80 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -14,7 +14,6 @@ use Drupal\Core\Entity\EntityMalformedException; use Drupal\Core\Entity\EntityStorageBase; use Drupal\Core\Config\Config; -use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Component\Uuid\UuidInterface; @@ -184,11 +183,24 @@ protected function doLoadMultiple(array $ids = NULL) { } // Load all of the configuration entities. - $records = array(); + /** @var \Drupal\Core\Config\Config[] $configs */ + $configs = []; + $records = []; foreach ($this->configFactory->loadMultiple($names) as $config) { - $records[$config->get($this->idKey)] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get(); + $id = $config->get($this->idKey); + $records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get(); + $configs[$id] = $config; } - return $this->mapFromStorageRecords($records); + $entities = $this->mapFromStorageRecords($records, $configs); + + // Config entities wrap config objects, and therefore they need to inherit + // the cacheability metadata of config objects (to ensure e.g. additional + // cacheability metadata added by config overrides is not lost). + foreach ($entities as $id => $entity) { + $entity->addCacheableDependency($configs[$id]); + } + + return $entities; } /** diff --git a/core/modules/block/src/BlockViewBuilder.php b/core/modules/block/src/BlockViewBuilder.php index d7161d7..187692d 100644 --- a/core/modules/block/src/BlockViewBuilder.php +++ b/core/modules/block/src/BlockViewBuilder.php @@ -70,7 +70,10 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la '#id' => $entity->id(), '#cache' => [ 'keys' => ['entity_view', 'block', $entity->id()], - 'contexts' => $plugin->getCacheContexts(), + 'contexts' => Cache::mergeContexts( + $entity->getCacheContexts(), + $plugin->getCacheContexts() + ), 'tags' => $cache_tags, 'max-age' => $plugin->getCacheMaxAge(), ], diff --git a/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideIntegrationTest.php b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideIntegrationTest.php new file mode 100644 index 0000000..22d0b11 --- /dev/null +++ b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideIntegrationTest.php @@ -0,0 +1,65 @@ +set('block_test.content', 'Needs to have some content'); + + $this->drupalLogin($this->drupalCreateUser()); + } + + /** + * Tests if config overrides correctly set cacheability metadata. + */ + public function testConfigOverride() { + // Check the default (disabled) state of the cache context. The block label + // should not be overridden. + $this->drupalGet(''); + $this->assertNoText('Overridden block label'); + + // Both the cache context and tag should be present. + $this->assertCacheContext('config_override_integration_test'); + $this->assertCacheTag('config_override_integration_test_tag'); + + // Flip the state of the cache context. The block label should now be + // overridden. + \Drupal::state()->set('config_override_integration_test.enabled', TRUE); + $this->drupalGet(''); + $this->assertText('Overridden block label'); + + // Both the cache context and tag should still be present. + $this->assertCacheContext('config_override_integration_test'); + $this->assertCacheTag('config_override_integration_test_tag'); + } + +} diff --git a/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php new file mode 100644 index 0000000..cc6e024 --- /dev/null +++ b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php @@ -0,0 +1,84 @@ +installEntitySchema('block_content'); + $this->installConfig(['config_override_test']); + } + + /** + * Tests if config overrides correctly set cacheability metadata. + */ + public function testConfigOverride() { + // It's pirate day today! + $GLOBALS['it_is_pirate_day'] = TRUE; + + $config_factory = $this->container->get('config.factory'); + $config = $config_factory->get('system.theme'); + + // Check that we are using the Pirate theme. + $theme = $config->get('default'); + $this->assertEqual('pirate', $theme); + + // Check that the cacheability metadata is correct. + $this->assertEqual(['pirate_day'], $config->getCacheContexts()); + $this->assertEqual(['config:system.theme', 'pirate-day-tag'], $config->getCacheTags()); + $this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $config->getCacheMaxAge()); + } + + /** + * Tests if config overrides set cacheability metadata on config entities. + */ + public function testConfigEntityOverride() { + // It's pirate day today! + $GLOBALS['it_is_pirate_day'] = TRUE; + + // Load the User login block and check that its cacheability metadata is + // overridden correctly. This verifies that the metadata is correctly + // applied to config entities. + /** @var EntityManagerInterface $entity_manager */ + $entity_manager = $this->container->get('entity.manager'); + $block = $entity_manager->getStorage('block')->load('call_to_action'); + + // Check that our call to action message is appealing to filibusters. + $this->assertEqual($block->label(), 'Draw yer cutlasses!'); + + // Check that the cacheability metadata is correct. + $this->assertEqual(['pirate_day'], $block->getCacheContexts()); + $this->assertEqual(['config:block.block.call_to_action', 'pirate-day-tag'], $block->getCacheTags()); + $this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge()); + } + +} diff --git a/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php b/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php index 2867c7d..3f79379 100644 --- a/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php +++ b/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php @@ -7,6 +7,7 @@ namespace Drupal\config_entity_static_cache_test; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryOverrideInterface; use Drupal\Core\Config\StorageInterface; @@ -40,4 +41,11 @@ public function createConfigObject($name, $collection = StorageInterface::DEFAUL return NULL; } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($name) { + return new CacheableMetadata(); + } + } diff --git a/core/modules/config/tests/config_override_integration_test/config/install/block.block.config_override_test.yml b/core/modules/config/tests/config_override_integration_test/config/install/block.block.config_override_test.yml new file mode 100644 index 0000000..be0616f --- /dev/null +++ b/core/modules/config/tests/config_override_integration_test/config/install/block.block.config_override_test.yml @@ -0,0 +1,24 @@ +id: config_override_test +theme: classy +weight: 0 +status: true +langcode: en +region: content +plugin: test_cache +settings: + label: 'Test HTML block' + provider: block_test + label_display: visible + status: true + info: '' + view_mode: default +dependencies: + module: + - block_test + theme: + - classy +visibility: + request_path: + id: request_path + pages: '' + negate: false diff --git a/core/modules/config/tests/config_override_integration_test/config_override_integration_test.info.yml b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.info.yml new file mode 100644 index 0000000..22b1e94 --- /dev/null +++ b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.info.yml @@ -0,0 +1,9 @@ +name: 'Configuration override integration test' +type: module +package: Testing +version: VERSION +core: 8.x + +dependencies: + - block + - block_test diff --git a/core/modules/config/tests/config_override_integration_test/config_override_integration_test.services.yml b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.services.yml new file mode 100644 index 0000000..147874e --- /dev/null +++ b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.services.yml @@ -0,0 +1,9 @@ +services: + cache_context.config_override_integration_test: + class: Drupal\config_override_integration_test\Cache\ConfigOverrideIntegrationTestCacheContext + tags: + - { name: cache.context } + config_override_integration_test.config_override: + class: Drupal\config_override_integration_test\CacheabilityMetadataConfigOverride + tags: + - { name: config.factory.override } diff --git a/core/modules/config/tests/config_override_integration_test/src/Cache/ConfigOverrideIntegrationTestCacheContext.php b/core/modules/config/tests/config_override_integration_test/src/Cache/ConfigOverrideIntegrationTestCacheContext.php new file mode 100644 index 0000000..f2192aa --- /dev/null +++ b/core/modules/config/tests/config_override_integration_test/src/Cache/ConfigOverrideIntegrationTestCacheContext.php @@ -0,0 +1,47 @@ +get('config_override_integration_test.enabled', FALSE) ? 'yes' : 'no'; + return 'config_override_integration_test.' . $state; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + // Since this depends on State this can change at any time and is not + // cacheable. + $metadata = new CacheableMetadata(); + $metadata->setCacheMaxAge(0); + return $metadata; + } + +} diff --git a/core/modules/config/tests/config_override_integration_test/src/CacheabilityMetadataConfigOverride.php b/core/modules/config/tests/config_override_integration_test/src/CacheabilityMetadataConfigOverride.php new file mode 100644 index 0000000..fc6a271 --- /dev/null +++ b/core/modules/config/tests/config_override_integration_test/src/CacheabilityMetadataConfigOverride.php @@ -0,0 +1,65 @@ +get('config_override_integration_test.enabled', FALSE); + if (in_array('block.block.config_override_test', $names) && $state !== FALSE) { + $overrides = $overrides + [ + 'block.block.config_override_test' => [ + 'settings' => ['label' => 'Overridden block label'], + ], + ]; + } + + return $overrides; + } + + /** + * {@inheritdoc} + */ + public function getCacheSuffix() { + return 'config_override_integration_test'; + } + + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($name) { + $metadata = new CacheableMetadata(); + if ($name === 'block.block.config_override_test') { + $metadata + ->setCacheContexts(['config_override_integration_test']) + ->setCacheTags(['config_override_integration_test_tag']); + } + return $metadata; + } + +} diff --git a/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml new file mode 100644 index 0000000..8951c0d --- /dev/null +++ b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + module: + - block_content + theme: + - classy +id: call_to_action +theme: classy +region: content +weight: null +provider: null +plugin: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653' +settings: + id: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653' + label: 'Shop for cheap now!' + provider: block_content + label_display: visible + status: true + info: '' + view_mode: default +visibility: + request_path: + id: request_path + pages: '' + negate: false diff --git a/core/modules/config/tests/config_override_test/config_override_test.info.yml b/core/modules/config/tests/config_override_test/config_override_test.info.yml index f1f1109..051729d 100644 --- a/core/modules/config/tests/config_override_test/config_override_test.info.yml +++ b/core/modules/config/tests/config_override_test/config_override_test.info.yml @@ -3,3 +3,7 @@ type: module package: Testing version: VERSION core: 8.x + +dependencies: + - block + - block_content diff --git a/core/modules/config/tests/config_override_test/config_override_test.services.yml b/core/modules/config/tests/config_override_test/config_override_test.services.yml index c3fae64..1a07412 100644 --- a/core/modules/config/tests/config_override_test/config_override_test.services.yml +++ b/core/modules/config/tests/config_override_test/config_override_test.services.yml @@ -1,4 +1,8 @@ services: + cache_context.pirate_day: + class: Drupal\config_override_test\Cache\PirateDayCacheContext + tags: + - { name: cache.context } config_override_test.overrider: class: Drupal\config_override_test\ConfigOverrider tags: @@ -7,3 +11,7 @@ services: class: Drupal\config_override_test\ConfigOverriderLowPriority tags: - { name: config.factory.override, priority: -100 } + config_override_test.pirate_day_cacheability_metadata_override: + class: Drupal\config_override_test\PirateDayCacheabilityMetadataConfigOverride + tags: + - { name: config.factory.override } diff --git a/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php b/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php new file mode 100644 index 0000000..c40cc6a --- /dev/null +++ b/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php @@ -0,0 +1,66 @@ + ['default' => 'pirate']]; + } + if (in_array('block.block.call_to_action', $names)) { + $overrides = $overrides + [ + 'block.block.call_to_action' => [ + 'settings' => ['label' => 'Draw yer cutlasses!'], + ], + ]; + } + } + + return $overrides; + } + + /** + * {@inheritdoc} + */ + public function getCacheSuffix() { + return 'PirateDayConfigOverrider'; + } + + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($name) { + $metadata = new CacheableMetadata(); + $metadata + ->setCacheContexts(['pirate_day']) + ->setCacheTags(['pirate-day-tag']) + ->setCacheMaxAge(PirateDayCacheContext::PIRATE_DAY_MAX_AGE); + return $metadata; + } + + /** + * Returns whether or not our overrides are potentially applicable. + * + * @param string $name + * The name of the config object that is being constructed. + * + * @return bool + * TRUE if the merchant ship will be boarded. FALSE if we drink rum instead. + */ + protected function isCacheabilityMetadataApplicable($name) { + return in_array($name, ['system.theme', 'block.block.call_to_action']); + } + +} diff --git a/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php b/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php index 512e735..6c95f7e 100644 --- a/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php +++ b/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php @@ -9,6 +9,7 @@ use Drupal\content_translation\Access\ContentTranslationManageAccessCheck; use Drupal\Core\Access\AccessResult; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Language\Language; use Drupal\Tests\UnitTestCase; use Symfony\Component\Routing\Route; @@ -23,6 +24,28 @@ class ContentTranslationManageAccessCheckTest extends UnitTestCase { /** + * The cache contexts manager. + * + * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheContextsManager; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') + ->disableOriginalConstructor() + ->getMock(); + + $container = new ContainerBuilder(); + $container->set('cache_contexts_manager', $this->cacheContextsManager); + \Drupal::setContainer($container); + } + + /** * Tests the create access method. * * @covers ::access @@ -76,6 +99,9 @@ public function testCreateAccess() { $entity->expects($this->once()) ->method('getCacheTags') ->will($this->returnValue(array('node:1337'))); + $entity->expects($this->once()) + ->method('getCacheContexts') + ->willReturn(array()); // Set the route requirements. $route = new Route('test_route'); diff --git a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php index 3d0a67b..e547d1e 100644 --- a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php +++ b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php @@ -7,6 +7,7 @@ namespace Drupal\language\Config; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigCollectionInfo; use Drupal\Core\Config\ConfigCrudEvent; use Drupal\Core\Config\ConfigFactoryOverrideBase; @@ -222,4 +223,15 @@ public function onConfigDelete(ConfigCrudEvent $event) { } } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($name) { + $metadata = new CacheableMetadata(); + if ($this->language) { + $metadata->setCacheContexts(['languages:language_interface']); + } + return $metadata; + } + } diff --git a/core/modules/views/src/Tests/Plugin/CacheTest.php b/core/modules/views/src/Tests/Plugin/CacheTest.php index 0d8c907..f8c4d1d 100644 --- a/core/modules/views/src/Tests/Plugin/CacheTest.php +++ b/core/modules/views/src/Tests/Plugin/CacheTest.php @@ -299,7 +299,7 @@ function testHeaderStorage() { $this->assertTrue(in_array('views_test_data/test', $output['#attached']['library']), 'Make sure libraries are added for cached views.'); $this->assertEqual(['foo' => 'bar'], $output['#attached']['drupalSettings'], 'Make sure drupalSettings are added for cached views.'); // Note: views_test_data_views_pre_render() adds some cache tags. - $this->assertEqual(['config:views.view.test_cache_header_storage', 'views_test_data:1'], $output['#cache']['tags']); + $this->assertEqual(['config:views.view.test_cache_header_storage', 'config:views.view.test_view', 'views_test_data:1'], $output['#cache']['tags']); $this->assertEqual(['non-existing-placeholder-just-for-testing-purposes' => ['#lazy_builder' => ['views_test_data_placeholders', ['bar']]]], $output['#attached']['placeholders']); $this->assertFalse(!empty($view->build_info['pre_render_called']), 'Make sure hook_views_pre_render is not called for the cached view.'); } diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 792898a..3234869 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -10,7 +10,8 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Timer; use Drupal\Component\Utility\Xss; -use Drupal\Core\Config\Entity\ThirdPartySettingsInterface; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\EventSubscriber\AjaxResponseSubscriber; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -18,7 +19,6 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\views\ViewExecutable; use Drupal\Core\Database\Database; -use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\Session\AccountInterface; use Drupal\views\Plugin\views\query\Sql; use Drupal\views\Entity\View; @@ -1339,6 +1339,14 @@ public function hasTrustedData() { /** * {@inheritdoc} */ + public function addCacheableDependency($other_object) { + $this->storage->addCacheableDependency($other_object); + return $this; + } + + /** + * {@inheritdoc} + */ public function addCacheContexts(array $cache_contexts) { return $this->storage->addCacheContexts($cache_contexts); } diff --git a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php index 123888a..881b096 100644 --- a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php +++ b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php @@ -12,6 +12,7 @@ use Drupal\Core\Access\AccessResultNeutral; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Tests\UnitTestCase; /** @@ -20,6 +21,28 @@ */ class AccessResultTest extends UnitTestCase { + /** + * The cache contexts manager. + * + * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheContextsManager; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') + ->disableOriginalConstructor() + ->getMock(); + + $container = new ContainerBuilder(); + $container->set('cache_contexts_manager', $this->cacheContextsManager); + \Drupal::setContainer($container); + } + protected function assertDefaultCacheability(AccessResult $access) { $this->assertSame([], $access->getCacheContexts()); $this->assertSame([], $access->getCacheTags()); @@ -382,18 +405,17 @@ public function testCacheContexts() { /** * @covers ::addCacheTags - * @covers ::resetCacheTags + * @covers ::addCacheableDependency * @covers ::getCacheTags - * @covers ::cacheUntilEntityChanges - * @covers ::cacheUntilConfigurationChanges + * @covers ::resetCacheTags */ public function testCacheTags() { - $verify = function (AccessResult $access, array $tags) { + $verify = function (AccessResult $access, array $tags, array $contexts = [], $max_age = Cache::PERMANENT) { $this->assertFalse($access->isAllowed()); $this->assertFalse($access->isForbidden()); $this->assertTrue($access->isNeutral()); - $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge()); - $this->assertSame([], $access->getCacheContexts()); + $this->assertSame($max_age, $access->getCacheMaxAge()); + $this->assertSame($contexts, $access->getCacheContexts()); $this->assertSame($tags, $access->getCacheTags()); }; @@ -420,29 +442,26 @@ public function testCacheTags() { ->addCacheTags(['bar:baz']); $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']); - // ::cacheUntilEntityChanges() convenience method. + // ::addCacheableDependency() convenience method. $node = $this->getMock('\Drupal\node\NodeInterface'); $node->expects($this->any()) ->method('getCacheTags') ->will($this->returnValue(array('node:20011988'))); + $node->expects($this->any()) + ->method('getCacheMaxAge') + ->willReturn(600); + $node->expects($this->any()) + ->method('getCacheContexts') + ->willReturn(['user']); $tags = array('node:20011988'); $a = AccessResult::neutral()->addCacheTags($tags); $verify($a, $tags); - $b = AccessResult::neutral()->cacheUntilEntityChanges($node); - $verify($b, $tags); - $this->assertEquals($a, $b); + $b = AccessResult::neutral()->addCacheableDependency($node); + $verify($b, $tags, ['user'], 600); - // ::cacheUntilConfigurationChanges() convenience method. - $configuration = $this->getMock('\Drupal\Core\Config\ConfigBase'); - $configuration->expects($this->any()) - ->method('getCacheTags') - ->will($this->returnValue(array('config:foo.bar.baz'))); - $tags = array('config:foo.bar.baz'); - $a = AccessResult::neutral()->addCacheTags($tags); - $verify($a, $tags); - $b = AccessResult::neutral()->cacheUntilConfigurationChanges($configuration); - $verify($b, $tags); - $this->assertEquals($a, $b); + $non_cacheable_dependency = new \stdClass(); + $non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency); + $verify($non_cacheable, [], [], 0); } /** diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index c4413eb..2566c73 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Config\Entity { +use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Language\Language; @@ -104,6 +105,13 @@ class ConfigEntityStorageTest extends UnitTestCase { protected $configManager; /** + * The cache contexts manager. + * + * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheContextsManager; + + /** * {@inheritdoc} * * @covers ::__construct @@ -170,12 +178,17 @@ protected function setUp() { $this->configManager = $this->getMock('Drupal\Core\Config\ConfigManagerInterface'); + $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') + ->disableOriginalConstructor() + ->getMock(); + $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('config.typed', $this->typedConfigManager); $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator); $container->set('config.manager', $this->configManager); $container->set('language_manager', $this->languageManager); + $container->set('cache_contexts_manager', $this->cacheContextsManager); \Drupal::setContainer($container); } @@ -611,6 +624,18 @@ public function testSaveChangedUuid() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $config_object->expects($this->exactly(2)) + ->method('getCacheTags') + ->willReturn(['config:foo']); + $config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(Cache::PERMANENT); + $config_object->expects($this->exactly(1)) + ->method('getName') + ->willReturn('foo'); $this->cacheTagsInvalidator->expects($this->never()) ->method('invalidateTags'); @@ -664,6 +689,18 @@ public function testLoad() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $config_object->expects($this->exactly(2)) + ->method('getCacheTags') + ->willReturn(['config:foo']); + $config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(Cache::PERMANENT); + $config_object->expects($this->exactly(1)) + ->method('getName') + ->willReturn('foo'); $this->configFactory->expects($this->once()) ->method('loadMultiple') @@ -694,6 +731,19 @@ public function testLoadMultipleAll() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $foo_config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $foo_config_object->expects($this->exactly(2)) + ->method('getCacheTags') + ->willReturn(['config:foo']); + $foo_config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(Cache::PERMANENT); + $foo_config_object->expects($this->exactly(1)) + ->method('getName') + ->willReturn('foo'); + $bar_config_object = $this->getMockBuilder('Drupal\Core\Config\Config') ->disableOriginalConstructor() ->getMock(); @@ -703,6 +753,18 @@ public function testLoadMultipleAll() { array('', array('id' => 'bar')), array('id', 'bar'), ))); + $bar_config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $bar_config_object->expects($this->exactly(2)) + ->method('getCacheTags') + ->willReturn(['config:bar']); + $bar_config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(Cache::PERMANENT); + $bar_config_object->expects($this->exactly(1)) + ->method('getName') + ->willReturn('foo'); $this->configFactory->expects($this->once()) ->method('listAll') @@ -742,6 +804,18 @@ public function testLoadMultipleIds() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $config_object->expects($this->exactly(2)) + ->method('getCacheTags') + ->willReturn(['config:foo']); + $config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(Cache::PERMANENT); + $config_object->expects($this->exactly(1)) + ->method('getName') + ->willReturn('foo'); $this->configFactory->expects($this->once()) ->method('loadMultiple')