Issue #2158003 by Wim Leers, msonnabaum, effulgentsia, moshe weitzman: Remove Block Cache API in favor of blocks returning #cache with cache tags.
parent
99c739a05b
commit
a6ffb28261
|
@ -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']
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -131,78 +131,6 @@ const JS_DEFAULT = 0;
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -4510,101 +4434,11 @@ function drupal_cache_tags_page_get(Response $response) {
|
|||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
|
||||
/**
|
||||
* Helper methods for cache.
|
||||
*
|
||||
|
@ -72,4 +74,26 @@ class Cache {
|
|||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ interface CacheBackendInterface {
|
|||
/**
|
||||
* 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.
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Cache\CacheContextInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
/**
|
||||
* Provides an interface for defining a cache context service.
|
||||
*/
|
||||
interface CacheContextInterface {
|
||||
|
||||
/**
|
||||
* Returns the label of the cache context.
|
||||
*
|
||||
* @return string
|
||||
* The label of the cache context.
|
||||
*/
|
||||
public static function getLabel();
|
||||
|
||||
/**
|
||||
* Returns the string representation of the cache context.
|
||||
*
|
||||
* A cache context service's name is used as a token (placeholder) cache key,
|
||||
* and is then replaced with the string returned by this method.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the cache context.
|
||||
*/
|
||||
public function getContext();
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Cache\CacheContexts.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines the CacheContexts service.
|
||||
*
|
||||
* Converts string placeholders into their final string values, to be used as a
|
||||
* cache key.
|
||||
*/
|
||||
class CacheContexts {
|
||||
|
||||
/**
|
||||
* The service container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Available cache contexts and corresponding labels.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contexts;
|
||||
|
||||
/**
|
||||
* Constructs a CacheContexts object.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The current service container.
|
||||
* @param array $contexts
|
||||
* An array of key-value pairs, where the keys are service names (which also
|
||||
* serve as the corresponding cache context token) and the values are the
|
||||
* cache context labels.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container, array $contexts) {
|
||||
$this->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Cache\CacheContextsPass.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Adds cache_contexts parameter to the container.
|
||||
*/
|
||||
class CacheContextsPass implements CompilerPassInterface {
|
||||
|
||||
/**
|
||||
* Implements CompilerPassInterface::process().
|
||||
*
|
||||
* Collects the cache contexts into the cache_contexts parameter.
|
||||
*/
|
||||
public function process(ContainerBuilder $container) {
|
||||
$cache_contexts = array_keys($container->findTaggedServiceIds('cache.context'));
|
||||
$container->setParameter('cache_contexts', $cache_contexts);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Cache\LanguageCacheContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
|
||||
/**
|
||||
* Defines the LanguageCacheContext service, for "per language" caching.
|
||||
*/
|
||||
class LanguageCacheContext implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Constructs a new LanguageCacheContext service.
|
||||
*
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(LanguageManagerInterface $language_manager) {
|
||||
$this->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Cache\LanguageCacheContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\Core\Theme\ThemeNegotiatorInterface;
|
||||
|
||||
/**
|
||||
* Defines the ThemeCacheContext service, for "per theme" caching.
|
||||
*/
|
||||
class ThemeCacheContext implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* The current request.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The theme negotiator.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\ThemeNegotiator
|
||||
*/
|
||||
protected $themeNegotiator;
|
||||
|
||||
/**
|
||||
* Constructs a new ThemeCacheContext service.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HTTP request object.
|
||||
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
|
||||
* The theme negotiator.
|
||||
*/
|
||||
public function __construct(Request $request, ThemeNegotiatorInterface $theme_negotiator) {
|
||||
$this->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';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Cache\UrlCacheContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Defines the UrlCacheContext service, for "per page" caching.
|
||||
*/
|
||||
class UrlCacheContext implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* The current request.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Constructs a new UrlCacheContext service.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HTTP request object.
|
||||
*/
|
||||
public function __construct(Request $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getLabel() {
|
||||
return t('URL');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
return $this->request->getUri();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 @@ class CoreServiceProvider implements ServiceProviderInterface {
|
|||
$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
|
||||
|
|
|
@ -158,8 +158,14 @@ class EntityViewBuilder extends EntityControllerBase implements EntityController
|
|||
// 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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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.
|
||||
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];
|
||||
}
|
||||
|
@ -364,26 +326,6 @@ function block_load($entity_id) {
|
|||
return entity_load('block', $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().
|
||||
*/
|
||||
|
@ -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']);
|
||||
|
|
|
@ -73,8 +73,18 @@ block.block.*:
|
|||
type: string
|
||||
label: 'Display title'
|
||||
cache:
|
||||
type: mapping
|
||||
label: 'Cache settings'
|
||||
mapping:
|
||||
max_age:
|
||||
type: integer
|
||||
label: 'Cache'
|
||||
label: 'Maximum age'
|
||||
contexts:
|
||||
type: sequence
|
||||
label: 'Vary by context'
|
||||
sequence:
|
||||
- type: string
|
||||
label: 'Context'
|
||||
status:
|
||||
type: boolean
|
||||
label: 'Status'
|
||||
|
|
|
@ -93,6 +93,12 @@ class CustomBlockBlock extends BlockBase implements ContainerFactoryPluginInterf
|
|||
'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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@ namespace Drupal\block;
|
|||
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 @@ abstract class BlockBase extends PluginBase implements BlockPluginInterface {
|
|||
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 @@ abstract class BlockBase extends PluginBase implements BlockPluginInterface {
|
|||
* {@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 @@ abstract class BlockBase extends PluginBase implements BlockPluginInterface {
|
|||
'#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 <em>always</em> 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 @@ abstract class BlockBase extends PluginBase implements BlockPluginInterface {
|
|||
* @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 @@ abstract class BlockBase extends PluginBase implements BlockPluginInterface {
|
|||
$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 @@ abstract class BlockBase extends PluginBase implements BlockPluginInterface {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 @@ use Drupal\Core\Session\AccountInterface;
|
|||
* 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.
|
||||
|
|
|
@ -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,54 +45,112 @@ class BlockViewBuilder extends EntityViewBuilder {
|
|||
$plugin_id = $plugin->getPluginId();
|
||||
$base_id = $plugin->getBasePluginId();
|
||||
$derivative_id = $plugin->getDerivativeId();
|
||||
|
||||
if ($content = $plugin->build()) {
|
||||
$configuration = $plugin->getConfiguration();
|
||||
|
||||
// Create the render array for the block as a whole.
|
||||
// @see template_preprocess_block().
|
||||
$build[$key] = array(
|
||||
$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),
|
||||
'route_parameters' => array('block' => $entity->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[$key]['#configuration']['label'] = String::checkPlain($configuration['label']);
|
||||
$build[$entity_id]['#configuration']['label'] = check_plain($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;
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
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;
|
||||
|
||||
// Don't run in ::buildBlock() to ensure cache keys can be altered. If an
|
||||
// alter hook wants to modify the block contents, it can append another
|
||||
// #pre_render hook.
|
||||
$this->moduleHandler()->alter(array('block_view', "block_view_$base_id"), $build[$entity_id], $plugin);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* #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.
|
||||
*/
|
||||
public function buildBlock($build) {
|
||||
$content = $build['#block']->getPlugin()->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;
|
||||
}
|
||||
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.
|
||||
$build = array(
|
||||
'#markup' => '',
|
||||
'#cache' => $build['#cache'],
|
||||
);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
@ -98,6 +158,13 @@ class BlockViewBuilder extends EntityViewBuilder {
|
|||
/**
|
||||
* {@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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\block\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\block\BlockPluginBag;
|
||||
use Drupal\block\BlockInterface;
|
||||
|
@ -146,6 +147,32 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin
|
|||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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.
|
||||
*/
|
||||
|
|
|
@ -46,7 +46,6 @@ class Block extends DisplayPluginBase {
|
|||
|
||||
$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 @@ class Block extends DisplayPluginBase {
|
|||
'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'),
|
||||
|
@ -145,33 +137,6 @@ class Block extends DisplayPluginBase {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -196,16 +161,6 @@ class Block extends DisplayPluginBase {
|
|||
'#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 @@ class Block extends DisplayPluginBase {
|
|||
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']]);
|
||||
|
|
|
@ -61,10 +61,13 @@ class BlockCacheTest extends WebTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 @@ class BlockCacheTest extends WebTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 @@ class BlockCacheTest extends WebTestBase {
|
|||
|
||||
$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 @@ class BlockCacheTest extends WebTestBase {
|
|||
$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 @@ class BlockCacheTest extends WebTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 @@ class BlockCacheTest extends WebTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -45,10 +45,13 @@ class BlockInterfaceTest extends DrupalUnitTestBase {
|
|||
);
|
||||
$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 @@ class BlockInterfaceTest extends DrupalUnitTestBase {
|
|||
$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 @@ class BlockInterfaceTest extends DrupalUnitTestBase {
|
|||
'#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',
|
||||
|
|
|
@ -48,21 +48,24 @@ class BlockRenderOrderTest extends WebTestBase {
|
|||
'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 @@ class BlockRenderOrderTest extends WebTestBase {
|
|||
}
|
||||
}
|
||||
$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.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,6 @@ class BlockStorageUnitTest extends DrupalUnitTestBase {
|
|||
// Run each test method in the same installation.
|
||||
$this->createTests();
|
||||
$this->loadTests();
|
||||
$this->renderTests();
|
||||
$this->deleteTests();
|
||||
}
|
||||
|
||||
|
@ -103,7 +102,10 @@ class BlockStorageUnitTest extends DrupalUnitTestBase {
|
|||
'label' => '',
|
||||
'module' => 'block_test',
|
||||
'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE,
|
||||
'cache' => DRUPAL_NO_CACHE,
|
||||
'cache' => array(
|
||||
'max_age' => 0,
|
||||
'contexts' => array(),
|
||||
),
|
||||
),
|
||||
'visibility' => NULL,
|
||||
);
|
||||
|
@ -113,7 +115,7 @@ class BlockStorageUnitTest extends DrupalUnitTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests the rendering of blocks.
|
||||
* Tests the loading of blocks.
|
||||
*/
|
||||
protected function loadTests() {
|
||||
$entity = $this->controller->load('test_block');
|
||||
|
@ -127,57 +129,6 @@ class BlockStorageUnitTest extends DrupalUnitTestBase {
|
|||
$this->assertTrue($entity->uuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the rendering of blocks.
|
||||
*/
|
||||
protected function renderTests() {
|
||||
// Test the rendering of a block.
|
||||
$entity = entity_load('block', 'test_block');
|
||||
$output = entity_view($entity, 'block');
|
||||
$expected = array();
|
||||
$expected[] = '<div class="block block-block-test" id="block-test-block">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' ';
|
||||
$expected[] = '';
|
||||
$expected[] = ' <div class="content">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' </div>';
|
||||
$expected[] = '</div>';
|
||||
$expected[] = '';
|
||||
$expected_output = implode("\n", $expected);
|
||||
$this->assertEqual(drupal_render($output), $expected_output);
|
||||
|
||||
// Reset the HTML IDs so that the next render is not affected.
|
||||
drupal_static_reset('drupal_html_id');
|
||||
|
||||
// Test the rendering of a block with a given title.
|
||||
$entity = $this->controller->create(array(
|
||||
'id' => 'test_block2',
|
||||
'theme' => 'stark',
|
||||
'plugin' => 'test_html',
|
||||
'settings' => array(
|
||||
'label' => 'Powered by Bananas',
|
||||
),
|
||||
));
|
||||
$entity->save();
|
||||
$output = entity_view($entity, 'block');
|
||||
$expected = array();
|
||||
$expected[] = '<div class="block block-block-test" id="block-test-block2">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' <h2>Powered by Bananas</h2>';
|
||||
$expected[] = ' ';
|
||||
$expected[] = '';
|
||||
$expected[] = ' <div class="content">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' </div>';
|
||||
$expected[] = '</div>';
|
||||
$expected[] = '';
|
||||
$expected_output = implode("\n", $expected);
|
||||
$this->assertEqual(drupal_render($output), $expected_output);
|
||||
// Clean up this entity.
|
||||
$entity->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deleting of blocks.
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\block\Tests;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
|
@ -249,35 +250,106 @@ class BlockTest extends BlockTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 ID>"
|
||||
* - "block_plugin:<block plugin ID>"
|
||||
*/
|
||||
function testBlockRehash() {
|
||||
\Drupal::moduleHandler()->install(array('block_test'));
|
||||
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('block_test'), 'Test block module enabled.');
|
||||
public function testBlockCacheTags() {
|
||||
// The page cache only works for anonymous users.
|
||||
$this->drupalLogout();
|
||||
|
||||
// Clear the block cache to load the block_test module's block definitions.
|
||||
$this->container->get('plugin.manager.block')->clearCachedDefinitions();
|
||||
// Enable page caching.
|
||||
$config = \Drupal::config('system.performance');
|
||||
$config->set('cache.page.use_internal', 1);
|
||||
$config->set('cache.page.max_age', 300);
|
||||
$config->save();
|
||||
|
||||
// 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'));
|
||||
// Place the "Powered by Drupal" block.
|
||||
$block = $this->drupalPlaceBlock('system_powered_by_block', array('id' => 'powered', 'cache' => array('max_age' => 315360000)));
|
||||
|
||||
// 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.');
|
||||
// Prime the page cache.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
|
||||
|
||||
// Disable caching for this block.
|
||||
$block->getPlugin()->setConfigurationValue('cache', DRUPAL_NO_CACHE);
|
||||
// Verify a cache hit, but also the presence of the correct cache tags in
|
||||
// both the page and block caches.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
|
||||
$cid_parts = array(url('<front>', array('absolute' => TRUE)), 'html');
|
||||
$cid = sha1(implode(':', $cid_parts));
|
||||
$cache_entry = \Drupal::cache('page')->get($cid);
|
||||
$expected_cache_tags = array(
|
||||
'content:1',
|
||||
'block_view:1',
|
||||
'block:powered',
|
||||
'block_plugin:system_powered_by_block',
|
||||
);
|
||||
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
|
||||
$cache_entry = \Drupal::cache('block')->get('entity_view:block:powered:en:stark');
|
||||
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
|
||||
|
||||
// The "Powered by Drupal" block is modified; verify a cache miss.
|
||||
$block->set('region', 'content');
|
||||
$block->save();
|
||||
// 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('<front>');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
|
||||
|
||||
// Now we should have a cache hit again.
|
||||
$this->drupalGet('<front>');
|
||||
$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('<front>');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
|
||||
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
|
||||
$cid_parts = array(url('<front>', 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('<front>');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
|
||||
|
||||
// Now we should have a cache hit again.
|
||||
$this->drupalGet('<front>');
|
||||
$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('<front>');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,346 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\block\Tests\BlockViewBuilderTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\block\Tests;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Cache\UrlCacheContext;
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests the block view builder.
|
||||
*/
|
||||
class BlockViewBuilderTest extends DrupalUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block', 'block_test', 'system');
|
||||
|
||||
/**
|
||||
* The block being tested.
|
||||
*
|
||||
* @var \Drupal\block\Entity\BlockInterface
|
||||
*/
|
||||
protected $block;
|
||||
|
||||
/**
|
||||
* The block storage controller.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Entity\ConfigStorageControllerInterface
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Block rendering',
|
||||
'description' => 'Tests the block view builder.',
|
||||
'group' => 'Block',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->controller = $this->container
|
||||
->get('entity.manager')
|
||||
->getStorageController('block');
|
||||
|
||||
\Drupal::state()->set('block_test.content', 'Llamas > unicorns!');
|
||||
|
||||
// Create a block with only required values.
|
||||
$this->block = $this->controller->create(array(
|
||||
'id' => 'test_block',
|
||||
'theme' => 'stark',
|
||||
'plugin' => 'test_cache',
|
||||
));
|
||||
$this->block->save();
|
||||
|
||||
$this->container->get('cache.block')->deleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the rendering of blocks.
|
||||
*/
|
||||
public function testBasicRendering() {
|
||||
\Drupal::state()->set('block_test.content', '');
|
||||
|
||||
$entity = $this->controller->create(array(
|
||||
'id' => 'test_block1',
|
||||
'theme' => 'stark',
|
||||
'plugin' => 'test_html',
|
||||
));
|
||||
$entity->save();
|
||||
|
||||
// Test the rendering of a block.
|
||||
$entity = entity_load('block', 'test_block1');
|
||||
$output = entity_view($entity, 'block');
|
||||
$expected = array();
|
||||
$expected[] = '<div class="block block-block-test" id="block-test-block1">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' ';
|
||||
$expected[] = '';
|
||||
$expected[] = ' <div class="content">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' </div>';
|
||||
$expected[] = '</div>';
|
||||
$expected[] = '';
|
||||
$expected_output = implode("\n", $expected);
|
||||
$this->assertEqual(drupal_render($output), $expected_output);
|
||||
|
||||
// Reset the HTML IDs so that the next render is not affected.
|
||||
drupal_static_reset('drupal_html_id');
|
||||
|
||||
// Test the rendering of a block with a given title.
|
||||
$entity = $this->controller->create(array(
|
||||
'id' => 'test_block2',
|
||||
'theme' => 'stark',
|
||||
'plugin' => 'test_html',
|
||||
'settings' => array(
|
||||
'label' => 'Powered by Bananas',
|
||||
),
|
||||
));
|
||||
$entity->save();
|
||||
$output = entity_view($entity, 'block');
|
||||
$expected = array();
|
||||
$expected[] = '<div class="block block-block-test" id="block-test-block2">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' <h2>Powered by Bananas</h2>';
|
||||
$expected[] = ' ';
|
||||
$expected[] = '';
|
||||
$expected[] = ' <div class="content">';
|
||||
$expected[] = ' ';
|
||||
$expected[] = ' </div>';
|
||||
$expected[] = '</div>';
|
||||
$expected[] = '';
|
||||
$expected_output = implode("\n", $expected);
|
||||
$this->assertEqual(drupal_render($output), $expected_output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests block render cache handling.
|
||||
*/
|
||||
public function testBlockViewBuilderCache() {
|
||||
// Verify cache handling for a non-empty block.
|
||||
$this->verifyRenderCacheHandling();
|
||||
|
||||
// Create an empty block.
|
||||
$this->block = $this->controller->create(array(
|
||||
'id' => 'test_block',
|
||||
'theme' => 'stark',
|
||||
'plugin' => 'test_cache',
|
||||
));
|
||||
$this->block->save();
|
||||
\Drupal::state()->set('block_test.content', NULL);
|
||||
|
||||
// Verify cache handling for an empty block.
|
||||
$this->verifyRenderCacheHandling();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies render cache handling of the block being tested.
|
||||
*
|
||||
* @see ::testBlockViewBuilderCache()
|
||||
*/
|
||||
protected function verifyRenderCacheHandling() {
|
||||
// Force a request via GET so we can get drupal_render() cache working.
|
||||
$request_method = \Drupal::request()->server->get('REQUEST_METHOD');
|
||||
$this->container->get('request')->setMethod('GET');
|
||||
|
||||
// Test that entities with caching disabled do not generate a cache entry.
|
||||
$build = $this->getBlockRenderArray();
|
||||
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'The render array element of uncacheable blocks is not cached, but does have cache tags set.');
|
||||
|
||||
// Enable block caching.
|
||||
$this->setBlockCacheConfig(array(
|
||||
'max_age' => 600,
|
||||
));
|
||||
|
||||
// Test that a cache entry is created.
|
||||
$build = $this->getBlockRenderArray();
|
||||
$cid = drupal_render_cid_create($build);
|
||||
drupal_render($build);
|
||||
$this->assertTrue($this->container->get('cache.block')->get($cid), 'The block render element has been cached.');
|
||||
|
||||
// Re-save the block and check that the cache entry has been deleted.
|
||||
$this->block->save();
|
||||
$this->assertFalse($this->container->get('cache.block')->get($cid), 'The block render cache entry has been cleared when the block was saved.');
|
||||
|
||||
// Rebuild the render array (creating a new cache entry in the process) and
|
||||
// delete the block to check the cache entry is deleted.
|
||||
unset($build['#printed']);
|
||||
drupal_render($build);
|
||||
$this->assertTrue($this->container->get('cache.block')->get($cid), 'The block render element has been cached.');
|
||||
$this->block->delete();
|
||||
$this->assertFalse($this->container->get('cache.block')->get($cid), 'The block render cache entry has been cleared when the block was deleted.');
|
||||
|
||||
// Restore the previous request method.
|
||||
$this->container->get('request')->setMethod($request_method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests block view altering.
|
||||
*/
|
||||
public function testBlockViewBuilderAlter() {
|
||||
// Establish baseline.
|
||||
$build = $this->getBlockRenderArray();
|
||||
$this->assertIdentical(drupal_render($build), 'Llamas > unicorns!');
|
||||
|
||||
// Enable the block view alter hook that adds a suffix, for basic testing.
|
||||
\Drupal::state()->set('block_test_view_alter_suffix', TRUE);
|
||||
|
||||
// Basic: non-empty block.
|
||||
$build = $this->getBlockRenderArray();
|
||||
$this->assertTrue(isset($build['#suffix']) && $build['#suffix'] === '<br>Goodbye!', 'A block with content is altered.');
|
||||
$this->assertIdentical(drupal_render($build), 'Llamas > unicorns!<br>Goodbye!');
|
||||
|
||||
// Basic: empty block.
|
||||
\Drupal::state()->set('block_test.content', NULL);
|
||||
$build = $this->getBlockRenderArray();
|
||||
$this->assertTrue(isset($build['#suffix']) && $build['#suffix'] === '<br>Goodbye!', 'A block without content is altered.');
|
||||
$this->assertIdentical(drupal_render($build), '<br>Goodbye!');
|
||||
|
||||
// Disable the block view alter hook that adds a suffix, for basic testing.
|
||||
\Drupal::state()->set('block_test_view_alter_suffix', FALSE);
|
||||
|
||||
// Force a request via GET so we can get drupal_render() cache working.
|
||||
$request_method = \Drupal::request()->server->get('REQUEST_METHOD');
|
||||
$this->container->get('request')->setMethod('GET');
|
||||
|
||||
$default_keys = array('entity_view', 'block', 'test_block', 'en', 'cache_context.theme');
|
||||
$default_tags = array('content' => TRUE, 'block_view' => TRUE, 'block' => array('test_block'), 'block_plugin' => array('test_cache'));
|
||||
|
||||
// Advanced: cached block, but an alter hook adds an additional cache key.
|
||||
$this->setBlockCacheConfig(array(
|
||||
'max_age' => 600,
|
||||
));
|
||||
$alter_add_key = $this->randomName();
|
||||
\Drupal::state()->set('block_test_view_alter_cache_key', $alter_add_key);
|
||||
$expected_keys = array_merge($default_keys, array($alter_add_key));
|
||||
$build = $this->getBlockRenderArray();
|
||||
$this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
|
||||
$cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys)));
|
||||
$this->assertIdentical(drupal_render($build), '');
|
||||
$cache_entry = $this->container->get('cache.block')->get($cid);
|
||||
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
|
||||
$expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'block_plugin:test_cache');
|
||||
$this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.');
|
||||
$this->container->get('cache.block')->delete($cid);
|
||||
|
||||
// Advanced: cached block, but an alter hook adds an additional cache tag.
|
||||
$alter_add_tag = $this->randomName();
|
||||
\Drupal::state()->set('block_test_view_alter_cache_tag', $alter_add_tag);
|
||||
$expected_tags = NestedArray::mergeDeep($default_tags, array($alter_add_tag => TRUE));
|
||||
$build = $this->getBlockRenderArray();
|
||||
$this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.');
|
||||
$cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys)));
|
||||
$this->assertIdentical(drupal_render($build), '');
|
||||
$cache_entry = $this->container->get('cache.block')->get($cid);
|
||||
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
|
||||
$expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'block_plugin:test_cache', $alter_add_tag . ':1');
|
||||
$this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.');
|
||||
$this->container->get('cache.block')->delete($cid);
|
||||
|
||||
// Advanced: cached block, but an alter hook adds a #pre_render callback to
|
||||
// alter the eventual content.
|
||||
\Drupal::state()->set('block_test_view_alter_append_pre_render_prefix', TRUE);
|
||||
$build = $this->getBlockRenderArray();
|
||||
$this->assertFalse(isset($build['#prefix']), 'The appended #pre_render callback has not yet run before calling drupal_render().');
|
||||
$this->assertIdentical(drupal_render($build), 'Hiya!<br>');
|
||||
$this->assertTrue(isset($build['#prefix']) && $build['#prefix'] === 'Hiya!<br>', 'A cached block without content is altered.');
|
||||
|
||||
// Restore the previous request method.
|
||||
$this->container->get('request')->setMethod($request_method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests block render cache handling with configurable cache contexts.
|
||||
*
|
||||
* This is only intended to test that an existing block can be configured with
|
||||
* additional contexts, not to test that each context works correctly.
|
||||
*
|
||||
* @see \Drupal\block\Tests\BlockCacheTest
|
||||
*/
|
||||
public function testBlockViewBuilderCacheContexts() {
|
||||
// Force a request via GET so we can get drupal_render() cache working.
|
||||
$request_method = \Drupal::request()->server->get('REQUEST_METHOD');
|
||||
$this->container->get('request')->setMethod('GET');
|
||||
|
||||
// First: no cache context.
|
||||
$this->setBlockCacheConfig(array(
|
||||
'max_age' => 600,
|
||||
));
|
||||
$build = $this->getBlockRenderArray();
|
||||
$cid = drupal_render_cid_create($build);
|
||||
drupal_render($build);
|
||||
$this->assertTrue($this->container->get('cache.block', $cid), 'The block render element has been cached.');
|
||||
|
||||
// Second: the "per URL" cache context.
|
||||
$this->setBlockCacheConfig(array(
|
||||
'max_age' => 600,
|
||||
'contexts' => array('cache_context.url'),
|
||||
));
|
||||
$old_cid = $cid;
|
||||
$build = $this->getBlockRenderArray();
|
||||
$cid = drupal_render_cid_create($build);
|
||||
drupal_render($build);
|
||||
$this->assertTrue($this->container->get('cache.block', $cid), 'The block render element has been cached.');
|
||||
$this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.');
|
||||
|
||||
// Third: the same block configuration, but a different URL.
|
||||
$original_url_cache_context = $this->container->get('cache_context.url');
|
||||
$temp_context = new UrlCacheContext(Request::create('/foo'));
|
||||
$this->container->set('cache_context.url', $temp_context);
|
||||
$old_cid = $cid;
|
||||
$build = $this->getBlockRenderArray();
|
||||
$cid = drupal_render_cid_create($build);
|
||||
drupal_render($build);
|
||||
$this->assertTrue($this->container->get('cache.block', $cid), 'The block render element has been cached.');
|
||||
$this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.');
|
||||
$this->container->set('cache_context.url', $original_url_cache_context);
|
||||
|
||||
// Restore the previous request method.
|
||||
$this->container->get('request')->setMethod($request_method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the test block's cache configuration.
|
||||
*
|
||||
* @param array $cache_config
|
||||
* The desired cache configuration.
|
||||
*/
|
||||
protected function setBlockCacheConfig(array $cache_config) {
|
||||
$block = $this->block->getPlugin();
|
||||
$block->setConfigurationValue('cache', $cache_config);
|
||||
$this->block->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fully built render array for a block.
|
||||
*
|
||||
* @return array
|
||||
* The render array.
|
||||
*/
|
||||
protected function getBlockRenderArray() {
|
||||
$build = $this->container->get('entity.manager')->getViewBuilder('block')->view($this->block, 'block');
|
||||
|
||||
// Mock the build array to not require the theme registry.
|
||||
unset($build['#theme']);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
|
@ -176,19 +176,6 @@ class DisplayBlockTest extends ViewTestBase {
|
|||
$this->assertBlockAppears($block_4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -283,19 +270,23 @@ class DisplayBlockTest extends ViewTestBase {
|
|||
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('<div' . new Attribute(array('data-contextual-id' => $id)) . '></div>', format_string('Contextual link placeholder with id @id exists.', array('@id' => $id)));
|
||||
$this->assertRaw('<div' . new Attribute(array('data-contextual-id' => $cached_id)) . '></div>', 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 = Json::decode($response);
|
||||
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="views-uiedit"><a href="' . base_path() . 'admin/structure/views/view/test_view_block/edit/block_1">Edit view</a></li></ul>');
|
||||
$this->assertIdentical($json[$cached_id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $cached_block->id() . '">Configure block</a></li><li class="views-uiedit"><a href="' . base_path() . 'admin/structure/views/view/test_view_block/edit/block_1">Edit view</a></li></ul>');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,11 +12,6 @@ use Drupal\Core\DependencyInjection\ContainerBuilder;
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* Provide test blocks.
|
||||
*/
|
||||
|
||||
use Drupal\block\BlockPluginInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_block_alter().
|
||||
*/
|
||||
|
@ -13,3 +15,29 @@ function block_test_block_alter(&$block_info) {
|
|||
$block_info['test_block_instantiation']['category'] = t('Custom category');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_view_BASE_BLOCK_ID_alter().
|
||||
*/
|
||||
function block_test_block_view_test_cache_alter(array &$build, BlockPluginInterface $block) {
|
||||
if (\Drupal::state()->get('block_test_view_alter_suffix') !== NULL) {
|
||||
$build['#suffix'] = '<br>Goodbye!';
|
||||
}
|
||||
if (\Drupal::state()->get('block_test_view_alter_cache_key') !== NULL) {
|
||||
$build['#cache']['keys'][] = \Drupal::state()->get('block_test_view_alter_cache_key');
|
||||
}
|
||||
if (\Drupal::state()->get('block_test_view_alter_cache_tag') !== NULL) {
|
||||
$build['#cache']['tags'][\Drupal::state()->get('block_test_view_alter_cache_tag')] = TRUE;
|
||||
}
|
||||
if (\Drupal::state()->get('block_test_view_alter_append_pre_render_prefix') !== NULL) {
|
||||
$build['#pre_render'][] = 'block_test_pre_render_alter_content';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #pre_render callback for a block to alter its content.
|
||||
*/
|
||||
function block_test_pre_render_alter_content($build) {
|
||||
$build['#prefix'] = 'Hiya!<br>';
|
||||
return $build;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'Test HTML block'
|
||||
module: block_test
|
||||
label_display: 'hidden'
|
||||
cache: 1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -19,24 +19,17 @@ use Drupal\block\BlockBase;
|
|||
*/
|
||||
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'),
|
||||
);
|
||||
$content = \Drupal::state()->get('block_test.content');
|
||||
|
||||
$build = array();
|
||||
if (!empty($content)) {
|
||||
$build['#markup'] = $content;
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,16 +16,4 @@ namespace Drupal\block_test\Plugin\Block;
|
|||
* )
|
||||
*/
|
||||
class TestXSSTitleBlock extends TestCacheBlock {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Sets a different caching strategy for testing purposes.
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
'cache' => DRUPAL_NO_CACHE,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
'cache' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE,
|
||||
'block_mode' => "all pages",
|
||||
);
|
||||
}
|
||||
|
@ -101,6 +100,7 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
*/
|
||||
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 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
$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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -538,23 +538,6 @@ function forum_form_node_form_alter(&$form, &$form_state, $form_id) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render API callback: Lists nodes based on the element's #query property.
|
||||
*
|
||||
* This function can be used as a #pre_render callback.
|
||||
*
|
||||
* @see \Drupal\forum\Plugin\block\block\NewTopicsBlock::build()
|
||||
* @see \Drupal\forum\Plugin\block\block\ActiveTopicsBlock::build()
|
||||
*/
|
||||
function forum_block_view_pre_render($elements) {
|
||||
$result = $elements['#query']->execute();
|
||||
if ($node_title_list = node_title_list($result)) {
|
||||
$elements['forum_list'] = $node_title_list;
|
||||
$elements['forum_more'] = array('#theme' => 'more_link', '#url' => 'forum', '#title' => t('Read the latest forum topics.'));
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for block templates.
|
||||
*/
|
||||
|
|
|
@ -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'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,18 +9,42 @@ namespace Drupal\forum\Plugin\Block;
|
|||
|
||||
use Drupal\block\BlockBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
|
||||
/**
|
||||
* Provides a base class for Forum blocks.
|
||||
*/
|
||||
abstract class ForumBlockBase extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@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 @@ abstract class ForumBlockBase extends BlockBase {
|
|||
$this->configuration['block_count'] = $form_state['values']['block_count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheKeys() {
|
||||
return array_merge(parent::getCacheKeys(), Cache::keyFromQuery($this->buildForumQuery()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ class LanguageBlock extends DerivativeBase {
|
|||
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.
|
||||
|
|
|
@ -58,7 +58,14 @@ class MenuCacheTagsTest extends PageCacheTagsTestBase {
|
|||
$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 @@ class MenuCacheTagsTest extends PageCacheTagsTestBase {
|
|||
$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.
|
||||
|
|
|
@ -354,6 +354,7 @@ abstract class WebTestBase extends TestBase {
|
|||
* - region: 'sidebar_first'.
|
||||
* - theme: The default theme.
|
||||
* - visibility: Empty array.
|
||||
* - cache: array('max_age' => 0).
|
||||
*
|
||||
* @return \Drupal\block\Entity\Block
|
||||
* The block entity.
|
||||
|
@ -370,6 +371,9 @@ abstract class WebTestBase extends TestBase {
|
|||
'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];
|
||||
|
|
|
@ -79,10 +79,12 @@ class PerformanceForm extends ConfigFormBase {
|
|||
'#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'),
|
||||
|
|
|
@ -117,4 +117,24 @@ class SystemHelpBlock extends BlockBase implements ContainerFactoryPluginInterfa
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,4 +28,26 @@ class SystemMainBlock extends BlockBase {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 @@ class SystemMenuBlock extends BlockBase {
|
|||
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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 @@ class SystemPoweredByBlock extends BlockBase {
|
|||
return array('#markup' => '<span>' . t('Powered by <a href="@poweredby">Drupal</a>', array('@poweredby' => 'http://drupal.org')) . '</span>');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ class SystemMenuBlock extends DerivativeBase implements ContainerDerivativeInter
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -64,13 +64,35 @@ class PageCacheTagsIntegrationTest extends WebTestBase {
|
|||
'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 @@ class PageCacheTagsIntegrationTest extends WebTestBase {
|
|||
// 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',
|
||||
));
|
||||
|
|
|
@ -144,7 +144,7 @@ class EntityViewBuilderTest extends EntityUnitTestBase {
|
|||
// 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');
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\user\Cache\UserCacheContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\user\Cache;
|
||||
|
||||
use Drupal\Core\Cache\CacheContextInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines the UserCacheContext service, for "per user" caching.
|
||||
*/
|
||||
class UserCacheContext implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* Constructs a new UserCacheContext service.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(AccountInterface $user) {
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getLabel() {
|
||||
return t('User');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
return "u." . $this->user->id();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\user\Cache\UserRolesCacheContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\user\Cache;
|
||||
|
||||
use Drupal\Core\Cache\CacheContextInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines the UserRolesCacheContext service, for "per role" caching.
|
||||
*/
|
||||
class UserRolesCacheContext implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* Constructs a new UserRolesCacheContext service.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(AccountInterface $user) {
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getLabel() {
|
||||
return t("User's roles");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
return 'r.' . implode(',', $this->user->getRoles());
|
||||
}
|
||||
|
||||
}
|
|
@ -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']
|
||||
|
|
|
@ -29,13 +29,11 @@ class ViewsBlock extends ViewsBlockBase {
|
|||
$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);
|
||||
|
@ -45,6 +43,20 @@ class ViewsBlock extends ViewsBlockBase {
|
|||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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}
|
||||
*/
|
||||
|
|
|
@ -99,10 +99,7 @@ abstract class ViewsBlockBase extends BlockBase implements ContainerFactoryPlugi
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
$settings = array();
|
||||
$settings['views_label'] = '';
|
||||
|
||||
return $settings;
|
||||
return array('views_label' => '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -102,7 +102,6 @@ class ViewsBlock implements ContainerDerivativeInterface {
|
|||
$this->derivatives[$delta] = array(
|
||||
'category' => $display->getOption('block_category'),
|
||||
'admin_label' => $desc,
|
||||
'cache' => $display->getCacheType()
|
||||
);
|
||||
$this->derivatives[$delta] += $base_plugin_definition;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,6 @@ class ViewsExposedFilterBlock implements ContainerDerivativeInterface {
|
|||
$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;
|
||||
}
|
||||
|
|
|
@ -2383,7 +2383,6 @@ abstract class DisplayPluginBase extends PluginBase {
|
|||
|
||||
$blocks[$delta] = array(
|
||||
'info' => $desc,
|
||||
'cache' => DRUPAL_NO_CACHE,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,9 +15,6 @@ use Drupal\block\Plugin\views\display\Block;
|
|||
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.
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: Administration
|
||||
module: system
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'User login'
|
||||
module: user
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: Tools
|
||||
module: system
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: Breadcrumbs
|
||||
module: system
|
||||
label_display: '0'
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'Main page content'
|
||||
module: system
|
||||
label_display: '0'
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'Footer menu'
|
||||
module: system
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'System Help'
|
||||
module: system
|
||||
label_display: '0'
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'User login'
|
||||
module: user
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'Powered by Drupal'
|
||||
module: system
|
||||
label_display: '0'
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: Search
|
||||
module: search
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: Tools
|
||||
module: system
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: Breadcrumbs
|
||||
module: system
|
||||
label_display: '0'
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'Main page content'
|
||||
module: system
|
||||
label_display: '0'
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'System Help'
|
||||
module: system
|
||||
label_display: '0'
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ settings:
|
|||
label: 'User login'
|
||||
module: user
|
||||
label_display: visible
|
||||
cache: -1
|
||||
visibility:
|
||||
path:
|
||||
visibility: 0
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Core\Cache\CacheContextsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\CacheContexts;
|
||||
use Drupal\Core\Cache\CacheContextInterface;
|
||||
|
||||
/**
|
||||
* Fake cache context class.
|
||||
*/
|
||||
class FooCacheContext implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getLabel() {
|
||||
return 'Foo';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the CacheContexts service.
|
||||
*
|
||||
* @group Cache
|
||||
*
|
||||
* @see \Drupal\Core\Cache\CacheContexts
|
||||
*/
|
||||
class CacheContextsTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => '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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue