diff --git a/core/core.services.yml b/core/core.services.yml index 969b0e0..2169617 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -4,6 +4,14 @@ services: arguments: ['@settings'] calls: - [setContainer, ['@service_container']] + cache_contexts: + class: Drupal\Core\Cache\CacheContexts + arguments: ['@service_container', '%cache_contexts%' ] + cache_context.url: + class: Drupal\Core\Cache\UrlCacheContext + arguments: ['@request'] + tags: + - { name: cache.context} cache.backend.database: class: Drupal\Core\Cache\DatabaseBackendFactory arguments: ['@database'] diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 222ac0c..eb4b964 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -974,6 +974,10 @@ function drupal_serve_page_from_cache(stdClass $cache, Response $response, Reque // max-age > 0, allowing the page to be cached by external proxies, when a // session cookie is present unless the Vary header has been replaced. $max_age = !$request->cookies->has(session_name()) || isset($boot_headers['vary']) ? $config->get('cache.page.max_age') : 0; + // Never set an expiration date further than one year into the future. + if ($max_age > 31536000 || $max_age === \Drupal\Core\Cache\Cache::PERMANENT) { + $max_age = 31536000; + } $response->headers->set('Cache-Control', 'public, max-age=' . $max_age); // Entity tag should change if the output changes. diff --git a/core/includes/common.inc b/core/includes/common.inc index 2cda0fc..73edc78 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -4535,8 +4535,7 @@ function drupal_cache_tags_page_get(Response $response) { */ function drupal_render_cache_by_query($query, $function, $expire = Cache::PERMANENT, $granularity = NULL) { $cache_keys = array_merge(array($function), drupal_render_cid_parts($granularity)); - $query->preExecute(); - $cache_keys[] = hash('sha256', serialize(array((string) $query, $query->getArguments()))); + $cache_keys[] = Cache::keyFromQuery($query); return array( '#query' => $query, '#pre_render' => array($function . '_pre_render'), @@ -4613,9 +4612,16 @@ function drupal_render_cid_create($elements) { return $elements['#cache']['cid']; } elseif (isset($elements['#cache']['keys'])) { + // Cache keys may either be static (just strings) or tokens (placeholders + // that are converted to static keys by the @cache_contexts service, + // depending on the request). + $cache_contexts = \Drupal::service("cache_contexts"); + $keys = $cache_contexts->convertTokensToKeys($elements['#cache']['keys']); + $granularity = isset($elements['#cache']['granularity']) ? $elements['#cache']['granularity'] : NULL; // Merge in additional cache ID parts based provided by drupal_render_cid_parts(). - $cid_parts = array_merge($elements['#cache']['keys'], drupal_render_cid_parts($granularity)); + $cid_parts = array_merge($keys, drupal_render_cid_parts($granularity)); + return implode(':', $cid_parts); } return FALSE; diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php index c2f5fe6..38ca1b1 100644 --- a/core/lib/Drupal/Core/Cache/Cache.php +++ b/core/lib/Drupal/Core/Cache/Cache.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Cache; +use Drupal\Core\Database\Query\SelectInterface; + /** * Helper methods for cache. */ @@ -70,4 +72,19 @@ public static function getBins() { return $bins; } + /** + * Generates a hash from a query object, to be used as part of the cache key. + * + * @param \Drupal\Core\Database\Query\SelectInterface $query + * A select query object. + * + * @return string + * A hash of the query arguments. + */ + public static function keyFromQuery(SelectInterface $query) { + $query->preExecute(); + $keys = array((string) $query, $query->getArguments()); + return hash('sha256', serialize($keys)); + } + } diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php index 3bdbc64..36fd5a3 100644 --- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php +++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php @@ -74,7 +74,7 @@ /** * Indicates that the item should never be removed unless explicitly deleted. */ - const CACHE_PERMANENT = 0; + const CACHE_PERMANENT = -1; /** * Returns data from the persistent cache. diff --git a/core/lib/Drupal/Core/Cache/CacheContextInterface.php b/core/lib/Drupal/Core/Cache/CacheContextInterface.php new file mode 100644 index 0000000..fd9783a --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheContextInterface.php @@ -0,0 +1,34 @@ +container = $container; + $this->contexts = $contexts; + } + + /** + * Provides an array of available cache contexts. + * + * @return array + * An array of available cache contexts. + */ + public function getAll() { + return $this->contexts; + } + + /** + * Provides an array of available cache context labels. + * + * To be used in a cache configuration form. + * + * @return array + * An array of available cache contexts and corresponding labels. + */ + public function getLabels() { + $with_labels = array(); + foreach ($this->contexts as $context) { + $with_labels[$context] = $this->getService($context)->getLabel(); + } + return $with_labels; + } + + /** + * Converts cache context tokens to string representations of the context. + * + * Cache keys may either be static (just strings) or tokens (placeholders + * that are converted to static keys by the @cache_contexts service, depending + * depending on the request). This is the default cache contexts service. + * + * @param array $keys + * An array of cache keys that may or may not contain cache context tokens. + * + * @return array + * A copy of the input, with cache context tokens converted. + */ + public function convertTokensToKeys(array $keys) { + $context_keys = array_intersect($keys, $this->getAll()); + $new_keys = $keys; + + // Iterate over the indices instead of the values so that the order of the + // cache keys are preserved. + foreach (array_keys($context_keys) as $index) { + $new_keys[$index] = $this->getContext($keys[$index]); + } + return $new_keys; + } + + /** + * Provides the string representaton of a cache context. + * + * @param string $context + * A cache context token of an available cache context service. + * + * @return string + * The string representaton of a cache context. + */ + protected function getContext($context) { + return $this->getService($context)->getContext(); + } + + /** + * Retrieves a service from the container. + * + * @param string $service + * The ID of the service to retrieve. + * + * @return mixed + * The specified service. + */ + protected function getService($service) { + return $this->container->get($service); + } + +} diff --git a/core/lib/Drupal/Core/Cache/CacheContextsPass.php b/core/lib/Drupal/Core/Cache/CacheContextsPass.php new file mode 100644 index 0000000..a5f666f --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheContextsPass.php @@ -0,0 +1,27 @@ +findTaggedServiceIds('cache.context')); + $container->setParameter('cache_contexts', $cache_contexts); + } +} diff --git a/core/lib/Drupal/Core/Cache/UrlCacheContext.php b/core/lib/Drupal/Core/Cache/UrlCacheContext.php new file mode 100644 index 0000000..8d9141e --- /dev/null +++ b/core/lib/Drupal/Core/Cache/UrlCacheContext.php @@ -0,0 +1,41 @@ +request = $request; + } + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return t('URL'); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return $this->request->getUri(); + } + +} diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 847960c..1de7b19 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -8,6 +8,7 @@ namespace Drupal\Core; use Drupal\Core\Cache\ListCacheBinsPass; +use Drupal\Core\Cache\CacheContextsPass; use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass; @@ -70,6 +71,7 @@ public function register(ContainerBuilder $container) { $container->addCompilerPass(new RegisterPathProcessorsPass()); $container->addCompilerPass(new RegisterRouteProcessorsPass()); $container->addCompilerPass(new ListCacheBinsPass()); + $container->addCompilerPass(new CacheContextsPass()); // Add the compiler pass for appending string translators. $container->addCompilerPass(new RegisterStringTranslatorsPass()); // Add the compiler pass that will process the tagged breadcrumb builder diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 1bca18b..08fb82d 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -169,52 +169,16 @@ 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(); - // Block caching is not compatible with node_access modules. We also - // preserve the submission of forms in blocks, by fetching from cache - // only if the request method is 'GET' (or 'HEAD'). User 1 being out of - // the regular 'roles define permissions' schema, it brings too many - // chances of having unwanted output get in the cache and later be served - // to other users. We therefore exclude user 1 from block caching. - $not_cacheable = \Drupal::currentUser()->id() == 1 || - count(\Drupal::moduleHandler()->getImplementations('node_grants')) || - !\Drupal::request()->isMethodSafe(); - - foreach ($list as $key => $block) { - $settings = $block->get('settings'); - if ($not_cacheable || in_array($settings['cache'], array(DRUPAL_NO_CACHE, DRUPAL_CACHE_CUSTOM))) { - // Non-cached blocks get built immediately. + foreach ($list as $key => $block) { if ($block->access()) { $build[$key] = entity_view($block, 'block'); } } - else { - $build[$key] = array( - '#block' => $block, - '#weight' => $block->get('weight'), - '#pre_render' => array('_block_get_renderable_block'), - '#cache' => array( - 'keys' => array($key, $settings['module']), - 'granularity' => $settings['cache'], - 'bin' => 'block', - 'tags' => array('content' => TRUE), - ), - ); + // If none of the blocks in this region are visible, then don't set anything + // else in the render array, because that would cause the region to show up. + if (!empty($build)) { + // block_list() already returned the blocks in sorted order. + $build['#sorted'] = TRUE; } } return $build; @@ -342,9 +306,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]; } @@ -365,26 +327,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() { @@ -456,6 +398,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']['#title']) && !empty($variables['configuration']['label_display'])) { + $variables['label'] = $variables['elements']['content']['#title']; + } $variables['attributes']['class'][] = 'block'; $variables['attributes']['class'][] = drupal_html_class('block-' . $variables['configuration']['module']); diff --git a/core/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml index 1ed3f49..24660b1 100644 --- a/core/modules/block/config/schema/block.schema.yml +++ b/core/modules/block/config/schema/block.schema.yml @@ -73,8 +73,12 @@ block.block.*: type: string label: 'Display title' cache: - type: integer - label: 'Cache' + type: mapping + label: 'Cache settings' + mapping: + max_age: + type: integer + label: 'Maximum age' status: type: boolean label: 'Status' diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php index d278663..de7916b 100644 --- a/core/modules/block/lib/Drupal/block/BlockBase.php +++ b/core/modules/block/lib/Drupal/block/BlockBase.php @@ -10,7 +10,10 @@ use Drupal\Core\Plugin\PluginBase; use Drupal\block\BlockInterface; use Drupal\Component\Utility\Unicode; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Language\Language; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableInterface; use Drupal\Core\Session\AccountInterface; /** @@ -28,12 +31,7 @@ public function __construct(array $configuration, $plugin_id, array $plugin_definition) { parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->configuration += $this->defaultConfiguration() + array( - 'label' => '', - 'module' => $plugin_definition['module'], - 'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE, - 'cache' => DRUPAL_NO_CACHE, - ); + $this->setConfiguration($configuration); } /** @@ -47,7 +45,29 @@ public function getConfiguration() { * {@inheritdoc} */ public function setConfiguration(array $configuration) { - $this->configuration = $configuration; + $this->configuration = NestedArray::mergeDeep( + $this->baseConfigurationDefaults(), + $this->defaultConfiguration(), + $configuration + ); + } + + /** + * Returns generic default configuration for block plugins. + * + * @return array + * An associative array with the default configuration. + */ + protected function baseConfigurationDefaults() { + return array( + 'label' => '', + 'module' => $this->pluginDefinition['module'], + 'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE, + 'cache' => array( + 'max_age' => 0, + 'contexts' => array(), + ), + ); } /** @@ -108,11 +128,36 @@ public function buildConfigurationForm(array $form, array &$form_state) { '#default_value' => ($this->configuration['label_display'] === BlockInterface::BLOCK_LABEL_VISIBLE), '#return_value' => BlockInterface::BLOCK_LABEL_VISIBLE, ); + // Identical options to the ones for page caching. + // @see \Drupal\system\Form\PerformanceForm::buildForm() + $period = array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400); + $period = array_map('format_interval', array_combine($period, $period)); + $period[0] = '<' . t('no caching') . '>'; + $period[\Drupal\Core\Cache\Cache::PERMANENT] = t('Forever'); $form['cache'] = array( - '#type' => 'value', - '#value' => $this->configuration['cache'], + '#type' => 'details', + '#title' => t('Cache settings'), + ); + $form['cache']['max_age'] = array( + '#type' => 'select', + '#title' => t('Maximum age'), + '#description' => t('The maximum time this block may be cached.'), + '#default_value' => $this->configuration['cache']['max_age'], + '#options' => $period, + ); + $form['cache']['contexts'] = array( + '#type' => 'select', + '#multiple' => TRUE, + '#title' => t('Vary by context'), + '#description' => t('The contexts this cached block must be varied by.'), + '#default_value' => $this->configuration['cache']['contexts'], + '#options' => \Drupal::service("cache_contexts")->getLabels(), + '#states' => array( + 'disabled' => array( + ':input[name="settings[cache][max_age]"]' => array('value' => (string) 0), + ), + ), ); - // Add plugin-specific settings for this block type. $form += $this->blockForm($form, $form_state); return $form; @@ -156,6 +201,7 @@ public function submitConfigurationForm(array &$form, array &$form_state) { $this->configuration['label'] = $form_state['values']['label']; $this->configuration['label_display'] = $form_state['values']['label_display']; $this->configuration['module'] = $form_state['values']['module']; + $this->configuration['cache'] = $form_state['values']['cache']; $this->blockSubmit($form, $form_state); } } @@ -189,4 +235,47 @@ public function getMachineNameSuggestion() { return $transliterated; } + /** + * {@inheritdoc} + */ + public function getCacheKeys() { + return $this->configuration['cache']['contexts']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + // 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)); + } + + /** + * {@inheritdoc} + */ + public function getCacheBin() { + return 'block'; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return (int)$this->configuration['cache']['max_age']; + } + + /** + * {@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. + $max_age = $this->configuration['cache']['max_age']; + return $max_age === Cache::PERMANENT || $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 a076dc9..6fe8abc 100644 --- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php +++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php @@ -7,7 +7,9 @@ namespace Drupal\block; +use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\String; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityViewBuilder; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Entity\EntityInterface; @@ -43,52 +45,55 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la $plugin_id = $plugin->getPluginId(); $base_id = $plugin->getBasePluginId(); $derivative_id = $plugin->getDerivativeId(); + $configuration = $plugin->getConfiguration(); - if ($content = $plugin->build()) { - $configuration = $plugin->getConfiguration(); - - // Create the render array for the block as a whole. - // @see template_preprocess_block(). - $build[$key] = array( - '#theme' => 'block', - '#attributes' => array(), - '#contextual_links' => array( - 'block' => array( - 'route_parameters' => array('block' => $entity_id), - ), + // Create the render array for the block as a whole. + // @see template_preprocess_block(). + $build[$entity_id] = array( + '#theme' => 'block', + '#attributes' => array(), + // All blocks get a "Configure block" contextual link. + '#contextual_links' => array( + 'block' => array( + 'route_parameters' => array('block' => $entity->id()), ), - '#configuration' => $configuration, - '#plugin_id' => $plugin_id, - '#base_plugin_id' => $base_id, - '#derivative_plugin_id' => $derivative_id, + ), + '#weight' => $entity->get('weight'), + '#configuration' => $configuration, + '#plugin_id' => $plugin_id, + '#base_plugin_id' => $base_id, + '#derivative_plugin_id' => $derivative_id, + // @todo Remove after fixing http://drupal.org/node/1989568. + '#block' => $entity, + ); + $build[$entity_id]['#configuration']['label'] = check_plain($configuration['label']); + + // Set cache tags; these always need to be set, whether the block is + // cacheable or not, so that the page cache is correctly informed. + $default_cache_tags = array( + 'content' => TRUE, + 'block_view' => TRUE, + 'block' => array($entity->id()), + ); + $build[$entity_id]['#cache']['tags'] = NestedArray::mergeDeep($default_cache_tags, $plugin->getCacheTags()); + + + if ($plugin->isCacheable()) { + $build[$entity_id]['#pre_render'][] = array($this, 'buildBlock'); + // 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); + $max_age = $plugin->getCacheMaxAge(); + $build[$entity_id]['#cache'] += array( + 'keys' => array_merge($default_cache_keys, $plugin->getCacheKeys()), + 'bin' => $plugin->getCacheBin(), + 'expire' => ($max_age === Cache::PERMANENT) ? Cache::PERMANENT : REQUEST_TIME + $max_age, ); - $build[$key]['#configuration']['label'] = String::checkPlain($configuration['label']); - - // Place the $content returned by the block plugin into a 'content' - // child element, as a way to allow the plugin to have complete control - // of its properties and rendering (e.g., its own #theme) without - // conflicting with the properties used above, or alternate ones used - // by alternate block rendering approaches in contrib (e.g., Panels). - // However, the use of a child element is an implementation detail of - // this particular block rendering approach. Semantically, the content - // returned by the plugin "is the" block, and in particular, - // #attributes and #contextual_links is information about the *entire* - // block. Therefore, we must move these properties from $content and - // merge them into the top-level element. - foreach (array('#attributes', '#contextual_links') as $property) { - if (isset($content[$property])) { - $build[$key][$property] += $content[$property]; - unset($content[$property]); - } - } - $build[$key]['content'] = $content; } else { - $build[$key] = array(); + $build[$entity_id] = $this->buildBlock($build[$entity_id]); } - $this->moduleHandler()->alter(array('block_view', "block_view_$base_id"), $build[$key], $plugin); - // @todo Remove after fixing http://drupal.org/node/1989568. $build[$key]['#block'] = $entity; } @@ -96,8 +101,59 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la } /** + * #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(); + + if (!empty($content)) { + // Place the $content returned by the block plugin into a 'content' child + // element, as a way to allow the plugin to have complete control of its + // properties and rendering (e.g., its own #theme) without conflicting + // with the properties used above, or alternate ones used by alternate + // block rendering approaches in contrib (e.g., Panels). However, the use + // of a child element is an implementation detail of this particular block + // rendering approach. Semantically, the content returned by the plugin + // "is the" block, and in particular, #attributes and #contextual_links is + // information about the *entire* block. Therefore, we must move these + // properties from $content and merge them into the top-level element. + foreach (array('#attributes', '#contextual_links') as $property) { + if (isset($content[$property])) { + $build[$property] += $content[$property]; + unset($content[$property]); + } + } + $build['content'] = $content; + + $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 cb49cac..1188946 100644 --- a/core/modules/block/lib/Drupal/block/Entity/Block.php +++ b/core/modules/block/lib/Drupal/block/Entity/Block.php @@ -7,7 +7,9 @@ namespace Drupal\block\Entity; +use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\block\BlockPluginBag; use Drupal\block\BlockInterface; use Drupal\Core\Config\Entity\EntityWithPluginBagInterface; @@ -146,6 +148,32 @@ public function getExportProperties() { } /** + * {@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/Plugin/views/display/Block.php b/core/modules/block/lib/Drupal/block/Plugin/views/display/Block.php index 657b635..94bea95 100644 --- a/core/modules/block/lib/Drupal/block/Plugin/views/display/Block.php +++ b/core/modules/block/lib/Drupal/block/Plugin/views/display/Block.php @@ -46,7 +46,6 @@ protected function defineOptions() { $options['block_description'] = array('default' => '', 'translatable' => TRUE); $options['block_category'] = array('default' => 'Lists (Views)', 'translatable' => TRUE); - $options['block_caching'] = array('default' => DRUPAL_NO_CACHE); $options['block_hide_empty'] = array('default' => FALSE); $options['allow'] = array( @@ -131,13 +130,6 @@ public function optionsSummary(&$categories, &$options) { 'value' => empty($filtered_allow) ? t('None') : t('Items per page'), ); - $types = $this->blockCachingModes(); - $options['block_caching'] = array( - 'category' => 'other', - 'title' => t('Block caching'), - 'value' => $types[$this->getCacheType()], - ); - $options['block_hide_empty'] = array( 'category' => 'other', 'title' => t('Hide block if the view output is empty'), @@ -146,33 +138,6 @@ public function optionsSummary(&$categories, &$options) { } /** - * Provide a list of core's block caching modes. - */ - protected function blockCachingModes() { - return array( - DRUPAL_NO_CACHE => t('Do not cache'), - DRUPAL_CACHE_GLOBAL => t('Cache once for everything (global)'), - DRUPAL_CACHE_PER_PAGE => t('Per page'), - DRUPAL_CACHE_PER_ROLE => t('Per role'), - DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE => t('Per role per page'), - DRUPAL_CACHE_PER_USER => t('Per user'), - DRUPAL_CACHE_PER_USER | DRUPAL_CACHE_PER_PAGE => t('Per user per page'), - ); - } - - /** - * Provide a single method to figure caching type, keeping a sensible default - * for when it's unset. - */ - public function getCacheType() { - $cache_type = $this->getOption('block_caching'); - if (empty($cache_type)) { - $cache_type = DRUPAL_NO_CACHE; - } - return $cache_type; - } - - /** * Provide the default form for setting options. */ public function buildOptionsForm(&$form, &$form_state) { @@ -196,16 +161,6 @@ public function buildOptionsForm(&$form, &$form_state) { '#default_value' => $this->getOption('block_category'), ); break; - case 'block_caching': - $form['#title'] .= t('Block caching type'); - - $form['block_caching'] = array( - '#type' => 'radios', - '#description' => t("This sets the default status for Drupal's built-in block caching method; this requires that caching be turned on in block administration, and be careful because you have little control over when this cache is flushed."), - '#options' => $this->blockCachingModes(), - '#default_value' => $this->getCacheType(), - ); - break; case 'block_hide_empty': $form['#title'] .= t('Block empty settings'); @@ -251,7 +206,6 @@ public function submitOptionsForm(&$form, &$form_state) { switch ($form_state['section']) { case 'block_description': case 'block_category': - case 'block_caching': case 'allow': case 'block_hide_empty': $this->setOption($form_state['section'], $form_state['values'][$form_state['section']]); diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php index 7414efd..ce78720 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php @@ -61,10 +61,13 @@ function setUp() { } /** - * Test DRUPAL_CACHE_PER_ROLE. + * Test "cache_context.user.roles" cache context. */ function testCachePerRole() { - $this->setCacheMode(DRUPAL_CACHE_PER_ROLE); + $this->setBlockCacheConfig(array( + 'max_age' => 600, + 'contexts' => array('cache_context.user.roles'), + )); // Enable our test block. Set some content for it to display. $current_content = $this->randomName(); @@ -108,10 +111,13 @@ function testCachePerRole() { } /** - * Test DRUPAL_CACHE_GLOBAL. + * Test a cacheable block without any cache context. */ function testCacheGlobal() { - $this->setCacheMode(DRUPAL_CACHE_GLOBAL); + $this->setBlockCacheConfig(array( + 'max_age' => 600, + )); + $current_content = $this->randomName(); \Drupal::state()->set('block_test.content', $current_content); @@ -124,18 +130,21 @@ function testCacheGlobal() { $this->drupalLogout(); $this->drupalGet('user'); - $this->assertText($old_content, 'Block content served from global cache.'); + $this->assertText($old_content, 'Block content served from cache.'); } /** - * Test DRUPAL_NO_CACHE. + * Test non-cacheable block. */ function testNoCache() { - $this->setCacheMode(DRUPAL_NO_CACHE); + $this->setBlockCacheConfig(array( + 'max_age' => 0, + )); + $current_content = $this->randomName(); \Drupal::state()->set('block_test.content', $current_content); - // If DRUPAL_NO_CACHE has no effect, the next request would be cached. + // If max_age = 0 has no effect, the next request would be cached. $this->drupalGet(''); $this->assertText($current_content, 'Block content displays.'); @@ -143,14 +152,18 @@ function testNoCache() { $current_content = $this->randomName(); \Drupal::state()->set('block_test.content', $current_content); $this->drupalGet(''); - $this->assertText($current_content, 'DRUPAL_NO_CACHE prevents blocks from being cached.'); + $this->assertText($current_content, 'Maximum age of zero prevents blocks from being cached.'); } /** - * Test DRUPAL_CACHE_PER_USER. + * Test "cache_context.user" cache context. */ function testCachePerUser() { - $this->setCacheMode(DRUPAL_CACHE_PER_USER); + $this->setBlockCacheConfig(array( + 'max_age' => 600, + 'contexts' => array('cache_context.user'), + )); + $current_content = $this->randomName(); \Drupal::state()->set('block_test.content', $current_content); $this->drupalLogin($this->normal_user); @@ -175,10 +188,14 @@ function testCachePerUser() { } /** - * Test DRUPAL_CACHE_PER_PAGE. + * Test "cache_context.url" cache context. */ function testCachePerPage() { - $this->setCacheMode(DRUPAL_CACHE_PER_PAGE); + $this->setBlockCacheConfig(array( + 'max_age' => 600, + 'contexts' => array('cache_context.url'), + )); + $current_content = $this->randomName(); \Drupal::state()->set('block_test.content', $current_content); @@ -196,10 +213,11 @@ function testCachePerPage() { } /** - * Private helper method to set the test block's cache mode. + * Private helper method to set the test block's cache configuration. */ - private function setCacheMode($cache_mode) { - $this->block->getPlugin()->setConfigurationValue('cache', $cache_mode); + private function setBlockCacheConfig($cache_config) { + $block = $this->block->getPlugin(); + $block->setConfigurationValue('cache', $cache_config); $this->block->save(); } diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php index a694b4e..240fc39 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php @@ -45,10 +45,13 @@ public function testBlockInterface() { ); $expected_configuration = array( 'label' => 'Custom Display Message', - 'display_message' => 'no message set', 'module' => 'block_test', 'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE, - 'cache' => DRUPAL_NO_CACHE, + 'cache' => array( + 'max_age' => 0, + 'contexts' => array(), + ), + 'display_message' => 'no message set', ); // Initial configuration of the block at construction time. $display_block = $manager->createInstance('test_block_instantiation', $configuration); @@ -60,6 +63,10 @@ public function testBlockInterface() { $this->assertIdentical($display_block->getConfiguration(), $expected_configuration, 'The block configuration was updated correctly.'); $definition = $display_block->getPluginDefinition(); + $period = array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400); + $period = array_map('format_interval', array_combine($period, $period)); + $period[0] = '<' . t('no caching') . '>'; + $period[\Drupal\Core\Cache\Cache::PERMANENT] = t('Forever'); $expected_form = array( 'module' => array( '#type' => 'value', @@ -84,8 +91,28 @@ public function testBlockInterface() { '#return_value' => 'visible', ), 'cache' => array( - '#type' => 'value', - '#value' => DRUPAL_NO_CACHE, + '#type' => 'details', + '#title' => t('Cache settings'), + 'max_age' => array( + '#type' => 'select', + '#title' => t('Maximum age'), + '#description' => t('The maximum time this block may be cached.'), + '#default_value' => 0, + '#options' => $period, + ), + 'contexts' => array( + '#type' => 'select', + '#multiple' => TRUE, + '#title' => t('Vary by context'), + '#description' => t('The contexts this cached block must be varied by.'), + '#default_value' => array(), + '#options' => \Drupal::service("cache_contexts")->getLabels(), + '#states' => array( + 'disabled' => array( + ':input[name="settings[cache][max_age]"]' => array('value' => (string) 0), + ), + ), + ), ), 'display_message' => array( '#type' => 'textfield', diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockRenderOrderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockRenderOrderTest.php index 0521aa9..1a7ea82 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 36ed74a..d95edca 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php @@ -102,7 +102,10 @@ protected function createTests() { 'label' => '', 'module' => 'block_test', 'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE, - 'cache' => DRUPAL_NO_CACHE, + 'cache' => array( + 'max_age' => 0, + 'contexts' => array(), + ), ), '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 e7b99e1..cc25986 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; /** @@ -249,35 +250,106 @@ function moveBlockToRegion(array $block, $region) { } /** - * Test _block_rehash(). + * 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:" */ - function testBlockRehash() { - \Drupal::moduleHandler()->install(array('block_test')); - $this->assertTrue(\Drupal::moduleHandler()->moduleExists('block_test'), 'Test block module enabled.'); - - // Clear the block cache to load the block_test module's block definitions. - $this->container->get('plugin.manager.block')->clearCachedDefinitions(); - - // Add a test block. - $block = array(); - $block['id'] = 'test_cache'; - $block['theme'] = \Drupal::config('system.theme')->get('default'); - $block['region'] = 'header'; - $block = $this->drupalPlaceBlock('test_cache', array('region' => 'header')); + public function testBlockCacheTags() { + // The page cache only works for anonymous users. + $this->drupalLogout(); - // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE. - $settings = $block->get('settings'); - $this->assertEqual($settings['cache'], DRUPAL_CACHE_PER_ROLE, 'Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.'); + // 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); - // Disable caching for this block. - $block->getPlugin()->setConfigurationValue('cache', DRUPAL_NO_CACHE); + // The "Powered by Drupal" block is modified; verify a cache miss. + $block->set('region', 'content'); $block->save(); - // Flushing all caches should call _block_rehash(). - $this->resetAll(); - // Verify that block is updated with the new caching mode. - $block = entity_load('block', $block->id()); - $settings = $block->get('settings'); - $this->assertEqual($settings['cache'], DRUPAL_NO_CACHE, "Test block's database entry updated to DRUPAL_NO_CACHE."); + $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 3c9745f..441e5e4 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/Drupal/block/Tests/BlockBaseTest.php b/core/modules/block/tests/Drupal/block/Tests/BlockBaseTest.php index f0d04ec..8d96c33 100644 --- a/core/modules/block/tests/Drupal/block/Tests/BlockBaseTest.php +++ b/core/modules/block/tests/Drupal/block/Tests/BlockBaseTest.php @@ -12,11 +12,6 @@ use Drupal\Core\Transliteration\PHPTransliteration; use Drupal\Tests\UnitTestCase; -// @todo Remove once the constants are replaced with constants on classes. -if (!defined('DRUPAL_NO_CACHE')) { - define('DRUPAL_NO_CACHE', -1); -} - /** * Tests the base block plugin. * diff --git a/core/modules/block/tests/modules/block_test/config/block.block.test_block.yml b/core/modules/block/tests/modules/block_test/config/block.block.test_block.yml index 9a8383c..9c00b0b 100644 --- a/core/modules/block/tests/modules/block_test/config/block.block.test_block.yml +++ b/core/modules/block/tests/modules/block_test/config/block.block.test_block.yml @@ -9,7 +9,6 @@ settings: label: 'Test HTML block' module: block_test label_display: 'hidden' - cache: 1 visibility: path: visibility: 0 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 2fa6122..0216fa1 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 @@ -21,21 +21,10 @@ class TestCacheBlock extends BlockBase { /** * {@inheritdoc} - * - * Sets a different caching strategy for testing purposes. - */ - public function defaultConfiguration() { - return array( - 'cache' => DRUPAL_CACHE_PER_ROLE, - ); - } - - /** - * {@inheritdoc} */ public function build() { return array( - '#children' => \Drupal::state()->get('block_test.content'), + '#markup' => \Drupal::state()->get('block_test.content'), ); } diff --git a/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestXSSTitleBlock.php b/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestXSSTitleBlock.php index 156abf9..3146b59 100644 --- a/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestXSSTitleBlock.php +++ b/core/modules/block/tests/modules/block_test/lib/Drupal/block_test/Plugin/Block/TestXSSTitleBlock.php @@ -16,16 +16,4 @@ * ) */ class TestXSSTitleBlock extends TestCacheBlock { - - /** - * {@inheritdoc} - * - * Sets a different caching strategy for testing purposes. - */ - public function defaultConfiguration() { - return array( - 'cache' => DRUPAL_NO_CACHE, - ); - } - } 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 4350e53..333a255 100644 --- a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php +++ b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php @@ -65,7 +65,6 @@ public static function create(ContainerInterface $container, array $configuratio */ public function defaultConfiguration() { return array( - 'cache' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE, 'block_mode' => "all pages", ); } @@ -101,6 +100,7 @@ public function blockSubmit($form, &$form_state) { */ public function build() { $current_bid = 0; + if ($node = $this->request->get('node')) { $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid']; } @@ -145,15 +145,51 @@ public function build() { $data = array_shift($tree); $below = \Drupal::service('book.manager')->bookTreeOutput($data['below']); if (!empty($below)) { - $book_title_link = array('#theme' => 'book_title_link', '#link' => $data['link']); - return array( - '#title' => drupal_render($book_title_link), - $below, - ); + return $below; } } } return array(); } + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // Remove the required cache contexts from the list of contexts a user can + // choose to modify by: they must always be applied. + $context_labels = array(); + foreach ($this->getRequiredCacheContexts() as $context) { + $context_labels[] = $form['cache']['contexts']['#options'][$context]; + unset($form['cache']['contexts']['#options'][$context]); + } + $required_context_list = implode(', ', $context_labels); + $form['cache']['contexts']['#description'] .= ' ' . t('This block is always varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list)); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getCacheKeys() { + // Return the required cache contexts, merged with the user-configured cache + // contexts, if any. + return array_merge($this->getRequiredCacheContexts(), parent::getCacheKeys()); + } + + /** + * Returns the cache contexts required for this block. + * + * Two cache contexts are required: cache by URL and by user's roles. + * + * @return array + * The required cache contexts IDs. + */ + protected function getRequiredCacheContexts() { + return array('cache_context.url', 'cache_context.user.roles'); + } + } diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index c44cffe..54fddd0 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -538,23 +538,6 @@ function forum_form_node_form_alter(&$form, &$form_state, $form_id) { } /** - * Render API callback: Lists nodes based on the element's #query property. - * - * This function can be used as a #pre_render callback. - * - * @see \Drupal\forum\Plugin\block\block\NewTopicsBlock::build() - * @see \Drupal\forum\Plugin\block\block\ActiveTopicsBlock::build() - */ -function forum_block_view_pre_render($elements) { - $result = $elements['#query']->execute(); - if ($node_title_list = node_title_list($result)) { - $elements['forum_list'] = $node_title_list; - $elements['forum_more'] = array('#theme' => 'more_link', '#url' => 'forum', '#title' => t('Read the latest forum topics.')); - } - return $elements; -} - -/** * Implements hook_preprocess_HOOK() for block templates. */ function forum_preprocess_block(&$variables) { 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 d83f736..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,17 +21,13 @@ class ActiveTopicsBlock extends ForumBlockBase { /** * {@inheritdoc} */ - public function build() { - $query = db_select('forum_index', 'f') + protected function buildForumQuery() { + return db_select('forum_index', 'f') ->fields('f') ->addTag('node_access') ->addMetaData('base_table', 'forum_index') ->orderBy('f.last_comment_timestamp', 'DESC') ->range(0, $this->configuration['block_count']); - - return array( - drupal_render_cache_by_query($query, 'forum_block_view'), - ); } } 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 f1c2f4e..6b760a9 100644 --- a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php +++ b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php @@ -9,6 +9,7 @@ use Drupal\block\BlockBase; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Cache\Cache; /** * Provides a base class for Forum blocks. @@ -18,9 +19,32 @@ /** * {@inheritdoc} */ + public function build() { + $result = $this->buildForumQuery()->execute(); + if ($node_title_list = node_title_list($result)) { + $elements['forum_list'] = $node_title_list; + $elements['forum_more'] = array( + '#theme' => 'more_link', + '#url' => 'forum', + '#title' => t('Read the latest forum topics.') + ); + } + return $elements; + } + + /** + * Builds the select query to use for this forum block. + * + * @return \Drupal\Core\Database\Query\Select + * A Select object. + */ + abstract protected function buildForumQuery(); + + /** + * {@inheritdoc} + */ public function defaultConfiguration() { return array( - 'cache' => DRUPAL_CACHE_CUSTOM, 'properties' => array( 'administrative' => TRUE, ), @@ -56,4 +80,11 @@ public function blockSubmit($form, &$form_state) { $this->configuration['block_count'] = $form_state['values']['block_count']; } + /** + * {@inheritdoc} + */ + public function getCacheKeys() { + return array_merge(parent::getCacheKeys(), Cache::keyFromQuery($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 8b75a83..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,17 +21,12 @@ class NewTopicsBlock extends ForumBlockBase { /** * {@inheritdoc} */ - public function build() { - $query = db_select('forum_index', 'f') + protected function buildForumQuery() { + return db_select('forum_index', 'f') ->fields('f') ->addTag('node_access') ->addMetaData('base_table', 'forum_index') ->orderBy('f.created', 'DESC') ->range(0, $this->configuration['block_count']); - - return array( - drupal_render_cache_by_query($query, 'forum_block_view'), - ); } - } diff --git a/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php index acf276f..0de6dcf 100644 --- a/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php +++ b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php @@ -27,7 +27,6 @@ public function getDerivativeDefinitions(array $base_plugin_definition) { foreach ($configurable_types as $type) { $this->derivatives[$type] = $base_plugin_definition; $this->derivatives[$type]['admin_label'] = t('Language switcher (!type)', array('!type' => $info[$type]['name'])); - $this->derivatives[$type]['cache'] = DRUPAL_NO_CACHE; } // If there is just one configurable type then change the title of the // block. diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php index d926528..34d515a 100644 --- a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php +++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php @@ -515,7 +515,14 @@ public function testMenuBlockPageCacheTags() { $cid_parts = array(url('test-page', array('absolute' => TRUE)), 'html'); $cid = sha1(implode(':', $cid_parts)); $cache_entry = \Drupal::cache('page')->get($cid); - $this->assertIdentical($cache_entry->tags, array('content:1', 'menu:llama')); + $expected_cache_tags = array( + 'content:1', + 'block_view:1', + 'block:' . $block->id(), + 'block_plugin:system_menu_block__llama', + 'menu:llama', + ); + $this->assertIdentical($cache_entry->tags, $expected_cache_tags); // The "Llama" menu is modified. $menu->label = 'Awesome llama'; diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 2bd20a2..933d57e 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -355,6 +355,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. @@ -371,6 +372,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/Form/PerformanceForm.php b/core/modules/system/lib/Drupal/system/Form/PerformanceForm.php index fe689d0..27d628a 100644 --- a/core/modules/system/lib/Drupal/system/Form/PerformanceForm.php +++ b/core/modules/system/lib/Drupal/system/Form/PerformanceForm.php @@ -79,10 +79,12 @@ public function buildForm(array $form, array &$form_state) { '#title' => t('Caching'), '#open' => TRUE, ); - + // Identical options to the ones for block caching. + // @see \Drupal\block\BlockBase::buildConfigurationForm() $period = array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400); $period = array_map('format_interval', array_combine($period, $period)); - $period[0] = '<' . t('none') . '>'; + $period[0] = '<' . t('no caching') . '>'; + $period[\Drupal\Core\Cache\Cache::PERMANENT] = t('Forever'); $form['caching']['page_cache_maximum_age'] = array( '#type' => 'select', '#title' => t('Page cache maximum age'), 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..89a859d 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,26 @@ public function build() { ); } + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // The help block is never cacheable, because it is path-specific. + $form['cache']['#disabled'] = TRUE; + $form['cache']['#description'] = t('This block is never cacheable, it is not configurable.'); + $form['cache']['max_age']['#value'] = 0; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function isCacheable() { + // The help block is never cacheable, because it is path-specific. + 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..666b515 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,26 @@ 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, because it may be dynamic. + $form['cache']['#disabled'] = TRUE; + $form['cache']['#description'] = t('This block is never cacheable, it is not configurable.'); + $form['cache']['max_age']['#value'] = 0; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function isCacheable() { + // The main content block is never cacheable, because it may be dynamic. + 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..52d7a02 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,59 @@ public function build() { return menu_tree($menu); } + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + // Modify the default max age for menu blocks: modifications made to menus, + // menu links and menu blocks will automatically invalidate corresponding + // cache tags, therefor allowing us to cache menu blocks forever. This is + // only not the case if there are user-specific or dynamic alterations (e.g. + // hook_node_access()), but in that: + // 1) it is possible to set a different max age for individual blocks, since + // this is just the default value. + // 2) modules can modify caching by implementing hook_block_view_alter() + return array('cache' => array('max_age' => \Drupal\Core\Cache\Cache::PERMANENT)); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // Remove the required cache contexts from the list of contexts a user can + // choose to modify by: they must always be applied. + $context_labels = array(); + foreach ($this->getRequiredCacheContexts() as $context) { + $context_labels[] = $form['cache']['contexts']['#options'][$context]; + unset($form['cache']['contexts']['#options'][$context]); + } + $required_context_list = implode(', ', $context_labels); + $form['cache']['contexts']['#description'] .= ' ' . t('This block is always varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list)); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getCacheKeys() { + // Return the required cache contexts, merged with the user-configured cache + // contexts, if any. + return array_merge($this->getRequiredCacheContexts(), parent::getCacheKeys()); + } + + /** + * Returns the cache contexts required for this block. + * + * Two cache contexts are required: cache by URL and by user's roles. + * + * @return array + * The required cache contexts IDs. + */ + protected function getRequiredCacheContexts() { + return array('cache_context.url', 'cache_context.user.roles'); + } + } diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemPoweredByBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemPoweredByBlock.php index 742af7b..b0878f8 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemPoweredByBlock.php +++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemPoweredByBlock.php @@ -8,6 +8,7 @@ namespace Drupal\system\Plugin\Block; use Drupal\block\BlockBase; +use Drupal\Core\Cache\Cache; /** * Provides a 'Powered by Drupal' block. @@ -26,4 +27,34 @@ public function build() { return array('#markup' => '' . t('Powered by Drupal', array('@poweredby' => 'http://drupal.org')) . ''); } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // The 'Powered by Drupal' block is permanently cacheable, because its + // contents can never change. + $form['cache']['#disabled'] = TRUE; + $form['cache']['max_age']['#value'] = Cache::PERMANENT; + $form['cache']['#description'] = t('This block is always cached forever, it is not configurable.'); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + } diff --git a/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php index 5542808..0c1d37f 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php +++ b/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php @@ -52,7 +52,6 @@ public function getDerivativeDefinitions(array $base_plugin_definition) { foreach ($this->menuStorage->loadMultiple() as $menu => $entity) { $this->derivatives[$menu] = $base_plugin_definition; $this->derivatives[$menu]['admin_label'] = $entity->label(); - $this->derivatives[$menu]['cache'] = DRUPAL_NO_CACHE; } return $this->derivatives; } 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 b245c34..9c2232a 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php @@ -64,9 +64,28 @@ function testPageCacheTags() { 'promote' => NODE_PROMOTED, )); + // Place a block, but only make it visible on full node page 2. + $block = $this->drupalPlaceBlock('views_block:comments_recent-block_1', array( + 'visibility' => array( + 'path' => array( + 'visibility' => BLOCK_VISIBILITY_LISTED, + 'pages' => 'node/' . $node_2->id(), + ), + ) + )); + // Full node page 1. $this->verifyPageCacheTags('node/' . $node_1->id(), array( 'content:1', + 'block_view:1', + 'block:bartik_content', + 'block:bartik_login', + 'block:bartik_footer', + 'block:bartik_powered', + 'block_plugin:system_main_block', + 'block_plugin:user_login_block', + 'block_plugin:system_menu_block__footer', + 'block_plugin:system_powered_by_block', 'node_view:1', 'node:' . $node_1->id(), 'user:' . $author_1->id(), @@ -78,6 +97,17 @@ function testPageCacheTags() { // Full node page 2. $this->verifyPageCacheTags('node/' . $node_2->id(), array( 'content:1', + 'block_view:1', + 'block:bartik_content', + 'block:bartik_login', + 'block:' . $block->id(), + 'block:bartik_footer', + 'block:bartik_powered', + 'block_plugin:system_main_block', + 'block_plugin:user_login_block', + 'block_plugin:views_block__comments_recent-block_1', + 'block_plugin:system_menu_block__footer', + 'block_plugin:system_powered_by_block', 'node_view:1', 'node:' . $node_2->id(), 'user:' . $author_2->id(), diff --git a/core/modules/user/lib/Drupal/user/Cache/UserCacheContext.php b/core/modules/user/lib/Drupal/user/Cache/UserCacheContext.php new file mode 100644 index 0000000..77b833e --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Cache/UserCacheContext.php @@ -0,0 +1,42 @@ +user = $user; + } + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return t('User'); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return "u." . $this->user->id(); + } + +} diff --git a/core/modules/user/lib/Drupal/user/Cache/UserRolesCacheContext.php b/core/modules/user/lib/Drupal/user/Cache/UserRolesCacheContext.php new file mode 100644 index 0000000..fb98c1a --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Cache/UserRolesCacheContext.php @@ -0,0 +1,42 @@ +user = $user; + } + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return t("User's roles"); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return 'r.' . implode(',', $this->user->getRoles()); + } + +} diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml index 23d8706..8a0db43 100644 --- a/core/modules/user/user.services.yml +++ b/core/modules/user/user.services.yml @@ -15,6 +15,16 @@ services: class: Drupal\user\Access\LoginStatusCheck tags: - { name: access_check, applies_to: _user_is_logged_in } + cache_context.user: + class: Drupal\user\Cache\UserCacheContext + arguments: ['@current_user'] + tags: + - { name: cache.context} + cache_context.user.roles: + class: Drupal\user\Cache\UserRolesCacheContext + arguments: ['@current_user'] + tags: + - { name: cache.context} user.data: class: Drupal\user\UserData arguments: ['@database'] 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 482c25f..2648919 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. - if (empty($this->configuration['views_label'])) { - $this->configuration['label'] = Xss::filterAdmin($this->view->getTitle()); - } - else { - $this->configuration['label'] = $this->configuration['views_label']; + // Override the label to the dynamic title configured in the view. + if (empty($this->configuration['views_label']) && $this->view->getTitle()) { + $output['#title'] = 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(); diff --git a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlockBase.php b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlockBase.php index 56f3c86..3c228d1 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlockBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlockBase.php @@ -99,10 +99,7 @@ public function access(AccountInterface $account) { * {@inheritdoc} */ public function defaultConfiguration() { - $settings = array(); - $settings['views_label'] = ''; - - return $settings; + return array('views_label' => ''); } /** diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsBlock.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsBlock.php index 30d242a..c8ba106 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsBlock.php +++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsBlock.php @@ -102,7 +102,6 @@ public function getDerivativeDefinitions(array $base_plugin_definition) { $this->derivatives[$delta] = array( 'category' => $display->getOption('block_category'), 'admin_label' => $desc, - 'cache' => $display->getCacheType() ); $this->derivatives[$delta] += $base_plugin_definition; } diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsExposedFilterBlock.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsExposedFilterBlock.php index 96bdd4c..a85efa3 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsExposedFilterBlock.php +++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsExposedFilterBlock.php @@ -93,7 +93,6 @@ public function getDerivativeDefinitions(array $base_plugin_definition) { $desc = t('Exposed form: @view-@display_id', array('@view' => $view->id(), '@display_id' => $display->display['id'])); $this->derivatives[$delta] = array( 'admin_label' => $desc, - 'cache' => DRUPAL_NO_CACHE, ); $this->derivatives[$delta] += $base_plugin_definition; } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php index db9eff1..c407cd7 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php @@ -2383,7 +2383,6 @@ public function getSpecialBlocks() { $blocks[$delta] = array( 'info' => $desc, - 'cache' => DRUPAL_NO_CACHE, ); } diff --git a/core/modules/views/tests/Drupal/views/Tests/Plugin/Block/ViewsBlockTest.php b/core/modules/views/tests/Drupal/views/Tests/Plugin/Block/ViewsBlockTest.php index 15d4d6e..d0a4d37 100644 --- a/core/modules/views/tests/Drupal/views/Tests/Plugin/Block/ViewsBlockTest.php +++ b/core/modules/views/tests/Drupal/views/Tests/Plugin/Block/ViewsBlockTest.php @@ -15,9 +15,6 @@ if (!defined('BLOCK_LABEL_VISIBLE')) { define('BLOCK_LABEL_VISIBLE', 'visible'); } -if (!defined('DRUPAL_NO_CACHE')) { - define('DRUPAL_NO_CACHE', -1); -} /** * Tests the views block plugin. diff --git a/core/profiles/minimal/config/block.block.stark_admin.yml b/core/profiles/minimal/config/block.block.stark_admin.yml index f3c1735..a2a51d1 100644 --- a/core/profiles/minimal/config/block.block.stark_admin.yml +++ b/core/profiles/minimal/config/block.block.stark_admin.yml @@ -9,7 +9,6 @@ settings: label: Administration module: system label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/minimal/config/block.block.stark_login.yml b/core/profiles/minimal/config/block.block.stark_login.yml index 4ca8266..a4a17cc 100644 --- a/core/profiles/minimal/config/block.block.stark_login.yml +++ b/core/profiles/minimal/config/block.block.stark_login.yml @@ -9,7 +9,6 @@ settings: label: 'User login' module: user label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/minimal/config/block.block.stark_tools.yml b/core/profiles/minimal/config/block.block.stark_tools.yml index a452643..648c266 100644 --- a/core/profiles/minimal/config/block.block.stark_tools.yml +++ b/core/profiles/minimal/config/block.block.stark_tools.yml @@ -9,7 +9,6 @@ settings: label: Tools module: system label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_breadcrumbs.yml b/core/profiles/standard/config/block.block.bartik_breadcrumbs.yml index 3465368..a525a35 100644 --- a/core/profiles/standard/config/block.block.bartik_breadcrumbs.yml +++ b/core/profiles/standard/config/block.block.bartik_breadcrumbs.yml @@ -9,7 +9,6 @@ settings: label: Breadcrumbs module: system label_display: '0' - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_content.yml b/core/profiles/standard/config/block.block.bartik_content.yml index 8146a54..537bcae 100644 --- a/core/profiles/standard/config/block.block.bartik_content.yml +++ b/core/profiles/standard/config/block.block.bartik_content.yml @@ -9,7 +9,6 @@ settings: label: 'Main page content' module: system label_display: '0' - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_footer.yml b/core/profiles/standard/config/block.block.bartik_footer.yml index 76f9508..4f4db57 100644 --- a/core/profiles/standard/config/block.block.bartik_footer.yml +++ b/core/profiles/standard/config/block.block.bartik_footer.yml @@ -9,7 +9,6 @@ settings: label: 'Footer menu' module: system label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_help.yml b/core/profiles/standard/config/block.block.bartik_help.yml index 5806db7..33ccc5a 100644 --- a/core/profiles/standard/config/block.block.bartik_help.yml +++ b/core/profiles/standard/config/block.block.bartik_help.yml @@ -9,7 +9,6 @@ settings: label: 'System Help' module: system label_display: '0' - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_login.yml b/core/profiles/standard/config/block.block.bartik_login.yml index 7fc17cb..1f96baa 100644 --- a/core/profiles/standard/config/block.block.bartik_login.yml +++ b/core/profiles/standard/config/block.block.bartik_login.yml @@ -9,7 +9,6 @@ settings: label: 'User login' module: user label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_powered.yml b/core/profiles/standard/config/block.block.bartik_powered.yml index 0e3ebab..65f24ad 100644 --- a/core/profiles/standard/config/block.block.bartik_powered.yml +++ b/core/profiles/standard/config/block.block.bartik_powered.yml @@ -9,7 +9,6 @@ settings: label: 'Powered by Drupal' module: system label_display: '0' - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_search.yml b/core/profiles/standard/config/block.block.bartik_search.yml index f68adbc..46279d7 100644 --- a/core/profiles/standard/config/block.block.bartik_search.yml +++ b/core/profiles/standard/config/block.block.bartik_search.yml @@ -9,7 +9,6 @@ settings: label: Search module: search label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.bartik_tools.yml b/core/profiles/standard/config/block.block.bartik_tools.yml index 257990d..d845957 100644 --- a/core/profiles/standard/config/block.block.bartik_tools.yml +++ b/core/profiles/standard/config/block.block.bartik_tools.yml @@ -9,7 +9,6 @@ settings: label: Tools module: system label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.seven_breadcrumbs.yml b/core/profiles/standard/config/block.block.seven_breadcrumbs.yml index b56f633..1a6318c 100644 --- a/core/profiles/standard/config/block.block.seven_breadcrumbs.yml +++ b/core/profiles/standard/config/block.block.seven_breadcrumbs.yml @@ -9,7 +9,6 @@ settings: label: Breadcrumbs module: system label_display: '0' - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.seven_content.yml b/core/profiles/standard/config/block.block.seven_content.yml index 23a6568..4f60d5e 100644 --- a/core/profiles/standard/config/block.block.seven_content.yml +++ b/core/profiles/standard/config/block.block.seven_content.yml @@ -9,7 +9,6 @@ settings: label: 'Main page content' module: system label_display: '0' - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.seven_help.yml b/core/profiles/standard/config/block.block.seven_help.yml index 77918e5..dcc87b7 100644 --- a/core/profiles/standard/config/block.block.seven_help.yml +++ b/core/profiles/standard/config/block.block.seven_help.yml @@ -9,7 +9,6 @@ settings: label: 'System Help' module: system label_display: '0' - cache: -1 visibility: path: visibility: 0 diff --git a/core/profiles/standard/config/block.block.seven_login.yml b/core/profiles/standard/config/block.block.seven_login.yml index 239bd36..f2602cc 100644 --- a/core/profiles/standard/config/block.block.seven_login.yml +++ b/core/profiles/standard/config/block.block.seven_login.yml @@ -9,7 +9,6 @@ settings: label: 'User login' module: user label_display: visible - cache: -1 visibility: path: visibility: 0 diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php new file mode 100644 index 0000000..2ed2cd6 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php @@ -0,0 +1,96 @@ + 'CacheContext test', + 'description' => 'Tests cache contexts.', + 'group' => 'Cache', + ); + } + + public function atestContextPlaceholdersAreReplaced() { + $container = $this->getMockContainer(); + $container->expects($this->once()) + ->method("get") + ->with("cache_context.foo") + ->will($this->returnValue(new FooCacheContext())); + + $cache_contexts = new CacheContexts($container, $this->getContextsFixture()); + + $new_keys = $cache_contexts->convertTokensToKeys( + array("non-cache-context", "cache_context.foo") + ); + + $expected = array("non-cache-context", "bar"); + $this->assertEquals($expected, $new_keys); + } + + public function testAvailableContextStrings() { + $cache_contexts = new CacheContexts($this->getMockContainer(), $this->getContextsFixture()); + $contexts = $cache_contexts->getAll(); + $this->assertEquals(array("cache_context.foo"), $contexts); + } + + public function atestAvailableContextLabels() { + $container = $this->getMockContainer(); + $container->expects($this->once()) + ->method("get") + ->with("cache_context.foo") + ->will($this->returnValue(new FooCacheContext())); + + $cache_contexts = new CacheContexts($this->getMockContainer(), $this->getContextsFixture()); + $labels = $cache_contexts->getLabels(); + $expected = array("cache_context.foo" => "Foo"); + $this->assertEquals($expected, $labels); + } + + protected function getContextsFixture() { + return array('cache_context.foo'); + } + + protected function getMockContainer() { + return $this->getMockBuilder('Drupal\Core\DependencyInjection\Container') + ->disableOriginalConstructor() + ->getMock(); + } +}