diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php index 9c4e42f..269c83b 100644 --- a/core/lib/Drupal/Core/Access/AccessResult.php +++ b/core/lib/Drupal/Core/Access/AccessResult.php @@ -25,6 +25,9 @@ * * When using ::orIf() and ::andIf(), cacheability metadata will be merged * accordingly as well. + * + * @todo Use MutableCacheableDependencyInterface and the corresponding trait in + * https://www.drupal.org/node/2526326. */ abstract class AccessResult implements AccessResultInterface, CacheableDependencyInterface { diff --git a/core/lib/Drupal/Core/Cache/CacheableMetadata.php b/core/lib/Drupal/Core/Cache/CacheableMetadata.php index a0c1ba8..e0bb6ac 100644 --- a/core/lib/Drupal/Core/Cache/CacheableMetadata.php +++ b/core/lib/Drupal/Core/Cache/CacheableMetadata.php @@ -10,6 +10,9 @@ * Defines a generic class for passing cacheability metadata. * * @ingroup cache + * + * @todo Use MutableCacheableDependencyInterface and the corresponding trait in + * https://www.drupal.org/node/2526326. */ class CacheableMetadata implements CacheableDependencyInterface { diff --git a/core/lib/Drupal/Core/Cache/MutableCacheableDependencyInterface.php b/core/lib/Drupal/Core/Cache/MutableCacheableDependencyInterface.php new file mode 100644 index 0000000..978a604 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/MutableCacheableDependencyInterface.php @@ -0,0 +1,54 @@ +cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts); + return $this; + } + + /** + * {@inheritdoc} + */ + public function addCacheTags(array $cache_tags) { + $this->cacheTags = Cache::mergeTags($this->cacheTags, $cache_tags); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setCacheMaxAgeIfLower($max_age) { + $this->cacheMaxAge = Cache::mergeMaxAges($this->cacheMaxAge, $max_age); + return $this; + } + +} diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php index eee76a2..51801b1 100644 --- a/core/lib/Drupal/Core/Config/ConfigBase.php +++ b/core/lib/Drupal/Core/Config/ConfigBase.php @@ -11,6 +11,8 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\MutableCacheableDependencyInterface; +use Drupal\Core\Cache\MutableCacheableDependencyTrait; use \Drupal\Core\DependencyInjection\DependencySerializationTrait; /** @@ -28,8 +30,9 @@ * @see \Drupal\Core\Config\Config * @see \Drupal\Core\Theme\ThemeSettings */ -abstract class ConfigBase implements CacheableDependencyInterface { +abstract class ConfigBase implements CacheableDependencyInterface, MutableCacheableDependencyInterface { use DependencySerializationTrait; + use MutableCacheableDependencyTrait; /** * The name of the configuration object. @@ -269,21 +272,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..907ea6d 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -126,6 +126,10 @@ protected function doGet($name, $immutable = TRUE) { $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]); } } + + // Propagate cache contexts to the config object. + $this->propagateCacheableDependencyOverrides($cache_key); + return $this->cache[$cache_key]; } } @@ -183,6 +187,10 @@ protected function doLoadMultiple(array $names, $immutable = TRUE) { $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]); } } + + // Propagate cacheable dependencies to the config object. + $this->propagateCacheableDependencyOverrides($cache_key); + $list[$name] = $this->cache[$cache_key]; } } @@ -210,6 +218,20 @@ protected function loadOverrides(array $names) { } /** + * Propagates overridden cacheable dependencies to cached config objects. + * + * @param string $cache_key + * The key of the cached config object to update. + */ + protected function propagateCacheableDependencyOverrides($cache_key) { + foreach ($this->configFactoryOverrides as $override) { + $this->cache[$cache_key]->addCacheContexts($override->getCacheContexts()); + $this->cache[$cache_key]->addCacheTags($override->getCacheTags()); + $this->cache[$cache_key]->setCacheMaxAgeIfLower($override->getCacheMaxAge()); + } + } + + /** * {@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..7b07c1a 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php @@ -7,10 +7,12 @@ namespace Drupal\Core\Config; +use Drupal\Core\Cache\CacheableDependencyInterface; + /** * Defines the interface for a configuration factory override object. */ -interface ConfigFactoryOverrideInterface { +interface ConfigFactoryOverrideInterface extends CacheableDependencyInterface { /** * Returns config overrides. diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index a810dbd..e9432f7 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -193,7 +193,7 @@ public function enable() { */ public function disable() { // An entity was disabled, invalidate its own cache tag. - Cache::invalidateTags($this->getCacheTags()); + Cache::invalidateTags($this->getCacheTagsForInvalidation()); return $this->setStatus(FALSE); } @@ -409,7 +409,7 @@ public function link($text = NULL, $rel = 'edit-form', array $options = []) { /** * {@inheritdoc} */ - public function getCacheTags() { + public function getCacheTagsForInvalidation() { // Use cache tags that match the underlying config object's name. // @see \Drupal\Core\Config\ConfigBase::getCacheTags() return ['config:' . $this->getConfigDependencyName()]; diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 59776aa..2a2de2f 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -187,6 +187,11 @@ protected function doLoadMultiple(array $ids = NULL) { $records = array(); foreach ($this->configFactory->loadMultiple($names) as $config) { $records[$config->get($this->idKey)] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get(); + + // Add cacheability metadata to the record. + $records[$config->get($this->idKey)]['cacheContexts'] = $config->getCacheContexts(); + $records[$config->get($this->idKey)]['cacheTags'] = $config->getCacheTags(); + $records[$config->get($this->idKey)]['cacheMaxAge'] = $config->getCacheMaxAge(); } return $this->mapFromStorageRecords($records); } diff --git a/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php b/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php index fa1de86..0561df1 100644 --- a/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php +++ b/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php @@ -101,7 +101,7 @@ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) /** * {@inheritdoc} */ - public function getCacheTags() { + public function getCacheTagsForInvalidation() { return ['rendered']; } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 9ffa497..4c77b5a 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\MutableCacheableDependencyTrait; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\Unicode; @@ -24,6 +25,8 @@ */ abstract class Entity implements EntityInterface { + use MutableCacheableDependencyTrait; + use DependencySerializationTrait { __sleep as traitSleep; } @@ -440,23 +443,36 @@ public function referencedEntities() { * {@inheritdoc} */ public function getCacheContexts() { - return []; + return $this->cacheContexts; } /** * {@inheritdoc} */ - public function getCacheTags() { + public function getCacheTagsForInvalidation() { // @todo Add bundle-specific listing cache tag? // https://www.drupal.org/node/2145751 + if ($this->isNew()) { + return []; + } return [$this->entityTypeId . ':' . $this->id()]; } /** * {@inheritdoc} */ + public function getCacheTags() { + if ($this->cacheTags) { + return Cache::mergeTags($this->getCacheTagsForInvalidation(), $this->cacheTags); + } + return $this->getCacheTagsForInvalidation(); + } + + /** + * {@inheritdoc} + */ public function getCacheMaxAge() { - return Cache::PERMANENT; + return $this->cacheMaxAge; } /** @@ -511,7 +527,7 @@ protected function invalidateTagsOnSave($update) { } if ($update) { // An existing entity was updated, also invalidate its unique cache tag. - $tags = Cache::mergeTags($tags, $this->getCacheTags()); + $tags = Cache::mergeTags($tags, $this->getCacheTagsForInvalidation()); } Cache::invalidateTags($tags); } @@ -532,7 +548,7 @@ protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_typ // other pages than the one it's on. The one it's on is handled by its own // cache tag, but subsequent list pages would not be invalidated, hence we // must invalidate its list cache tags as well.) - $tags = Cache::mergeTags($tags, $entity->getCacheTags()); + $tags = Cache::mergeTags($tags, $entity->getCacheTagsForInvalidation()); } Cache::invalidateTags($tags); } diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php index 6d61d0c..9caaf8d 100644 --- a/core/lib/Drupal/Core/Entity/EntityForm.php +++ b/core/lib/Drupal/Core/Entity/EntityForm.php @@ -101,6 +101,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { $this->init($form_state); } + // Ensure that edit forms have the correct cacheability metadata so they can + // be cached. + if (!$this->entity->isNew()) { + \Drupal::service('renderer')->addCacheableDependency($form, $this->entity); + } + // Retrieve the form array using the possibly updated entity in form state. $form = $this->form($form, $form_state); diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index 68dafd0..2d443d1 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -9,13 +9,14 @@ use Drupal\Core\Access\AccessibleInterface; use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\MutableCacheableDependencyInterface; /** * Defines a common interface for all entity objects. * * @ingroup entity_api */ -interface EntityInterface extends AccessibleInterface, CacheableDependencyInterface { +interface EntityInterface extends AccessibleInterface, CacheableDependencyInterface, MutableCacheableDependencyInterface { /** * Gets the entity UUID (Universally Unique Identifier). @@ -349,6 +350,20 @@ public function referencedEntities(); public function getOriginalId(); /** + * Returns the cache tags that should be used when invalidating caches. + * + * This will not return additional cache tags added through addCacheTags(). + * Invalidating caches that are depending on this entity must use this method. + * + * @return string[] + * Set of cache tags. + * + * @see \Drupal\Core\Cache\MutableCacheableDependencyInterface::addCacheTags() + * @see \Drupal\Core\Cache\CacheableDependencyInterface::getCacheTags() + */ + public function getCacheTagsForInvalidation(); + + /** * Sets the original ID. * * @param int|string|null $id diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 1e1323e..42e475a 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -969,6 +969,7 @@ public function getTranslationFromContext(EntityInterface $entity, $langcode = N if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) { if (empty($langcode)) { $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); + $entity->addCacheContexts(['languages:' . LanguageInterface::TYPE_CONTENT]); } // Retrieve language fallback candidates to perform the entity language diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index 09736d2..8281eb9 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -117,14 +117,10 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N * {@inheritdoc} */ public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) { - if (!isset($langcode)) { - $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); - } - $build_list = array( '#sorted' => TRUE, '#pre_render' => array(array($this, 'buildMultiple')), - '#langcode' => $langcode, + '#langcode' => $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(), ); $weight = 0; foreach ($entities as $key => $entity) { @@ -133,9 +129,10 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la $entity = $this->entityManager->getTranslationFromContext($entity, $langcode); // Set build defaults. - $build_list[$key] = $this->getBuildDefaults($entity, $view_mode, $langcode); + $entity_langcode = $entity->language()->getId(); + $build_list[$key] = $this->getBuildDefaults($entity, $view_mode, $entity_langcode); $entityType = $this->entityTypeId; - $this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode, $langcode); + $this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode, $entity_langcode); $build_list[$key]['#weight'] = $weight++; } diff --git a/core/modules/aggregator/src/Entity/Item.php b/core/modules/aggregator/src/Entity/Item.php index 6986aaa..44f4c15 100644 --- a/core/modules/aggregator/src/Entity/Item.php +++ b/core/modules/aggregator/src/Entity/Item.php @@ -225,13 +225,13 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // handles the regular cases. The Item entity has one special case: a newly // created Item is *also* associated with a Feed, so we must invalidate the // associated Feed's cache tag. - Cache::invalidateTags($this->getCacheTags()); + Cache::invalidateTags($this->getCacheTagsForInvalidation()); } /** * {@inheritdoc} */ - public function getCacheTags() { + public function getCacheTagsForInvalidation() { return Feed::load($this->getFeedId())->getCacheTags(); } diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php index 948d293..869b544 100644 --- a/core/modules/block/src/Entity/Block.php +++ b/core/modules/block/src/Entity/Block.php @@ -246,7 +246,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // so we must invalidate the associated block's cache tag (which includes // the theme cache tag). if (!$update) { - Cache::invalidateTags($this->getCacheTags()); + Cache::invalidateTags($this->getCacheTagsForInvalidation()); } } diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php index ccb53e4..7d21775 100644 --- a/core/modules/block/src/Tests/BlockViewBuilderTest.php +++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php @@ -194,7 +194,7 @@ public function testBlockViewBuilderAlter() { // Enable the block view alter hook that adds a suffix, for basic testing. \Drupal::state()->set('block_test_view_alter_suffix', TRUE); - Cache::invalidateTags($this->block->getCacheTags()); + Cache::invalidateTags($this->block->getCacheTagsForInvalidation()); $build = $this->getBlockRenderArray(); $this->assertTrue(isset($build['#suffix']) && $build['#suffix'] === '
Goodbye!', 'A block with content is altered.'); $this->assertIdentical($this->renderer->renderRoot($build), 'Llamas > unicorns!
Goodbye!'); @@ -206,7 +206,7 @@ public function testBlockViewBuilderAlter() { $request->setMethod('GET'); \Drupal::state()->set('block_test.content', NULL); - Cache::invalidateTags($this->block->getCacheTags()); + Cache::invalidateTags($this->block->getCacheTagsForInvalidation()); $default_keys = array('entity_view', 'block', 'test_block'); $default_tags = array('block_view', 'config:block.block.test_block'); diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php index 6923220..66bb193 100644 --- a/core/modules/comment/src/CommentViewBuilder.php +++ b/core/modules/comment/src/CommentViewBuilder.php @@ -71,11 +71,9 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco ->getFieldDefinition($entity->getFieldName()) ->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED; // If threading is enabled, don't render cache individual comments, but do - // keep the cache tags, so they can bubble up. + // keep the cacheability metadata, so it can bubble up. if ($build['#comment_threaded']) { - $cache_tags = $build['#cache']['tags']; - $build['#cache'] = []; - $build['#cache']['tags'] = $cache_tags; + unset($build['#cache']['keys']); } return $build; diff --git a/core/modules/comment/src/Tests/CommentTranslationUITest.php b/core/modules/comment/src/Tests/CommentTranslationUITest.php index cfb63c8..dc1bf9e 100644 --- a/core/modules/comment/src/Tests/CommentTranslationUITest.php +++ b/core/modules/comment/src/Tests/CommentTranslationUITest.php @@ -33,6 +33,18 @@ class CommentTranslationUITest extends ContentTranslationUITestBase { protected $adminUser; /** + * {inheritdoc} + */ + protected $defaultCacheContexts = [ + 'languages:language_interface', + 'theme', + 'user.permissions', + 'timezone', + 'url.query_args.pagers:0', + 'user.roles' + ]; + + /** * Modules to install. * * @var array diff --git a/core/modules/config/src/Tests/CacheContextConfigOverrideTest.php b/core/modules/config/src/Tests/CacheContextConfigOverrideTest.php new file mode 100644 index 0000000..4782a51 --- /dev/null +++ b/core/modules/config/src/Tests/CacheContextConfigOverrideTest.php @@ -0,0 +1,73 @@ +installEntitySchema('block_content'); + $this->installConfig(['config_override_test']); + } + + 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 cacheable properties are 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()); + + // Load the User login block and check that its cacheability metadata is + // overridden correctly. This ensures 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 properties are 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..8dad5bb 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\Cache; use Drupal\Core\Config\ConfigFactoryOverrideInterface; use Drupal\Core\Config\StorageInterface; @@ -40,4 +41,25 @@ public function createConfigObject($name, $collection = StorageInterface::DEFAUL return NULL; } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + } 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.services.yml b/core/modules/config/tests/config_override_test/config_override_test.services.yml index c3fae64..886b7fd 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_cache_context_overrider: + class: Drupal\config_override_test\PirateDayCacheContextConfigOverride + 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..80e6000 --- /dev/null +++ b/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php @@ -0,0 +1,52 @@ + ['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 getCacheContexts() { + return ['pirate_day']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return ['pirate-day-tag']; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return PirateDayCacheContext::PIRATE_DAY_MAX_AGE; + } + +} diff --git a/core/modules/contact/src/Tests/Views/ContactLinkTest.php b/core/modules/contact/src/Tests/Views/ContactLinkTest.php index 4c82d18..a4db76e 100644 --- a/core/modules/contact/src/Tests/Views/ContactLinkTest.php +++ b/core/modules/contact/src/Tests/Views/ContactLinkTest.php @@ -86,7 +86,7 @@ public function testContactLink() { // Disable contact link for no_contact. $this->userData->set('contact', $no_contact_account->id(), 'enabled', FALSE); // @todo Remove cache invalidation in https://www.drupal.org/node/2477903. - Cache::invalidateTags($no_contact_account->getCacheTags()); + Cache::invalidateTags($no_contact_account->getCacheTagsForInvalidation()); $this->drupalGet('test-contact-link'); $this->assertContactLinks($accounts, array('root', 'admin')); } diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php index e524359..5faa85c 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php @@ -7,18 +7,22 @@ namespace Drupal\content_translation\Tests; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Url; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Component\Utility\SafeMarkup; +use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; /** * Tests the Content Translation UI. */ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase { + use AssertPageCacheContextsAndTagsTrait; + /** * The id of the entity being translated. * @@ -34,6 +38,15 @@ protected $testLanguageSelector = TRUE; /** + * Default cache contexts expected on a non-translated entity. + * + * Cache contexts will not be checked if this list is empty. + * + * @var string[] + */ + protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user.permissions']; + + /** * Tests the basic translation UI. */ function testTranslationUI() { @@ -64,6 +77,11 @@ protected function doTestBasicTranslation() { $this->assertTrue($entity, 'Entity found in the database.'); $this->drupalGet($entity->urlInfo()); $this->assertResponse(200, 'Entity URL is valid.'); + + // Ensure that the content language cache context is not yet added to the + // page. + $this->assertCacheContexts($this->defaultCacheContexts); + $this->drupalGet($entity->urlInfo('drupal:content-translation-overview')); $this->assertNoText('Source language', 'Source language column correctly hidden.'); @@ -87,9 +105,14 @@ protected function doTestBasicTranslation() { ], array('language' => $language)); $this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode)); - // Get the entity and reset its cache, so that the new translation gets the - // updated values. + // Ensure that the content language cache context is not yet added to the + // page. $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); + $this->drupalGet($entity->urlInfo()); + $this->assertCacheContexts(Cache::mergeContexts(['languages:language_content'], $this->defaultCacheContexts)); + + // Reset the cache of the entity, so that the new translation gets the + // updated values. $metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode)); $metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode)); diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index a05d3a8..1ed79b6 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -267,7 +267,7 @@ public function flush($path = NULL) { // Clear caches so that formatters may be added for this style. drupal_theme_rebuild(); - Cache::invalidateTags($this->getCacheTags()); + Cache::invalidateTags($this->getCacheTagsForInvalidation()); return $this; } diff --git a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php index 3d0a67b..e2e8c3b 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\Cache; use Drupal\Core\Config\ConfigCollectionInfo; use Drupal\Core\Config\ConfigCrudEvent; use Drupal\Core\Config\ConfigFactoryOverrideBase; @@ -222,4 +223,25 @@ public function onConfigDelete(ConfigCrudEvent $event) { } } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['languages:language_interface']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + } diff --git a/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php b/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php index b068808..615da6b 100644 --- a/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php +++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php @@ -18,6 +18,11 @@ class MenuLinkContentTranslationUITest extends ContentTranslationUITestBase { /** + * {inheritdoc} + */ + protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user.permissions', 'user.roles:authenticated']; + + /** * Modules to enable. * * @var array diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php index 287920b..c392750 100644 --- a/core/modules/node/src/Tests/NodeTranslationUITest.php +++ b/core/modules/node/src/Tests/NodeTranslationUITest.php @@ -22,6 +22,21 @@ class NodeTranslationUITest extends ContentTranslationUITestBase { /** + * {inheritdoc} + */ + protected $defaultCacheContexts = [ + 'languages:language_interface', + 'theme', + 'user.permissions', + 'route.menu_active_trails:account', + 'route.menu_active_trails:footer', + 'route.menu_active_trails:main', + 'route.menu_active_trails:tools', + 'timezone', + 'user.roles' + ]; + + /** * Modules to enable. * * @var array diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php index a2179f9..29a18f4 100644 --- a/core/modules/shortcut/src/Entity/Shortcut.php +++ b/core/modules/shortcut/src/Entity/Shortcut.php @@ -102,7 +102,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // newly created shortcut is *also* added to a shortcut set, so we must // invalidate the associated shortcut set's cache tag. if (!$update) { - Cache::invalidateTags($this->getCacheTags()); + Cache::invalidateTags($this->getCacheTagsForInvalidation()); } } @@ -178,7 +178,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { /** * {@inheritdoc} */ - public function getCacheTags() { + public function getCacheTagsForInvalidation() { return $this->shortcut_set->entity->getCacheTags(); } diff --git a/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php index 3fc8232..e324d80 100644 --- a/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php +++ b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php @@ -19,6 +19,11 @@ class ShortcutTranslationUITest extends ContentTranslationUITestBase { /** + * {inheritdoc} + */ + protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user']; + + /** * Modules to enable. * * @var array diff --git a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php index 0b365cb..6472cb0 100644 --- a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php @@ -571,7 +571,7 @@ public function testReferencedEntity() { // a cache miss for every route except the ones for the non-referencing // entity and the empty entity listing. $this->pass("Test invalidation of referenced entity's cache tag.", 'Debug'); - Cache::invalidateTags($this->entity->getCacheTags()); + Cache::invalidateTags($this->entity->getCacheTagsForInvalidation()); $this->verifyPageCache($referencing_entity_url, 'MISS'); $this->verifyPageCache($listing_url, 'MISS'); $this->verifyPageCache($nonempty_entity_listing_url, 'MISS'); diff --git a/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php index 0b6f430..5ef3660 100644 --- a/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php @@ -121,7 +121,7 @@ public function testEntityUri() { // Verify that after invalidating the entity's cache tag directly, there is // a cache miss. $this->pass("Test invalidation of entity's cache tag.", 'Debug'); - Cache::invalidateTags($this->entity->getCacheTags()); + Cache::invalidateTags($this->entity->getCacheTagsForInvalidation()); $this->verifyPageCache($entity_url, 'MISS'); // Verify a cache hit. diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php index 06a18e3..5ba6bcd 100644 --- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php @@ -153,7 +153,7 @@ public function cacheGet($type) { * Clear out cached data for a view. */ public function cacheFlush() { - Cache::invalidateTags($this->view->storage->getCacheTags()); + Cache::invalidateTags($this->view->storage->getCacheTagsForInvalidation()); } /** diff --git a/core/modules/views/src/Tests/Plugin/CacheTagTest.php b/core/modules/views/src/Tests/Plugin/CacheTagTest.php index 3d497d5..309a856 100644 --- a/core/modules/views/src/Tests/Plugin/CacheTagTest.php +++ b/core/modules/views/src/Tests/Plugin/CacheTagTest.php @@ -197,7 +197,7 @@ public function testTagCaching() { $view->destroy(); // Invalidate the views cache tags in order to invalidate the render // caching. - \Drupal::service('cache_tags.invalidator')->invalidateTags($view->storage->getCacheTags()); + \Drupal::service('cache_tags.invalidator')->invalidateTags($view->storage->getCacheTagsForInvalidation()); $build = $view->buildRenderable(); $renderer->renderPlain($build); diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 05451be..b7d3aa5 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -1336,4 +1336,32 @@ public function hasTrustedData() { return $this->storage->hasTrustedData(); } + /** + * {@inheritdoc} + */ + public function addCacheContexts(array $cache_contexts) { + return $this->storage->addCacheContexts($cache_contexts); + } + + /** + * {@inheritdoc} + */ + public function setCacheMaxAgeIfLower($max_age) { + return $this->storage->setCacheMaxAgeIfLower($max_age); + } + + /** + * {@inheritdoc} + */ + public function getCacheTagsForInvalidation() { + return $this->storage->getCacheTagsForInvalidation(); + } + + /** + * {@inheritdoc} + */ + public function addCacheTags(array $cache_tags) { + return $this->storage->addCacheTags($cache_tags); + } + }