diff --git a/core/core.services.yml b/core/core.services.yml
index 802d143..7dd2719 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -4,6 +4,24 @@ 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_context.language:
+ class: Drupal\Core\Cache\LanguageCacheContext
+ arguments: ['@language_manager']
+ tags:
+ - { name: cache.context}
+ cache_context.theme:
+ class: Drupal\Core\Cache\ThemeCacheContext
+ arguments: ['@request', '@theme.negotiator']
+ 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 9e5c50f..e38cfda 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -900,6 +900,13 @@ 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;
+ // RFC 2616, section 14.21 says: 'To mark a response as "never expires," an
+ // origin server sends an Expires date approximately one year from the time
+ // the response is sent. HTTP/1.1 servers SHOULD NOT send Expires dates more
+ // than one year in 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 a2226c8..cdb4380 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -132,78 +132,6 @@
const JS_THEME = 100;
/**
- * @defgroup block_caching Block Caching
- * @{
- * Constants that define each block's caching state.
- *
- * Modules specify how their blocks can be cached in their hook_block_info()
- * implementations. Caching can be turned off (DRUPAL_NO_CACHE), managed by the
- * module declaring the block (DRUPAL_CACHE_CUSTOM), or managed by the core
- * Block module. If the Block module is managing the cache, you can specify that
- * the block is the same for every page and user (DRUPAL_CACHE_GLOBAL), or that
- * it can change depending on the page (DRUPAL_CACHE_PER_PAGE) or by user
- * (DRUPAL_CACHE_PER_ROLE or DRUPAL_CACHE_PER_USER). Page and user settings can
- * be combined with a bitwise-binary or operator; for example,
- * DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE means that the block can change
- * depending on the user role or page it is on.
- *
- * The block cache is cleared when the 'content' cache tag is invalidated,
- * following the same pattern as the page cache (node, comment, user, taxonomy
- * added or updated...).
- *
- * Note that user 1 is excluded from block caching.
- */
-
-/**
- * The block should not get cached.
- *
- * This setting should be used:
- * - For simple blocks (notably those that do not perform any db query), where
- * querying the db cache would be more expensive than directly generating the
- * content.
- * - For blocks that change too frequently.
- */
-const DRUPAL_NO_CACHE = -1;
-
-/**
- * The block is handling its own caching in its hook_block_view().
- *
- * This setting is useful when time based expiration is needed or a site uses a
- * node access which invalidates standard block cache.
- */
-const DRUPAL_CACHE_CUSTOM = -2;
-
-/**
- * The block or element can change depending on the user's roles.
- *
- * This is the default setting for blocks, used when the block does not specify
- * anything.
- */
-const DRUPAL_CACHE_PER_ROLE = 0x0001;
-
-/**
- * The block or element can change depending on the user.
- *
- * This setting can be resource-consuming for sites with large number of users,
- * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
- */
-const DRUPAL_CACHE_PER_USER = 0x0002;
-
-/**
- * The block or element can change depending on the page being viewed.
- */
-const DRUPAL_CACHE_PER_PAGE = 0x0004;
-
-/**
- * The block or element is the same for every user and page that it is visible.
- */
-const DRUPAL_CACHE_GLOBAL = 0x0008;
-
-/**
- * @} End of "defgroup block_caching".
- */
-
-/**
* The delimiter used to split plural strings.
*
* This is the ETX (End of text) character and is used as a minimal means to
@@ -3718,16 +3646,12 @@ function drupal_render_page($page) {
* associative array with one or several of the following keys:
* - 'keys': An array of one or more keys that identify the element. If
* 'keys' is set, the cache ID is created automatically from these keys.
- * See drupal_render_cid_create().
- * - 'granularity' (optional): Define the cache granularity using binary
- * combinations of the cache granularity constants, e.g.
- * DRUPAL_CACHE_PER_USER to cache for each user separately or
- * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for
- * each page and role. If not specified the element is cached globally for
- * each theme and language.
+ * 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). See drupal_render_cid_create().
* - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is
- * required. If 'cid' is set, 'keys' and 'granularity' are ignored. Use
- * only if you have special requirements.
+ * required. If 'cid' is set, 'keys' is ignored. Use only if you have
+ * special requirements.
* - 'expire': Set to one of the cache lifetime constants.
* - 'bin': Specify a cache bin to cache the element in. Default is 'cache'.
* - If this element has #type defined and the default attributes for this
@@ -4511,100 +4435,10 @@ function drupal_cache_tags_page_get(Response $response) {
}
/**
- * Prepares an element for caching based on a query.
- *
- * This smart caching strategy saves Drupal from querying and rendering to HTML
- * when the underlying query is unchanged.
- *
- * Expensive queries should use the query builder to create the query and then
- * call this function. Executing the query and formatting results should happen
- * in a #pre_render callback.
- *
- * @param $query
- * A select query object as returned by db_select().
- * @param $function
- * The name of the function doing this caching. A _pre_render suffix will be
- * added to this string and is also part of the cache key in
- * drupal_render_cache_set() and drupal_render_cache_get().
- * @param $expire
- * The cache expire time, passed eventually to \Drupal::cache()->set().
- * @param $granularity
- * One or more granularity constants passed to drupal_render_cid_parts().
- *
- * @return
- * A renderable array with the following keys and values:
- * - #query: The passed-in $query.
- * - #pre_render: $function with a _pre_render suffix.
- * - #cache: An associative array prepared for drupal_render_cache_set().
- */
-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())));
- return array(
- '#query' => $query,
- '#pre_render' => array($function . '_pre_render'),
- '#cache' => array(
- 'keys' => $cache_keys,
- 'expire' => $expire,
- ),
- );
-}
-
-/**
- * Returns cache ID parts for building a cache ID.
- *
- * @param $granularity
- * One or more cache granularity constants. For example, to cache separately
- * for each user, use DRUPAL_CACHE_PER_USER. To cache separately for each
- * page and role, use the expression:
- * @code
- * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE
- * @endcode
- *
- * @return
- * An array of cache ID parts, always containing the active theme. If the
- * locale module is enabled it also contains the active language. If
- * $granularity was passed in, more parts are added.
- */
-function drupal_render_cid_parts($granularity = NULL) {
- global $theme, $base_root;
-
- $cid_parts[] = $theme;
-
- // If we have only one language enabled we do not need it as cid part.
- $language_manager = \Drupal::languageManager();
- if ($language_manager->isMultilingual()) {
- foreach ($language_manager->getLanguageTypes() as $type) {
- $cid_parts[] = $language_manager->getCurrentLanguage($type)->id;
- }
- }
-
- if (!empty($granularity)) {
- // 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
- // resource drag for sites with many users, so when a module is being
- // equivocal, we favor the less expensive 'PER_ROLE' pattern.
- if ($granularity & DRUPAL_CACHE_PER_ROLE) {
- $cid_parts[] = 'r.' . implode(',', \Drupal::currentUser()->getRoles());
- }
- elseif ($granularity & DRUPAL_CACHE_PER_USER) {
- $cid_parts[] = 'u.' . \Drupal::currentUser()->id();
- }
-
- if ($granularity & DRUPAL_CACHE_PER_PAGE) {
- $cid_parts[] = $base_root . request_uri();
- }
- }
-
- return $cid_parts;
-}
-
-/**
* Creates the cache ID for a renderable element.
*
* This creates the cache ID string, either by returning the #cache['cid']
- * property if present or by building the cache ID out of the #cache['keys']
- * and, optionally, the #cache['granularity'] properties.
+ * property if present or by building the cache ID out of the #cache['keys'].
*
* @param $elements
* A renderable array.
@@ -4617,10 +4451,12 @@ function drupal_render_cid_create($elements) {
return $elements['#cache']['cid'];
}
elseif (isset($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));
- return implode(':', $cid_parts);
+ // 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']);
+ return implode(':', $keys);
}
return FALSE;
}
diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php
index c2f5fe6..a9c18ae 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,26 @@ public static function getBins() {
return $bins;
}
+ /**
+ * Generates a hash from a query object, to be used as part of the cache key.
+ *
+ * This smart caching strategy saves Drupal from querying and rendering to
+ * HTML when the underlying query is unchanged.
+ *
+ * Expensive queries should use the query builder to create the query and then
+ * call this function. Executing the query and formatting results should
+ * happen in a #pre_render callback.
+ *
+ * @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 cache configuration forms.
+ *
+ * @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..645df03
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheContextsPass.php
@@ -0,0 +1,28 @@
+findTaggedServiceIds('cache.context'));
+ $container->setParameter('cache_contexts', $cache_contexts);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/LanguageCacheContext.php b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php
new file mode 100644
index 0000000..b73652f
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php
@@ -0,0 +1,54 @@
+languageManager = $language_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getLabel() {
+ return t('Language');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContext() {
+ $context_parts = array();
+ if ($this->language_manager->isMultilingual()) {
+ foreach ($this->language_manager->getLanguageTypes() as $type) {
+ $context_parts[] = $this->language_manager->getCurrentLanguage($type)->id;
+ }
+ }
+ return implode(':', $context_parts);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/ThemeCacheContext.php b/core/lib/Drupal/Core/Cache/ThemeCacheContext.php
new file mode 100644
index 0000000..d777471
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/ThemeCacheContext.php
@@ -0,0 +1,59 @@
+request = $request;
+ $this->themeNegotiator = $theme_negotiator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getLabel() {
+ return t('Theme');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContext() {
+ return $this->themeNegotiator->determineActiveTheme($this->request) ?: 'stark';
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/UrlCacheContext.php b/core/lib/Drupal/Core/Cache/UrlCacheContext.php
new file mode 100644
index 0000000..596e134
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/UrlCacheContext.php
@@ -0,0 +1,48 @@
+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 927189e..aa822fd 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -7,6 +7,7 @@
namespace Drupal\Core;
+use Drupal\Core\Cache\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\Config\ConfigFactoryOverridePass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
@@ -72,6 +73,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/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 7cbadb2..2e48ecc 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -158,8 +158,14 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
// type configuration.
if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
$return['#cache'] += array(
- 'keys' => array('entity_view', $this->entityTypeId, $entity->id(), $view_mode),
- 'granularity' => DRUPAL_CACHE_PER_ROLE,
+ 'keys' => array(
+ 'entity_view',
+ $this->entityTypeId,
+ $entity->id(),
+ $view_mode,
+ 'cache_context.theme',
+ 'cache_context.user.roles',
+ ),
'bin' => $this->cacheBin,
);
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..dc7c0e0 100644
--- a/core/modules/block/config/schema/block.schema.yml
+++ b/core/modules/block/config/schema/block.schema.yml
@@ -73,8 +73,18 @@ 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'
+ contexts:
+ type: sequence
+ label: 'Vary by context'
+ sequence:
+ - type: string
+ label: 'Context'
status:
type: boolean
label: 'Status'
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Block/CustomBlockBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Block/CustomBlockBlock.php
index fe7f461..dab9b26 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Block/CustomBlockBlock.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Block/CustomBlockBlock.php
@@ -93,6 +93,12 @@ public function defaultConfiguration() {
'status' => TRUE,
'info' => '',
'view_mode' => 'full',
+ // Modify the default max age for custom block blocks: modifications made
+ // to them will automatically invalidate corresponding cache tags, thus
+ // allowing us to cache custom block blocks forever.
+ 'cache' => array(
+ 'max_age' => \Drupal\Core\Cache\Cache::PERMANENT,
+ ),
);
}
diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php
index d278663..2dd5cde 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,10 +128,50 @@ 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,
+ );
+ $contexts = \Drupal::service("cache_contexts")->getLabels();
+ // Blocks are always rendered in a "per theme" cache context. No need to
+ // show that option to the end user.
+ unset($contexts['cache_context.theme']);
+ $form['cache']['contexts'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Vary by context'),
+ '#description' => t('The contexts this cached block must be varied by.'),
+ '#default_value' => $this->configuration['cache']['contexts'],
+ '#options' => $contexts,
+ '#states' => array(
+ 'disabled' => array(
+ ':input[name="settings[cache][max_age]"]' => array('value' => (string) 0),
+ ),
+ ),
);
+ if (count($this->getRequiredCacheContexts()) > 0) {
+ // 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));
+ }
// Add plugin-specific settings for this block type.
$form += $this->blockForm($form, $form_state);
@@ -134,6 +194,9 @@ public function blockForm($form, &$form_state) {
* @see \Drupal\block\BlockBase::blockValidate()
*/
public function validateConfigurationForm(array &$form, array &$form_state) {
+ // Transform the #type = checkboxes value to a numerically indexed array.
+ $form_state['values']['cache']['contexts'] = array_values(array_filter($form_state['values']['cache']['contexts']));
+
$this->blockValidate($form, $form_state);
}
@@ -156,6 +219,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 +253,59 @@ public function getMachineNameSuggestion() {
return $transliterated;
}
+ /**
+ * Returns the cache contexts required for this block.
+ *
+ * @return array
+ * The required cache contexts IDs.
+ */
+ protected function getRequiredCacheContexts() {
+ return array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheKeys() {
+ // Return the required cache contexts, merged with the user-configured cache
+ // contexts, if any.
+ return array_merge($this->getRequiredCacheContexts(), $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->getCacheMaxAge();
+ 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..4ccfcf4 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,62 @@ 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 'cache_context.user.roles').
+ $default_cache_keys = array(
+ 'entity_view',
+ 'block',
+ $entity->id(),
+ $entity->langcode,
+ // Blocks are always rendered in a "per theme" cache context.
+ 'cache_context.theme',
+ );
+ $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 +108,65 @@ 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: render as the empty string and ensure this block is
+ // render cached, so we can avoid the work of having to repeatedly
+ // determine whether the block is empty. E.g. modifying or adding entities
+ // could cause the block to no longer be empty.
+ return array(
+ '#markup' => '',
+ '#cache' => $build['#cache'],
+ );
+ }
+ 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..93ce334 100644
--- a/core/modules/block/lib/Drupal/block/Entity/Block.php
+++ b/core/modules/block/lib/Drupal/block/Entity/Block.php
@@ -7,10 +7,12 @@
namespace Drupal\block\Entity;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\block\BlockPluginBag;
use Drupal\block\BlockInterface;
use Drupal\Core\Config\Entity\EntityWithPluginBagInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
/**
* Defines a Block configuration entity class.
@@ -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..5e7d9d0 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,12 @@ 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');
+ $contexts = \Drupal::service("cache_contexts")->getLabels();
+ unset($contexts['cache_context.theme']);
$expected_form = array(
'module' => array(
'#type' => 'value',
@@ -84,8 +93,27 @@ 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' => 'checkboxes',
+ '#title' => t('Vary by context'),
+ '#description' => t('The contexts this cached block must be varied by.'),
+ '#default_value' => array(),
+ '#options' => $contexts,
+ '#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 8f2ddc0..986ade7 100644
--- a/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php
@@ -176,19 +176,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() {
@@ -282,19 +269,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..6a9cfff 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,21 @@ 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}
+ */
+ protected function getRequiredCacheContexts() {
+ // The "Book navigation" block must be cached per URL and per role: the
+ // "active" menu link may differ per URL and different roles may have access
+ // to different menu links.
+ return array('cache_context.url', 'cache_context.user.roles');
+ }
+
}
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 959a700..c0e8d3a 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -539,23 +539,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 88c0736..b673d66 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($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/MenuCacheTagsTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
index c09875c..e8f00a3 100644
--- a/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
@@ -58,7 +58,14 @@ public function testMenuBlock() {
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
- $this->verifyPageCache($path, 'HIT', array('content:1', 'menu:llama'));
+ $expected_tags = array(
+ 'content:1',
+ 'block_view:1',
+ 'block:' . $block->id(),
+ 'block_plugin:system_menu_block__llama',
+ 'menu:llama',
+ );
+ $this->verifyPageCache($path, 'HIT', $expected_tags);
// Verify that after modifying the menu, there is a cache miss.
@@ -101,7 +108,7 @@ public function testMenuBlock() {
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
- $this->verifyPageCache($path, 'HIT', array('content:1', 'menu:llama'));
+ $this->verifyPageCache($path, 'HIT', $expected_tags);
// Verify that after deleting the menu, there is a cache miss.
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 30a6280..6fd6ab2 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -353,6 +353,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.
@@ -369,6 +370,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..c594b98 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 defaultConfiguration() {
+ // Modify the default max age for the System Help block: help text is static
+ // for a given URL, except when a module is updated, in which case
+ // update.php must be run, which clears all caches. Thus it's safe to cache
+ // the output for this block forever on a per-URL basis.
+ return array('cache' => array('max_age' => \Drupal\Core\Cache\Cache::PERMANENT));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRequiredCacheContexts() {
+ // The "System Help" block must be cached per URL: help is defined for a
+ // given path, and does not come with any access restrictions.
+ return array('cache_context.url');
+ }
+
}
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..15cb1d8 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
@@ -7,8 +7,8 @@
namespace Drupal\system\Plugin\Block;
+use Drupal\Component\Utility\NestedArray;
use Drupal\block\BlockBase;
-use Drupal\Core\Session\AccountInterface;
/**
* Provides a generic Menu block.
@@ -30,4 +30,41 @@ 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, therefore 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 getCacheTags() {
+ // Even when the menu block renders to the empty string for a user, we want
+ // the cache tag for this menu to be set: whenever the menu is changed, this
+ // menu block must also be re-rendered for that user, because maybe a menu
+ // link that is accessible for that user has been added.
+ $tags = array('menu' => array($this->getDerivativeId()));
+ return NestedArray::mergeDeep(parent::getCacheTags(), $tags);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRequiredCacheContexts() {
+ // Menu blocks must be cached per URL and per role: the "active" menu link
+ // may differ per URL and different roles may have access to different menu
+ // links.
+ 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 372de9b..72a4c49 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($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..cc584a3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -64,13 +64,35 @@ 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_tools',
+ 'block:bartik_login',
+ 'block:bartik_footer',
+ 'block:bartik_powered',
+ 'block_plugin:system_main_block',
+ 'block_plugin:system_menu_block__tools',
+ '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(),
'filter_format:basic_html',
+ 'menu:tools',
'menu:footer',
'menu:main',
));
@@ -78,10 +100,24 @@ function testPageCacheTags() {
// Full node page 2.
$this->verifyPageCacheTags('node/' . $node_2->id(), array(
'content:1',
+ 'block_view:1',
+ 'block:bartik_content',
+ 'block:bartik_tools',
+ 'block:bartik_login',
+ 'block:' . $block->id(),
+ 'block:bartik_footer',
+ 'block:bartik_powered',
+ 'block_plugin:system_main_block',
+ 'block_plugin:system_menu_block__tools',
+ '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(),
'filter_format:full_html',
+ 'menu:tools',
'menu:footer',
'menu:main',
));
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
index 2d0369b..0379aa7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
@@ -144,7 +144,7 @@ public function testEntityViewBuilderCacheToggling() {
// Test a view mode in default conditions: render caching is enabled for
// the entity type and the view mode.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
- $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags', 'keys', 'granularity', 'bin') , 'A view mode with render cache enabled has the correct output (cache tags, keys, granularity and bin).');
+ $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags', 'keys', 'bin') , 'A view mode with render cache enabled has the correct output (cache tags, keys and bin).');
// Test that a view mode can opt out of render caching.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'test');
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..d9d880e
--- /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..0f7975d
--- /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 36d1e79..7f73ccc 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 a7fb041..9541124 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($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 0423bb5..29439a6 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($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 bc99b02..b18fff3 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..6e5864d
--- /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 testContextPlaceholdersAreReplaced() {
+ $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 testAvailableContextLabels() {
+ $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());
+ $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();
+ }
+}