core/lib/Drupal/Core/Cache/CacheableHelper.php | 2 +- core/modules/block/block.module | 70 ++----------- core/modules/block/lib/Drupal/block/BlockBase.php | 30 +++--- .../lib/Drupal/block/BlockPluginInterface.php | 3 +- .../block/lib/Drupal/block/BlockViewBuilder.php | 77 +++++++++++--- .../block/lib/Drupal/block/Entity/Block.php | 27 +++++ .../lib/Drupal/block/Tests/BlockInterfaceTest.php | 11 ++ .../Drupal/block/Tests/BlockRenderOrderTest.php | 7 +- .../Drupal/block/Tests/BlockStorageUnitTest.php | 3 + .../block/lib/Drupal/block/Tests/BlockTest.php | 105 ++++++++++++++++++++ .../Drupal/block/Tests/Views/DisplayBlockTest.php | 19 +--- .../block_test/Plugin/Block/TestCacheBlock.php | 7 +- .../book/Plugin/Block/BookNavigationBlock.php | 11 +- .../forum/Plugin/Block/ActiveTopicsBlock.php | 2 +- .../Drupal/forum/Plugin/Block/ForumBlockBase.php | 22 ++-- .../Drupal/forum/Plugin/Block/NewTopicsBlock.php | 2 +- core/modules/menu/menu.module | 2 +- .../lib/Drupal/simpletest/WebTestBase.php | 4 + .../Drupal/system/Plugin/Block/SystemHelpBlock.php | 20 ++++ .../Drupal/system/Plugin/Block/SystemMainBlock.php | 20 ++++ .../Drupal/system/Plugin/Block/SystemMenuBlock.php | 8 +- core/modules/system/system.module | 24 +++++ .../lib/Drupal/views/Plugin/Block/ViewsBlock.php | 26 +++-- .../standard/config/block.block.bartik_powered.yml | 2 + 24 files changed, 367 insertions(+), 137 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/CacheableHelper.php b/core/lib/Drupal/Core/Cache/CacheableHelper.php index 0ff3fd4..be1f402 100644 --- a/core/lib/Drupal/Core/Cache/CacheableHelper.php +++ b/core/lib/Drupal/Core/Cache/CacheableHelper.php @@ -9,7 +9,7 @@ /** * A helper class that contains methods that could be useful to any object using - * the Drupal cache API. + * the Drupal Cache API. * * @todo Consider converting to a trait once traits are available. */ diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 282fbf9..f9d54cd 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -193,44 +193,13 @@ function block_page_build(&$page) { function block_get_blocks_by_region($region) { $build = array(); if ($list = block_list($region)) { - $build = _block_get_renderable_region($list); - } - return $build; -} - -/** - * Gets an array of blocks suitable for drupal_render(). - * - * @param $list - * A list of blocks such as that returned by block_list(). - * - * @return - * A renderable array. - */ -function _block_get_renderable_region($list = array()) { - $build = array(); - - foreach ($list as $key => $block) { - if ($block->access()) { - $build[$key] = entity_view($block, 'block'); - } - - // Add contextual links for this block; skip the main content block, since - // contextual links are basically output as tabs/local tasks already. Also - // skip the help block, since we assume that most users do not need or want - // to perform contextual actions on the help block, and the links needlessly - // draw attention on it. - if (isset($build[$key]) && !in_array($block->get('plugin'), array('system_help_block', 'system_main_block'))) { - $build[$key]['#contextual_links']['block'] = array( - 'route_parameters' => array('block' => $key), - ); - - // If there are any nested contextual links, move them to the top level. - if (isset($build[$key]['content']['#contextual_links'])) { - $build[$key]['#contextual_links'] += $build[$key]['content']['#contextual_links']; - unset($build[$key]['content']['#contextual_links']); + foreach ($list as $key => $block) { + if ($block->access()) { + $build[$key] = entity_view($block, 'block'); } } + // block_list() already returned the blocks in sorted order. + $build['#sorted'] = TRUE; } return $build; } @@ -357,9 +326,7 @@ function block_list($region) { $blocks[$region] = array(); } - uasort($blocks[$region], function($first, $second) { - return $first->weight === $second->weight ? 0 : ($first->weight < $second->weight ? -1 : 1); - }); + uasort($blocks[$region], 'Drupal\block\Entity\Block::sort'); return $blocks[$region]; } @@ -380,26 +347,6 @@ function block_load($entity_id) { } /** - * Builds the content and label for a block. - * - * For cacheable blocks, this is called during #pre_render. - * - * @param $element - * A renderable array. - * - * @return - * A renderable array. - */ -function _block_get_renderable_block($element) { - $block = $element['#block']; - // Don't bother to build blocks that aren't accessible. - if ($element['#access'] = $block->access()) { - $element += entity_view($block, 'block'); - } - return $element; -} - -/** * Implements hook_rebuild(). */ function block_rebuild() { @@ -471,6 +418,11 @@ function template_preprocess_block(&$variables) { $variables['derivative_plugin_id'] = $variables['elements']['#derivative_plugin_id']; $variables['label'] = !empty($variables['configuration']['label_display']) ? $variables['configuration']['label'] : ''; $variables['content'] = $variables['elements']['content']; + // A block's label is configuration: it is static. Allow dynamic labels to be + // set in the render array. + if (isset($variables['elements']['content']['#block_label']) && !empty($variables['configuration']['label_display'])) { + $variables['label'] = $variables['elements']['content']['#block_label']; + } $variables['attributes']['class'][] = 'block'; $variables['attributes']['class'][] = drupal_html_class('block-' . $variables['configuration']['module']); diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php index aedb7f0..17c9d49 100644 --- a/core/modules/block/lib/Drupal/block/BlockBase.php +++ b/core/modules/block/lib/Drupal/block/BlockBase.php @@ -21,8 +21,7 @@ * block settings, and handling for general user-defined block visibility * settings. */ -abstract class BlockBase extends PluginBase implements BlockPluginInterface, - CacheableInterface { +abstract class BlockBase extends PluginBase implements BlockPluginInterface { /** * {@inheritdoc} @@ -112,13 +111,13 @@ public function buildConfigurationForm(array $form, array &$form_state) { '#default_value' => ($this->configuration['label_display'] === BlockInterface::BLOCK_LABEL_VISIBLE), '#return_value' => BlockInterface::BLOCK_LABEL_VISIBLE, ); - $form['cache']['max_age'] = array( '#type' => 'select', '#title' => t('Cache: Max age'), '#default_value' => $this->configuration['cache']['max_age'], - '#options' => drupal_map_assoc(array(0, 60, 300, 1800, 3600, 21600, 518400), 'format_interval'), + '#options' => drupal_map_assoc(array(0, 60, 300, 1800, 3600, 21600, 86400, 604800, 2592000, 31536000, 315360000), 'format_interval'), ); + // Add plugin-specific settings for this block type. $form += $this->blockForm($form, $form_state); return $form; @@ -197,24 +196,21 @@ public function getMachineNameSuggestion() { } /** - * Returns an underscore'd version of the class name. Override this method to - * customize. - * * {@inheritdoc} */ public function getCacheKeys() { - return array(str_replace('\\', '_', get_called_class())); + return array(); } - /** - * Uses the "content" tag by default. Implementations are encouraged to - * overide this method to provide more granular tags. - * * {@inheritdoc} */ public function getCacheTags() { - return array('content' => TRUE); + // If a block plugin's output changes, then it must be able to invalidate a + // cache tag that affects all instances of this block: across themes and + // across regions. + $block_plugin_cache_tag = str_replace(':', '__', $this->getPluginID()); + return array('block_plugin' => array($block_plugin_cache_tag)); } /** @@ -232,15 +228,13 @@ public function getCacheMaxAge() { } /** - * Similar to the page cache, a block is cacheable if it has a max age. Blocks - * that should never be cached can override this method to simply return - * false. - * * {@inheritdoc} */ public function isCacheable() { + // Similar to the page cache, a block is cacheable if it has a max age. + // Blocks that should never be cached can override this method to simply + // return FALSE. return (int)$this->configuration['cache']['max_age'] > 0; } - } diff --git a/core/modules/block/lib/Drupal/block/BlockPluginInterface.php b/core/modules/block/lib/Drupal/block/BlockPluginInterface.php index 047efd9..4f6bdc2 100644 --- a/core/modules/block/lib/Drupal/block/BlockPluginInterface.php +++ b/core/modules/block/lib/Drupal/block/BlockPluginInterface.php @@ -7,6 +7,7 @@ namespace Drupal\block; +use Drupal\Core\Cache\CacheableInterface; use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Component\Plugin\ConfigurablePluginInterface; use Drupal\Core\Plugin\PluginFormInterface; @@ -20,7 +21,7 @@ * brif references to the important components that are not coupled to the * interface. */ -interface BlockPluginInterface extends ConfigurablePluginInterface, PluginFormInterface, PluginInspectionInterface { +interface BlockPluginInterface extends ConfigurablePluginInterface, PluginFormInterface, PluginInspectionInterface, CacheableInterface { /** * Indicates whether the block should be shown. diff --git a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php index d17504c..16572a9 100644 --- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php +++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php @@ -7,6 +7,8 @@ namespace Drupal\block; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityViewBuilder; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Entity\EntityInterface; @@ -50,36 +52,87 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la '#plugin_id' => $plugin_id, '#base_plugin_id' => $base_id, '#derivative_plugin_id' => $derivative_id, + // All blocks get a "Configure block" contextual link. + '#contextual_links' => array( + 'block' => array( + 'route_parameters' => array('block' => $entity->id()), + ), + ), + // @todo Remove after fixing http://drupal.org/node/1989568. + '#block' => $entity, ); if ($plugin->isCacheable()) { + // Generic cache keys, with the block plugin's custom keys appended + // (usually cache context keys like DRUPAL_CACHE_PER_ROLE). + $default_cache_keys = array('entity_view', 'block', $entity->id(), $entity->langcode); + $default_cache_tags = array( + 'content' => TRUE, + 'block_view' => TRUE, + 'block' => array($entity->id()), + ); $build[$entity_id] += array( - 'content' => array( - '#pre_render' => array(array($plugin, 'build')), + '#pre_render' => array( + array($this, 'buildBlock'), ), '#cache' => array( - 'keys' => $plugin->cacheKeys(), - 'bin' => $plugin->cacheBin(), - 'tags' => $plugin->cacheTags(), - 'expire' => REQUEST_TIME + $plugin->cacheMaxAge(), + 'keys' => $default_cache_keys + $plugin->getCacheKeys(), + 'bin' => $plugin->getCacheBin(), + 'tags' => NestedArray::mergeDeep($default_cache_tags, $plugin->getCacheTags()), + 'expire' => REQUEST_TIME + $plugin->getCacheMaxAge(), ), ); } else { - $build[$entity_id]['content'] = $plugin->build(); + $build[$entity_id] = $this->buildBlock($build[$entity_id]); } + } + return $build; + } - $this->moduleHandler()->alter(array('block_view', "block_view_$base_id"), $build[$entity_id], $plugin); + /** + * #pre_render callback for building a block. + * + * Renders the content using the provided block plugin, and then: + * - if there is no content, aborts rendering, and makes sure the block won't + * be rendered. + * - if there is content, moves the contextual links from the block content to + * the block itself, and invokes the alter hooks. + */ + public function buildBlock($build) { + $plugin = $build['#block']->getPlugin(); + $content = $plugin->build(); - // @todo Remove after fixing http://drupal.org/node/1989568. - $build[$entity_id]['#block'] = $entity; + if (!empty($content)) { + $build['content'] = $content; + + // If there are any nested contextual links, move them to the top level. + if (isset($build['content']['#contextual_links'])) { + $build['#contextual_links'] += $build['content']['#contextual_links']; + unset($build['content']['#contextual_links']); + } + + $base_id = $plugin->getBasePluginId(); + $this->moduleHandler()->alter(array('block_view', "block_view_$base_id"), $build, $plugin); + + return $build; + } + else { + // Abort rendering. + return array('#access' => FALSE, '#printed' => TRUE); } - return $build; } /** * {@inheritdoc} */ - public function resetCache(array $ids = NULL) { } + public function resetCache(array $entities = NULL) { + if (isset($entities)) { + Cache::invalidateTags(array('block' => array_keys($entities))); + } + else { + Cache::invalidateTags(array('block_view' => TRUE)); + } + } } diff --git a/core/modules/block/lib/Drupal/block/Entity/Block.php b/core/modules/block/lib/Drupal/block/Entity/Block.php index fe82d44..507bed8 100644 --- a/core/modules/block/lib/Drupal/block/Entity/Block.php +++ b/core/modules/block/lib/Drupal/block/Entity/Block.php @@ -7,6 +7,7 @@ namespace Drupal\block\Entity; +use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\block\BlockPluginBag; use Drupal\block\BlockInterface; @@ -158,6 +159,32 @@ public function preSave(EntityStorageControllerInterface $storage_controller) { } /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + parent::postSave($storage_controller, $update); + + if ($update) { + Cache::invalidateTags(array('block' => $this->id())); + } + // 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)); + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + parent::postDelete($storage_controller, $entities); + + Cache::invalidateTags(array('block' => array_keys($entities))); + } + + /** * Sorts active blocks by weight; sorts inactive blocks by name. */ public static function sort($a, $b) { diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php index cc1abd6..e8b0f85 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php @@ -48,6 +48,9 @@ public function testBlockInterface() { 'display_message' => 'no message set', 'module' => 'block_test', 'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE, + 'cache' => array( + 'max_age' => 0, + ), ); // Initial configuration of the block at construction time. $display_block = $manager->createInstance('test_block_instantiation', $configuration); @@ -82,6 +85,14 @@ public function testBlockInterface() { '#default_value' => TRUE, '#return_value' => 'visible', ), + 'cache' => array( + 'max_age' => array( + '#type' => 'select', + '#title' => t('Cache: Max age'), + '#default_value' => 0, + '#options' => drupal_map_assoc(array(0, 60, 300, 1800, 3600, 21600, 86400, 604800, 2592000, 31536000, 315360000), 'format_interval'), + ), + ), 'display_message' => array( '#type' => 'textfield', '#title' => t('Display message'), diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockRenderOrderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockRenderOrderTest.php index da6202e..dbbd614 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockRenderOrderTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockRenderOrderTest.php @@ -48,21 +48,24 @@ function testBlockRenderOrder() { 'stark_powered' => array( 'weight' => '-3', 'id' => 'stark_powered', + 'label' => 'Test block A', ), 'stark_by' => array( 'weight' => '3', 'id' => 'stark_by', + 'label' => 'Test block C', ), 'stark_drupal' => array( 'weight' => '3', 'id' => 'stark_drupal', + 'label' => 'Test block B', ), ); // Place the test blocks. foreach ($test_blocks as $test_block) { $this->drupalPlaceBlock('system_powered_by_block', array( - 'label' => 'Test Block', + 'label' => $test_block['label'], 'region' => $region, 'weight' => $test_block['weight'], 'id' => $test_block['id'], @@ -81,6 +84,6 @@ function testBlockRenderOrder() { } } $this->assertTrue($position['stark_powered'] < $position['stark_by'], 'Blocks with different weight are rendered in the correct order.'); - $this->assertTrue($position['stark_drupal'] < $position['stark_by'], 'Blocks with identical weight are rendered in reverse alphabetical order.'); + $this->assertTrue($position['stark_drupal'] < $position['stark_by'], 'Blocks with identical weight are rendered in alphabetical order.'); } } diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php index 827f6b9..9188d17 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php @@ -102,6 +102,9 @@ protected function createTests() { 'label' => '', 'module' => 'block_test', 'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE, + 'cache' => array( + 'max_age' => 0, + ), ), 'visibility' => NULL, ); diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php index cb09834..fc061cf 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php @@ -7,6 +7,7 @@ namespace Drupal\block\Tests; +use Drupal\Core\Cache\Cache; use Drupal\simpletest\WebTestBase; /** @@ -245,4 +246,108 @@ function moveBlockToRegion(array $block, $region) { )); $this->assertFieldByXPath($xpath, NULL, t('Block found in %region_name region.', array('%region_name' => drupal_html_class($region)))); } + + /** + * Test that cache tags are properly set and bubbled up to the page cache. + * + * Verify that invalidation of these cache tags works: + * - "block:" + * - "block_plugin:" + */ + public function testBlockCacheTags() { + // The page cache only works for anonymous users. + $this->drupalLogout(); + + // Enable page caching. + $config = \Drupal::config('system.performance'); + $config->set('cache.page.use_internal', 1); + $config->set('cache.page.max_age', 300); + $config->save(); + + // Place the "Powered by Drupal" block. + $block = $this->drupalPlaceBlock('system_powered_by_block', array('id' => 'powered', 'cache' => array('max_age' => 315360000))); + + // Prime the page cache. + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); + + // Verify a cache hit, but also the presence of the correct cache tags in + // both the page and block caches. + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); + $cid_parts = array(url('', array('absolute' => TRUE)), 'html'); + $cid = sha1(implode(':', $cid_parts)); + $cache_entry = \Drupal::cache('page')->get($cid); + $expected_cache_tags = array( + 'content:1', + 'block_view:1', + 'block:powered', + 'block_plugin:system_powered_by_block', + ); + $this->assertIdentical($cache_entry->tags, $expected_cache_tags); + $cache_entry = \Drupal::cache('block')->get('entity_view:block:powered:en:stark'); + $this->assertIdentical($cache_entry->tags, $expected_cache_tags); + + // The "Powered by Drupal" block is modified; verify a cache miss. + $block->set('region', 'content'); + $block->save(); + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); + + // Now we should have a cache hit again. + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); + + // Place the "Powered by Drupal" block another time; verify a cache miss. + $block_2 = $this->drupalPlaceBlock('system_powered_by_block', array('id' => 'powered-2', 'cache' => array('max_age' => 315360000))); + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); + + // Verify a cache hit, but also the presence of the correct cache tags. + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); + $cid_parts = array(url('', array('absolute' => TRUE)), 'html'); + $cid = sha1(implode(':', $cid_parts)); + $cache_entry = \Drupal::cache('page')->get($cid); + $expected_cache_tags = array( + 'content:1', + 'block_view:1', + 'block:powered-2', + 'block:powered', + 'block_plugin:system_powered_by_block', + ); + $this->assertEqual($cache_entry->tags, $expected_cache_tags); + $expected_cache_tags = array( + 'content:1', + 'block_view:1', + 'block:powered', + 'block_plugin:system_powered_by_block', + ); + $cache_entry = \Drupal::cache('block')->get('entity_view:block:powered:en:stark'); + $this->assertIdentical($cache_entry->tags, $expected_cache_tags); + $expected_cache_tags = array( + 'content:1', + 'block_view:1', + 'block:powered-2', + 'block_plugin:system_powered_by_block', + ); + $cache_entry = \Drupal::cache('block')->get('entity_view:block:powered-2:en:stark'); + $this->assertIdentical($cache_entry->tags, $expected_cache_tags); + + // The plugin providing the "Powered by Drupal" block is modified; verify a + // cache miss. + Cache::invalidateTags(array('block_plugin:system_powered_by_block')); + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); + + // Now we should have a cache hit again. + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); + + // Delete the "Powered by Drupal" blocks; verify a cache miss. + entity_delete_multiple('block', array('powered', 'powered-2')); + $this->drupalGet(''); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); + } + } diff --git a/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php b/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php index b86a1ea..00fb33c 100644 --- a/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php @@ -175,19 +175,6 @@ protected function testDeleteBlockDisplay() { } /** - * Tests views block plugin definitions. - */ - public function testViewsBlockPlugins() { - // Ensures that the cache setting gets to the block settings. - $instance = $this->container->get('plugin.manager.block')->createInstance('views_block:test_view_block2-block_2'); - $configuration = $instance->getConfiguration(); - $this->assertEqual($configuration['cache'], DRUPAL_NO_CACHE); - $instance = $this->container->get('plugin.manager.block')->createInstance('views_block:test_view_block2-block_3'); - $configuration = $instance->getConfiguration(); - $this->assertEqual($configuration['cache'], DRUPAL_CACHE_PER_USER); - } - - /** * Test the block form for a Views block. */ public function testViewsBlockForm() { @@ -281,19 +268,23 @@ public function testBlockRendering() { public function testBlockContextualLinks() { $this->drupalLogin($this->drupalCreateUser(array('administer views', 'access contextual links', 'administer blocks'))); $block = $this->drupalPlaceBlock('views_block:test_view_block-block_1'); + $cached_block = $this->drupalPlaceBlock('views_block:test_view_block-block_1', array('cache' => array('max_age' => 3600))); $this->drupalGet('test-page'); $id = 'block:block=' . $block->id() . ':|views_ui_edit:view=test_view_block:location=block&name=test_view_block&display_id=block_1'; + $cached_id = 'block:block=' . $cached_block->id() . ':|views_ui_edit:view=test_view_block:location=block&name=test_view_block&display_id=block_1'; // @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder() $this->assertRaw(' $id)) . '>', format_string('Contextual link placeholder with id @id exists.', array('@id' => $id))); + $this->assertRaw(' $cached_id)) . '>', format_string('Contextual link placeholder with id @id exists.', array('@id' => $cached_id))); // Get server-rendered contextual links. // @see \Drupal\contextual\Tests\ContextualDynamicContextTest:renderContextualLinks() - $post = array('ids[0]' => $id); + $post = array('ids[0]' => $id, 'ids[1]' => $cached_id); $response = $this->drupalPost('contextual/render', 'application/json', $post, array('query' => array('destination' => 'test-page'))); $this->assertResponse(200); $json = drupal_json_decode($response); $this->assertIdentical($json[$id], ''); + $this->assertIdentical($json[$cached_id], ''); } } diff --git a/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestCacheBlock.php b/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestCacheBlock.php index fc9a28f..08fc35a 100644 --- a/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestCacheBlock.php +++ b/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestCacheBlock.php @@ -28,8 +28,11 @@ public function build() { ); } - public function cacheKeys() { - return array_merge(parent::cacheKeys(), $this->configuration['test_cache_contexts']); + /** + * {@inheritdoc} + */ + public function getCacheKeys() { + return array_merge($this->configuration['test_cache_contexts']); } } diff --git a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php index 64f713e..1a28758 100644 --- a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php +++ b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php @@ -116,12 +116,11 @@ public function build() { return array(); } - public function cacheKeys() { - return array('book_navigation', 'book', DRUPAL_CACHE_PER_PAGE, DRUPAL_CACHE_PER_ROLE); + /** + * {@inheritdoc} + */ + public function getCacheKeys() { + return array(DRUPAL_CACHE_PER_PAGE, DRUPAL_CACHE_PER_ROLE); } - public function cacheTags() { - // @todo Save tags for each entity in the book. - return array('content' => TRUE); - } } diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php index 0ed096a..133529a 100644 --- a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php +++ b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php @@ -21,7 +21,7 @@ class ActiveTopicsBlock extends ForumBlockBase { /** * {@inheritdoc} */ - protected function forumQuery() { + protected function buildForumQuery() { return db_select('forum_index', 'f') ->fields('f') ->addTag('node_access') diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php index 9b63a76..26ed640 100644 --- a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php +++ b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php @@ -20,7 +20,7 @@ * {@inheritdoc} */ public function build() { - $result = $this->forumQuery()->execute(); + $result = $this->buildForumQuery()->execute(); if ($node_title_list = node_title_list($result)) { $elements['forum_list'] = $node_title_list; $elements['forum_more'] = array( @@ -32,7 +32,13 @@ public function build() { return $elements; } - abstract protected function forumQuery(); + /** + * Builds the select query to use for this forum block. + * + * @return \Drupal\Core\Database\Query\Select + * A Select object. + */ + abstract protected function buildForumQuery(); /** * {@inheritdoc} @@ -73,14 +79,12 @@ public function blockSubmit($form, &$form_state) { $this->configuration['block_count'] = $form_state['values']['block_count']; } - - public function cacheKeys() { - return array('forum_block_view', $this->cacheKeyFromQuery($this->forumQuery())); - } - - protected function cacheKeyFromQuery($query) { + /** + * {@inheritdoc} + */ + public function getCacheKeys() { $cacheable_helper = new CacheableHelper; - return $cacheable_helper->keyFromQuery($query); + return array($this->cacheKeyFromQuery($this->buildForumQuery())); } } diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php b/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php index a61970f..635dab4 100644 --- a/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php +++ b/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php @@ -21,7 +21,7 @@ class NewTopicsBlock extends ForumBlockBase { /** * {@inheritdoc} */ - protected function forumQuery() { + protected function buildForumQuery() { return db_select('forum_index', 'f') ->fields('f') ->addTag('node_access') diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index d4a8857..1563b45 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -332,7 +332,7 @@ function menu_block_view_system_menu_block_alter(array &$build, BlockPluginInter $menu_name = $block->getDerivativeId(); if (isset($menus[$menu_name]) && isset($build['content'])) { foreach (element_children($build['content']) as $key) { - $build['content']['#contextual_links']['menu'] = array( + $build['#contextual_links']['menu'] = array( 'route_parameters' => array('menu' => $build['content'][$key]['#original_link']['menu_name']), ); } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index b284941..019abab 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -357,6 +357,7 @@ protected function drupalCreateContentType(array $values = array()) { * - region: 'sidebar_first'. * - theme: The default theme. * - visibility: Empty array. + * - cache: array('max_age' => 0). * * @return \Drupal\block\Entity\Block * The block entity. @@ -373,6 +374,9 @@ protected function drupalPlaceBlock($plugin_id, array $settings = array()) { 'label' => $this->randomName(8), 'visibility' => array(), 'weight' => 0, + 'cache' => array( + 'max_age' => 0, + ), ); foreach (array('region', 'id', 'theme', 'plugin', 'visibility', 'weight') as $key) { $values[$key] = $settings[$key]; diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php index f669e61..9f1e73b 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php +++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php @@ -117,4 +117,24 @@ public function build() { ); } + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // The help block is never cacheable. + $form['cache']['#access'] = FALSE; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function isCacheable() { + // The help block is never cacheable. + return FALSE; + } + } diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMainBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMainBlock.php index a64951a..56b22b0 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMainBlock.php +++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMainBlock.php @@ -28,4 +28,24 @@ public function build() { ); } + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // The main content block is never cacheable. + $form['cache']['#access'] = FALSE; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function isCacheable() { + // The main content block is never cacheable. + return FALSE; + } + } diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php index 8298193..f44e533 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php +++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php @@ -8,7 +8,6 @@ namespace Drupal\system\Plugin\Block; use Drupal\block\BlockBase; -use Drupal\Core\Session\AccountInterface; /** * Provides a generic Menu block. @@ -30,4 +29,11 @@ public function build() { return menu_tree($menu); } + /** + * {@inheritdoc} + */ + public function getCacheKeys() { + return array(DRUPAL_CACHE_PER_PAGE, DRUPAL_CACHE_PER_ROLE); + } + } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index cc647a2..bc10db2 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -9,6 +9,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Language\Language; use Drupal\Core\Utility\ModuleInfo; +use Drupal\block\BlockPluginInterface; use Drupal\menu_link\MenuLinkInterface; use Drupal\user\UserInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -3173,3 +3174,26 @@ function system_admin_paths() { ); return $paths; } + +/** + * Implements hook_block_view_BASE_BLOCK_ID_alter(). + */ +function system_block_view_system_main_block_alter(array &$build, BlockPluginInterface $block) { + // Remove contextual links on the main content block, since contextual links + // are basically output as tabs/local tasks already. + if (isset($build['#contextual_links'])) { + unset($build['#contextual_links']); + } +} + +/** + * Implements hook_block_view_BASE_BLOCK_ID_alter(). + */ +function system_block_view_system_help_block_alter(array &$build, BlockPluginInterface $block) { + // Remove contextual links on the help block, since we assume that most users + // don't need or want to perform contextual actions on the help block, and the + // links needlessly draw atention to it. + if (isset($build['#contextual_links'])) { + unset($build['#contextual_links']); + } +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php index 73612e1..c097f23 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php +++ b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php @@ -29,13 +29,11 @@ public function build() { $this->view->display_handler->preBlockBuild($this); if ($output = $this->view->executeDisplay($this->displayID)) { - // Set the label to the title configured in the view. + // Override the label to the dynamic title configured in the view. if (empty($this->configuration['views_label'])) { - $this->configuration['label'] = Xss::filterAdmin($this->view->getTitle()); - } - else { - $this->configuration['label'] = $this->configuration['views_label']; + $output['#block_label'] = Xss::filterAdmin($this->view->getTitle()); } + // Before returning the block output, convert it to a renderable array // with contextual links. $this->addContextualLinks($output); @@ -48,6 +46,20 @@ public function build() { /** * {@inheritdoc} */ + public function getConfiguration() { + $configuration = parent::getConfiguration(); + + // Set the label to the static title configured in the view. + if (!empty($configuration['views_label'])) { + $configuration['label'] = $configuration['views_label']; + } + + return $configuration; + } + + /** + * {@inheritdoc} + */ public function defaultConfiguration() { $settings = parent::defaultConfiguration(); @@ -101,8 +113,4 @@ public function getMachineNameSuggestion() { return 'views_block__' . $this->view->storage->id() . '_' . $this->view->current_display; } - public function cacheKeys() { - return array($this->getMachineNameSuggestion()); - } - } diff --git a/core/profiles/standard/config/block.block.bartik_powered.yml b/core/profiles/standard/config/block.block.bartik_powered.yml index b8aab83..7297528 100644 --- a/core/profiles/standard/config/block.block.bartik_powered.yml +++ b/core/profiles/standard/config/block.block.bartik_powered.yml @@ -10,6 +10,8 @@ settings: label: 'Powered by Drupal' module: system label_display: '0' + cache: + max_age: '315360000' visibility: path: visibility: '0'