diff --git a/core/includes/theme.inc b/core/includes/theme.inc index c63f6790ef9488738333fe655b999ded899a87ee..ed5bd5846b5d0738e3a776c03367a3c23ccba89c 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1925,6 +1925,22 @@ function drupal_pre_render_html(array $element) { return $element; } + +/** + * #pre_render callback for the page element type. + * + * @param array $element + * A structured array containing the page element type build properties. + * + * @see system_element_info() + */ +function drupal_pre_render_page(array $element) { + global $theme; + $element['#cache']['tags']['theme'] = $theme; + $element['#cache']['tags']['theme_global_settings'] = TRUE; + return $element; +} + /** * Prepares variables for HTML document templates. * diff --git a/core/lib/Drupal/Core/Cache/LanguageCacheContext.php b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php index b73652f8f76b6da907edf68dc6ac56961040f8f4..e2c1bd93bc277a80844e67f890802d9e2e5f3785 100644 --- a/core/lib/Drupal/Core/Cache/LanguageCacheContext.php +++ b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php @@ -43,9 +43,9 @@ public static function getLabel() { */ public function getContext() { $context_parts = array(); - if ($this->language_manager->isMultilingual()) { - foreach ($this->language_manager->getLanguageTypes() as $type) { - $context_parts[] = $this->language_manager->getCurrentLanguage($type)->id; + if ($this->languageManager->isMultilingual()) { + foreach ($this->languageManager->getLanguageTypes() as $type) { + $context_parts[] = $this->languageManager->getCurrentLanguage($type)->id; } } return implode(':', $context_parts); diff --git a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php index a6a925afb6c69a6f98d6969332cf73bf08071ed9..cb448515352d9a4905a6c60613e10ef0cdff363b 100644 --- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php +++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php @@ -74,6 +74,7 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la 'content' => TRUE, 'block_view' => TRUE, 'block' => array($entity->id()), + 'theme' => $entity->get('theme'), ); $build[$entity_id]['#cache']['tags'] = NestedArray::mergeDeep($default_cache_tags, $plugin->getCacheTags()); diff --git a/core/modules/block/lib/Drupal/block/Entity/Block.php b/core/modules/block/lib/Drupal/block/Entity/Block.php index 9e171711c8dc13dd0ffa394fd07156cd889b0fce..aa9141c11c9a14f8f5e3a5ac5b062516c7a4342d 100644 --- a/core/modules/block/lib/Drupal/block/Entity/Block.php +++ b/core/modules/block/lib/Drupal/block/Entity/Block.php @@ -160,8 +160,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // When placing a new block, invalidate all cache entries for this theme, // since any page that uses this theme might be affected. else { - // @todo Replace with theme cache tag: https://drupal.org/node/2185617 - Cache::invalidateTags(array('content' => TRUE)); + Cache::invalidateTags(array('theme' => $this->theme)); } } diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php index 4fa4e63a43dace09c619d5eaa62f6d5c9a637afb..37074ccb619a14308cbefe3bef15e4d2b4210b30 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php @@ -281,6 +281,8 @@ public function testBlockCacheTags() { $cid = sha1(implode(':', $cid_parts)); $cache_entry = \Drupal::cache('render')->get($cid); $expected_cache_tags = array( + 'theme:stark', + 'theme_global_settings:1', 'content:1', 'block_view:1', 'block:powered', @@ -288,6 +290,13 @@ public function testBlockCacheTags() { ); $this->assertIdentical($cache_entry->tags, $expected_cache_tags); $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:stark'); + $expected_cache_tags = array( + 'content:1', + 'block_view:1', + 'block:powered', + 'theme:stark', + 'block_plugin:system_powered_by_block', + ); $this->assertIdentical($cache_entry->tags, $expected_cache_tags); // The "Powered by Drupal" block is modified; verify a cache miss. @@ -312,6 +321,8 @@ public function testBlockCacheTags() { $cid = sha1(implode(':', $cid_parts)); $cache_entry = \Drupal::cache('render')->get($cid); $expected_cache_tags = array( + 'theme:stark', + 'theme_global_settings:1', 'content:1', 'block_view:1', 'block:powered-2', @@ -323,6 +334,7 @@ public function testBlockCacheTags() { 'content:1', 'block_view:1', 'block:powered', + 'theme:stark', 'block_plugin:system_powered_by_block', ); $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:stark'); @@ -331,6 +343,7 @@ public function testBlockCacheTags() { 'content:1', 'block_view:1', 'block:powered-2', + 'theme:stark', 'block_plugin:system_powered_by_block', ); $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered-2:en:stark'); diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php index 63bbf45cb792f82b2408baa5e8abe01a1231783d..d4066b097d4a69f916b1bb5849aec975a14938e3 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php @@ -221,7 +221,7 @@ public function testBlockViewBuilderAlter() { $this->container->get('request')->setMethod('GET'); $default_keys = array('entity_view', 'block', 'test_block', 'en', 'cache_context.theme'); - $default_tags = array('content' => TRUE, 'block_view' => TRUE, 'block' => array('test_block'), 'block_plugin' => array('test_cache')); + $default_tags = array('content' => TRUE, 'block_view' => TRUE, 'block' => array('test_block'), 'theme' => 'stark', 'block_plugin' => array('test_cache')); // Advanced: cached block, but an alter hook adds an additional cache key. $this->setBlockCacheConfig(array( @@ -236,7 +236,7 @@ public function testBlockViewBuilderAlter() { $this->assertIdentical(drupal_render($build), ''); $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); - $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'block_plugin:test_cache'); + $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache'); $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.'); $this->container->get('cache.render')->delete($cid); @@ -250,7 +250,7 @@ public function testBlockViewBuilderAlter() { $this->assertIdentical(drupal_render($build), ''); $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); - $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'block_plugin:test_cache', $alter_add_tag . ':1'); + $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', $alter_add_tag . ':1'); $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.'); $this->container->get('cache.render')->delete($cid); diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php index 089c2ada55755d532ff7c1a53fc79efd1eb6a775..fec45d043bfbf07db71b2bd80df558f31d62f0ee 100644 --- a/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php +++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php @@ -59,6 +59,8 @@ public function testMenuBlock() { // Verify a cache hit, but also the presence of the correct cache tags. $expected_tags = array( + 'theme:stark', + 'theme_global_settings:1', 'content:1', 'block_view:1', 'block:' . $block->id(), @@ -117,7 +119,7 @@ public function testMenuBlock() { $this->verifyPageCache($path, 'MISS'); // Verify a cache hit. - $this->verifyPageCache($path, 'HIT', array('content:1')); + $this->verifyPageCache($path, 'HIT', array('content:1', 'theme:stark', 'theme_global_settings:1')); } } diff --git a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php index 2cce3b8a940b97a4f2c35788ea67a764b4ccf087..9c605e9bd318c26a78fa1e5d3533ba0781874b47 100644 --- a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php +++ b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php @@ -419,6 +419,16 @@ public function submitForm(array &$form, array &$form_state) { theme_settings_convert_to_config($values, $config)->save(); + // Invalidate either the theme-specific cache tag or the global theme + // settings cache tag, depending on whose settings were actually changed. + if (isset($values['theme'])) { + Cache::invalidateTags(array('theme' => $values['theme'])); + } + else { + Cache::invalidateTags(array('theme_global_settings' => TRUE)); + } + + // @todo Remove this in https://drupal.org/node/2124957. Cache::invalidateTags(array('content' => TRUE)); } diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php index 0b3213902711aff88a9b43c362ee13b8f6038a92..e9555b79b1449c9b5196f94a22e4921d9f207fe8 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php +++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php @@ -7,6 +7,7 @@ namespace Drupal\system\Plugin\Block; +use Drupal\Component\Utility\NestedArray; use Drupal\block\BlockBase; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -44,7 +45,7 @@ class SystemBrandingBlock extends BlockBase implements ContainerFactoryPluginInt * * @var \Drupal\Core\Session\AccountInterface */ - protected $current_user; + protected $currentUser; /** * Creates a SystemBrandingBlock instance. @@ -92,6 +93,12 @@ public function defaultConfiguration() { 'use_site_name' => TRUE, 'use_site_slogan' => TRUE, 'label_display' => FALSE, + // Modify the default max age for the 'Site branding' block: the site + // logo, name and slogan are static for a given language, except when the + // theme settings are updated (global theme settings or theme-specific + // settings). Cache tags for those cases ensure that a cached version of + // this block is invalidated automatically. + 'cache' => array('max_age' => \Drupal\Core\Cache\Cache::PERMANENT), ); } @@ -108,12 +115,12 @@ public function blockForm($form, &$form_state) { if ($administer_themes_access) { // Get paths to theme settings pages. - $appearance_url = $this->urlGenerator->generateFromRoute('system.themes_page'); + $appearance_settings_url = $this->urlGenerator->generateFromRoute('system.theme_settings'); $theme_settings_url = $this->urlGenerator->generateFromRoute('system.theme_settings_theme', array('theme' => $theme)); - // Provide links to the Appearance and Theme Settings pages + // Provide links to the Appearance Settings and Theme Settings pages // if the user has access to administer themes. - $site_logo_description = $this->t('Defined on the <a href="@appearance">Appearance</a> or <a href="@theme">Theme Settings</a> page.', array('@appearance' => $appearance_url, '@theme' => $theme_settings_url)); + $site_logo_description = $this->t('Defined on the <a href="@appearance">Appearance Settings</a> or <a href="@theme">Theme Settings</a> page.', array('@appearance' => $appearance_settings_url, '@theme' => $theme_settings_url)); } else { // Explain that the user does not have access to the Appearance and Theme @@ -200,4 +207,29 @@ public function build() { return $build; } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + // The theme-specific cache tag is set automatically for each block, but the + // output of this block also depends on the global theme settings. + $tags = array( + 'theme_global_setting' => TRUE, + ); + return NestedArray::mergeDeep(parent::getCacheTags(), $tags); + } + + /** + * {@inheritdoc} + */ + protected function getRequiredCacheContexts() { + // The 'Site branding' block must be cached per theme and per language: the + // site logo, name and slogan are defined on a per-theme basis, and the name + // and slogan may be translated. + // We don't need to return 'cache_context.theme' also, because that cache + // context is automatically applied to all blocks. + // @see \Drupal\block\BlockViewBuilder::viewMultiple() + return array('cache_context.language'); + } + } diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php index 029bcb81a7668420d3c3c49ee31908db22091f52..7f6b6a0fe0848b80dd83dcd4d8962c5c8e0e235f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php @@ -66,7 +66,14 @@ function testPageCacheTags() { $cid = sha1(implode(':', $cid_parts)); $cache_entry = \Drupal::cache('render')->get($cid); sort($cache_entry->tags); - $this->assertIdentical($cache_entry->tags, array('content:1', 'pre_render:1', 'system_test_cache_tags_page:1')); + $expected_tags = array( + 'content:1', + 'pre_render:1', + 'system_test_cache_tags_page:1', + 'theme:stark', + 'theme_global_settings:1', + ); + $this->assertIdentical($cache_entry->tags, $expected_tags); Cache::invalidateTags($tags); $this->drupalGet($path); diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php index 7e9d1d577d5060a58fe7e6177872686073de6915..f9f76ac291b94c49e496478f2d90aa287f1d6df0 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php @@ -77,6 +77,8 @@ function testPageCacheTags() { // Full node page 1. $this->verifyPageCacheTags('node/' . $node_1->id(), array( 'content:1', + 'theme:bartik', + 'theme_global_settings:1', 'block_view:1', 'block:bartik_content', 'block:bartik_tools', @@ -100,6 +102,8 @@ function testPageCacheTags() { // Full node page 2. $this->verifyPageCacheTags('node/' . $node_2->id(), array( 'content:1', + 'theme:bartik', + 'theme_global_settings:1', 'block_view:1', 'block:bartik_content', 'block:bartik_tools', diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php index e1f66b5e3e725f369a86cea4d8ce706deb80f02d..5112cc2a1ae349a59893a41a6b0820065f5b0217 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php @@ -210,6 +210,8 @@ public function testReferencedEntity() { $non_referencing_entity_path = $this->non_referencing_entity->getSystemPath(); $listing_path = 'entity_test/list/' . $entity_type . '_reference/' . $entity_type . '/' . $this->entity->id(); + $theme_cache_tags = array('content:1', 'theme:stark', 'theme_global_settings:1'); + // Generate the standardized entity cache tags. $cache_tag = $entity_type . ':' . $this->entity->id(); $view_cache_tag = $entity_type . '_view:1'; @@ -232,7 +234,7 @@ public function testReferencedEntity() { $this->verifyPageCache($referencing_entity_path, 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags. - $tags = array_merge(array('content:1'), $referencing_entity_cache_tags); + $tags = array_merge($theme_cache_tags, $referencing_entity_cache_tags); $this->verifyPageCache($referencing_entity_path, 'HIT', $tags); // Also verify the existence of an entity render cache entry. @@ -245,7 +247,7 @@ public function testReferencedEntity() { $this->verifyPageCache($non_referencing_entity_path, 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags. - $tags = array_merge(array('content:1'), $non_referencing_entity_cache_tags); + $tags = array_merge($theme_cache_tags, $non_referencing_entity_cache_tags); $this->verifyPageCache($non_referencing_entity_path, 'HIT', $tags); // Also verify the existence of an entity render cache entry. @@ -259,7 +261,7 @@ public function testReferencedEntity() { $this->verifyPageCache($listing_path, 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags. - $tags = array_merge(array('content:1'), $referencing_entity_cache_tags); + $tags = array_merge($theme_cache_tags, $referencing_entity_cache_tags); $this->verifyPageCache($listing_path, 'HIT', $tags); @@ -407,13 +409,12 @@ public function testReferencedEntity() { $this->verifyPageCache($non_referencing_entity_path, 'HIT'); // Verify cache hits. - $tags = array( - 'content:1', + $tags = array_merge($theme_cache_tags, array( 'entity_test_view:1', 'entity_test:' . $this->referencing_entity->id(), - ); + )); $this->verifyPageCache($referencing_entity_path, 'HIT', $tags); - $this->verifyPageCache($listing_path, 'HIT', array('content:1')); + $this->verifyPageCache($listing_path, 'HIT', $theme_cache_tags); } } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index a870cb3ab99d54c94fe904c16cdd3129a19f3617..4b6a1654751d5109deb13a05f6fe1c8b5b3b2da2 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -290,6 +290,7 @@ function system_element_info() { ); $types['page'] = array( '#show_messages' => TRUE, + '#pre_render' => array('drupal_pre_render_page'), '#theme' => 'page', '#title' => '', );