Issue #2263981 by znerol, beejeebus: Introduce a robust and extensible page cache-policy framework.
parent
1476c56c62
commit
fb6c562c9e
|
@ -100,6 +100,18 @@ services:
|
|||
factory_method: get
|
||||
factory_service: cache_factory
|
||||
arguments: [discovery]
|
||||
page_cache_request_policy:
|
||||
class: Drupal\Core\PageCache\DefaultRequestPolicy
|
||||
tags:
|
||||
- { name: service_collector, tag: page_cache_request_policy, call: addPolicy}
|
||||
page_cache_response_policy:
|
||||
class: Drupal\Core\PageCache\ChainResponsePolicy
|
||||
tags:
|
||||
- { name: service_collector, tag: page_cache_response_policy, call: addPolicy}
|
||||
page_cache_kill_switch:
|
||||
class: Drupal\Core\PageCache\ResponsePolicy\KillSwitch
|
||||
tags:
|
||||
- { name: page_cache_response_policy }
|
||||
config.manager:
|
||||
class: Drupal\Core\Config\ConfigManager
|
||||
arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage', '@event_dispatcher']
|
||||
|
@ -729,7 +741,7 @@ services:
|
|||
class: Drupal\Core\EventSubscriber\FinishResponseSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
arguments: ['@language_manager', '@config.factory']
|
||||
arguments: ['@language_manager', '@config.factory', '@page_cache_request_policy', '@page_cache_response_policy']
|
||||
redirect_response_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\RedirectResponseSubscriber
|
||||
arguments: ['@url_generator']
|
||||
|
|
|
@ -389,30 +389,6 @@ function drupal_page_get_cache(Request $request) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the cacheability of the current page.
|
||||
*
|
||||
* Note: we do not serve cached pages to authenticated users, or to anonymous
|
||||
* users when $_SESSION is non-empty. $_SESSION may contain status messages
|
||||
* from a form submission, the contents of a shopping cart, or other user-
|
||||
* specific content that should not be cached and displayed to other users.
|
||||
*
|
||||
* @param $allow_caching
|
||||
* Set to FALSE if you want to prevent this page to get cached.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the current page can be cached, FALSE otherwise.
|
||||
*/
|
||||
function drupal_page_is_cacheable($allow_caching = NULL) {
|
||||
$allow_caching_static = &drupal_static(__FUNCTION__, TRUE);
|
||||
if (isset($allow_caching)) {
|
||||
$allow_caching_static = $allow_caching;
|
||||
}
|
||||
|
||||
return $allow_caching_static && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')
|
||||
&& PHP_SAPI !== 'cli';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an HTTP response header for the current page.
|
||||
*
|
||||
|
@ -931,7 +907,7 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
|
|||
}
|
||||
|
||||
// Mark this page as being uncacheable.
|
||||
drupal_page_is_cacheable(FALSE);
|
||||
\Drupal::service('page_cache_kill_switch')->trigger();
|
||||
}
|
||||
|
||||
// Messages not set when DB connection fails.
|
||||
|
|
|
@ -62,8 +62,8 @@ function drupal_rebuild(ClassLoader $classloader, Request $request) {
|
|||
$bin->deleteAll();
|
||||
}
|
||||
|
||||
// Disable the page cache.
|
||||
drupal_page_is_cacheable(FALSE);
|
||||
// Disable recording of cached pages.
|
||||
\Drupal::service('page_cache_kill_switch')->trigger();
|
||||
|
||||
drupal_flush_all_caches();
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ use Drupal\Core\DependencyInjection\ServiceProviderInterface;
|
|||
use Drupal\Core\DependencyInjection\YamlFileLoader;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Drupal\Core\PhpStorage\PhpStorageFactory;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -466,9 +467,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
|
|||
$cache_enabled = $config->get('cache.page.use_internal');
|
||||
}
|
||||
|
||||
// If there is no session cookie and cache is enabled (or forced), try to
|
||||
// serve a cached page.
|
||||
if (!$request->cookies->has(session_name()) && $cache_enabled && drupal_page_is_cacheable()) {
|
||||
$request_policy = \Drupal::service('page_cache_request_policy');
|
||||
if ($cache_enabled && $request_policy->check($request) === RequestPolicyInterface::ALLOW) {
|
||||
// Get the page from the cache.
|
||||
$response = drupal_page_get_cache($request);
|
||||
// If there is a cached page, display it.
|
||||
|
|
|
@ -11,6 +11,8 @@ use Drupal\Component\Datetime\DateTimePlus;
|
|||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
@ -40,6 +42,20 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
|
|||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* A policy rule determining the cacheability of a request.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\RequestPolicyInterface
|
||||
*/
|
||||
protected $requestPolicy;
|
||||
|
||||
/**
|
||||
* A policy rule determining the cacheability of the response.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\ResponsePolicyInterface
|
||||
*/
|
||||
protected $responsePolicy;
|
||||
|
||||
/**
|
||||
* Constructs a FinishResponseSubscriber object.
|
||||
*
|
||||
|
@ -47,10 +63,16 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
|
|||
* The language manager object for retrieving the correct language code.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* A config factory for retrieving required config objects.
|
||||
* @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy
|
||||
* A policy rule determining the cacheability of a request.
|
||||
* @param \Drupal\Core\PageCache\ResponsePolicyInterface $response_policy
|
||||
* A policy rule determining the cacheability of a response.
|
||||
*/
|
||||
public function __construct(LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory) {
|
||||
public function __construct(LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy) {
|
||||
$this->languageManager = $language_manager;
|
||||
$this->config = $config_factory->get('system.performance');
|
||||
$this->requestPolicy = $request_policy;
|
||||
$this->responsePolicy = $response_policy;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,16 +105,21 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
|
|||
$response->headers->set($name, $value, FALSE);
|
||||
}
|
||||
|
||||
$is_cacheable = drupal_page_is_cacheable();
|
||||
$is_cacheable = ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY);
|
||||
|
||||
// Add headers necessary to specify whether the response should be cached by
|
||||
// proxies and/or the browser.
|
||||
if ($is_cacheable && $this->config->get('cache.page.max_age') > 0) {
|
||||
if (!$this->isCacheControlCustomized($response)) {
|
||||
// Only add the default Cache-Control header if the controller did not
|
||||
// specify one on the response.
|
||||
$this->setResponseCacheable($response, $request);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If either the policy forbids caching or the sites configuration does
|
||||
// not allow to add a max-age directive, then enforce a Cache-Control
|
||||
// header declaring the response as not cacheable.
|
||||
$this->setResponseNotCacheable($response, $request);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\ChainRequestPolicy.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Implements a compound request policy.
|
||||
*
|
||||
* When evaluating the compound policy, all of the contained rules are applied
|
||||
* to the request. The overall result is computed according to the following
|
||||
* rules:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Returns static::DENY if any of the rules evaluated to static::DENY</li>
|
||||
* <li>Returns static::ALLOW if at least one of the rules evaluated to
|
||||
* static::ALLOW and none to static::DENY</li>
|
||||
* <li>Otherwise returns NULL</li>
|
||||
* </ol>
|
||||
*/
|
||||
class ChainRequestPolicy implements ChainRequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* A list of policy rules to apply when this policy is evaluated.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\RequestPolicyInterface[]
|
||||
*/
|
||||
protected $rules = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Request $request) {
|
||||
$final_result = NULL;
|
||||
|
||||
foreach ($this->rules as $rule) {
|
||||
$result = $rule->check($request);
|
||||
if ($result === static::DENY) {
|
||||
return $result;
|
||||
}
|
||||
elseif ($result === static::ALLOW) {
|
||||
$final_result = $result;
|
||||
}
|
||||
elseif (isset($result)) {
|
||||
throw new \UnexpectedValueException('Return value of RequestPolicyInterface::check() must be one of RequestPolicyInterface::ALLOW, RequestPolicyInterface::DENY or NULL');
|
||||
}
|
||||
}
|
||||
|
||||
return $final_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPolicy(RequestPolicyInterface $policy) {
|
||||
$this->rules[] = $policy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\ChainRequestPolicyInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache;
|
||||
|
||||
/**
|
||||
* Defines the interface for compound request policies.
|
||||
*/
|
||||
interface ChainRequestPolicyInterface extends RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* Add a policy to the list of policy rules.
|
||||
*
|
||||
* @param \Drupal\Core\PageCache\RequestPolicyInterface $policy
|
||||
* The request policy rule to add.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addPolicy(RequestPolicyInterface $policy);
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\ChainResponsePolicy.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Implements a compound response policy.
|
||||
*
|
||||
* When evaluating the compound policy, all of the contained rules are applied
|
||||
* to the response. The overall result is computed according to the following
|
||||
* rules:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Returns static::DENY if any of the rules evaluated to static::DENY</li>
|
||||
* <li>Otherwise returns NULL</li>
|
||||
* </ol>
|
||||
*/
|
||||
class ChainResponsePolicy implements ChainResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* A list of policy rules to apply when this policy is checked.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\ResponsePolicyInterface[]
|
||||
*/
|
||||
protected $rules = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Response $response, Request $request) {
|
||||
foreach ($this->rules as $rule) {
|
||||
$result = $rule->check($response, $request);
|
||||
if ($result === static::DENY) {
|
||||
return $result;
|
||||
}
|
||||
elseif (isset($result)) {
|
||||
throw new \UnexpectedValueException('Return value of ResponsePolicyInterface::check() must be one of ResponsePolicyInterface::DENY or NULL');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPolicy(ResponsePolicyInterface $policy) {
|
||||
$this->rules[] = $policy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\ChainResponsePolicyInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache;
|
||||
|
||||
/**
|
||||
* Defines the interface for compound request policies.
|
||||
*/
|
||||
interface ChainResponsePolicyInterface extends ResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* Add a policy to the list of policy rules.
|
||||
*
|
||||
* @param \Drupal\Core\PageCache\ResponsePolicyInterface $policy
|
||||
* The request policy rule to add.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addPolicy(ResponsePolicyInterface $policy);
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\DefaultRequestPolicy.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache;
|
||||
|
||||
/**
|
||||
* The default page cache request policy.
|
||||
*
|
||||
* Delivery of cached pages is denied if either the application is running from
|
||||
* the command line or the request was not initiated with a safe method (GET or
|
||||
* HEAD). Also caching is only allowed for requests without a session cookie.
|
||||
*/
|
||||
class DefaultRequestPolicy extends ChainRequestPolicy {
|
||||
|
||||
/**
|
||||
* Constructs the default page cache request policy.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->addPolicy(new RequestPolicy\CommandLineOrUnsafeMethod());
|
||||
$this->addPolicy(new RequestPolicy\NoSessionOpen());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\RequestPolicy\CommandLineOrUnsafeMethod.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache\RequestPolicy;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Reject when running from the command line or when HTTP method is not safe.
|
||||
*
|
||||
* The policy denies caching if the request was initiated from the command line
|
||||
* interface (drush) or the request method is neither GET nor HEAD (see RFC
|
||||
* 2616, section 9.1.1 - Safe Methods).
|
||||
*/
|
||||
class CommandLineOrUnsafeMethod implements RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Request $request) {
|
||||
if ($this->isCli() || !$request->isMethodSafe()) {
|
||||
return static::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this is a CLI request.
|
||||
*/
|
||||
protected function isCli() {
|
||||
return PHP_SAPI === 'cli';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\RequestPolicy\NoSessionOpen.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache\RequestPolicy;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* A policy allowing delivery of cached pages when there is no session open.
|
||||
*
|
||||
* Do not serve cached pages to authenticated users, or to anonymous users when
|
||||
* $_SESSION is non-empty. $_SESSION may contain status messages from a form
|
||||
* submission, the contents of a shopping cart, or other userspecific content
|
||||
* that should not be cached and displayed to other users.
|
||||
*/
|
||||
class NoSessionOpen implements RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* The name of the session cookie.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionCookieName;
|
||||
|
||||
/**
|
||||
* Constructs a new page cache session policy.
|
||||
*
|
||||
* @param string $session_cookie_name
|
||||
* (optional) The name of the session cookie. Defaults to session_name().
|
||||
*/
|
||||
public function __construct($session_cookie_name = NULL) {
|
||||
$this->sessionCookieName = $session_cookie_name ?: session_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Request $request) {
|
||||
if (!$request->cookies->has($this->sessionCookieName)) {
|
||||
return static::ALLOW;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\RequestPolicyInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Defines the interface for request policy implementations.
|
||||
*
|
||||
* The request policy is evaluated in order to determine whether delivery of a
|
||||
* cached page should be attempted. The caller should do so if static::ALLOW is
|
||||
* returned from the check() method.
|
||||
*/
|
||||
interface RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* Allow delivery of cached pages.
|
||||
*/
|
||||
const ALLOW = 'allow';
|
||||
|
||||
/**
|
||||
* Deny delivery of cached pages.
|
||||
*/
|
||||
const DENY = 'deny';
|
||||
|
||||
/**
|
||||
* Determines whether delivery of a cached page should be attempted.
|
||||
*
|
||||
* Note that the request-policy check runs very early. In particular it is
|
||||
* not possible to determine the logged in user. Also the current route match
|
||||
* is not yet present when the check runs. Therefore, request-policy checks
|
||||
* need to be designed in a way such that they do not depend on any other
|
||||
* service and only take in account the information present on the incoming
|
||||
* request.
|
||||
*
|
||||
* When matching against the request path, special attention is needed to
|
||||
* support path prefixes which are often used on multilingual sites.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The incoming request object.
|
||||
*
|
||||
* @return string|NULL
|
||||
* One of static::ALLOW, static::DENY or NULL. Calling code may attempt to
|
||||
* deliver a cached page if static::ALLOW is returned. Returns NULL if the
|
||||
* policy is not specified for the given request.
|
||||
*/
|
||||
public function check(Request $request);
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\ResponsePolicy\KillSwitch.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache\ResponsePolicy;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* A policy evaluating to static::DENY when the kill switch was triggered.
|
||||
*/
|
||||
class KillSwitch implements ResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* A flag indicating whether the kill switch was triggered.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $kill = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Response $response, Request $request) {
|
||||
if ($this->kill) {
|
||||
return static::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deny any page caching on the current request.
|
||||
*/
|
||||
public function trigger() {
|
||||
$this->kill = TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\PageCache\ResponsePolicyInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\PageCache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Defines the interface for response policy implementations.
|
||||
*
|
||||
* The response policy is evaluated in order to determine whether a page should
|
||||
* be stored a in the cache. Calling code should do so unless static::DENY is
|
||||
* returned from the check() method.
|
||||
*/
|
||||
interface ResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* Deny storage of a page in the cache.
|
||||
*/
|
||||
const DENY = 'deny';
|
||||
|
||||
/**
|
||||
* Determines whether it is save to store a page in the cache.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* The response which is about to be sent to the client.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*
|
||||
* @return string|NULL
|
||||
* Either static::DENY or NULL. Calling code may attempt to store a page in
|
||||
* the cache unless static::DENY is returned. Returns NULL if the policy
|
||||
* policy is not specified for the given response.
|
||||
*/
|
||||
public function check(Response $response, Request $request);
|
||||
|
||||
}
|
|
@ -130,9 +130,6 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter
|
|||
// anonymous users not use a session cookie unless something is stored in
|
||||
// $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
|
||||
$result = $this->startNow();
|
||||
if ($user->isAuthenticated() || !$this->isSessionObsolete()) {
|
||||
drupal_page_is_cacheable(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
|
|
|
@ -6,3 +6,8 @@ services:
|
|||
plugin.manager.image.effect:
|
||||
class: Drupal\image\ImageEffectManager
|
||||
parent: default_plugin_manager
|
||||
image.page_cache_request_policy.deny_private_image_style_download:
|
||||
class: Drupal\image\PageCache\DenyPrivateImageStyleDownload
|
||||
arguments: ['@current_route_match']
|
||||
tags:
|
||||
- { name: page_cache_response_policy }
|
||||
|
|
|
@ -155,7 +155,6 @@ class ImageStyleDownloadController extends FileDownloadController {
|
|||
}
|
||||
|
||||
if ($success) {
|
||||
drupal_page_is_cacheable(FALSE);
|
||||
$image = $this->imageFactory->get($derivative_uri);
|
||||
$uri = $image->getSource();
|
||||
$headers += array(
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\image\PageCache\DenyPrivateImageStyleDownload.
|
||||
*/
|
||||
|
||||
namespace Drupal\image\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Cache policy for image preview page.
|
||||
*
|
||||
* This policy rule denies caching of responses generated by the
|
||||
* entity.image.preview route.
|
||||
*/
|
||||
class DenyPrivateImageStyleDownload implements ResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a deny image preview page cache policy.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*/
|
||||
public function __construct(RouteMatchInterface $route_match) {
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Response $response, Request $request) {
|
||||
if ($this->routeMatch->getRouteName() === 'image.style_private') {
|
||||
return static::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\image\Unit\PageCache\DenyPrivateImageStyleDownloadTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\image\Unit\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\image\PageCache\DenyPrivateImageStyleDownload;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\image\PageCache\DenyPrivateImageStyleDownload
|
||||
* @group image
|
||||
*/
|
||||
class DenyPrivateImageStyleDownloadTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The response policy under test.
|
||||
*
|
||||
* @var \Drupal\image\PageCache\DenyPrivateImageStyleDownload
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
/**
|
||||
* A request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* A response object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatch|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
public function setUp() {
|
||||
$this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
|
||||
$this->policy = new DenyPrivateImageStyleDownload($this->routeMatch);
|
||||
$this->response = new Response();
|
||||
$this->request = new Request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that caching is denied on the private image style download route.
|
||||
*
|
||||
* @dataProvider providerPrivateImageStyleDownloadPolicy
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testPrivateImageStyleDownloadPolicy($expected_result, $route_name) {
|
||||
$this->routeMatch->expects($this->once())
|
||||
->method('getRouteName')
|
||||
->will($this->returnValue($route_name));
|
||||
|
||||
$actual_result = $this->policy->check($this->response, $this->request);
|
||||
$this->assertSame($expected_result, $actual_result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data and expected results for the test method.
|
||||
*
|
||||
* @return array
|
||||
* Data and expected results.
|
||||
*/
|
||||
public function providerPrivateImageStyleDownloadPolicy() {
|
||||
return [
|
||||
[ResponsePolicyInterface::DENY, 'image.style_private'],
|
||||
[NULL, 'some.other.route'],
|
||||
[NULL, NULL],
|
||||
[NULL, FALSE],
|
||||
[NULL, TRUE],
|
||||
[NULL, new \StdClass()],
|
||||
[NULL, [1, 2, 3]],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -34,3 +34,8 @@ services:
|
|||
arguments: ['@user.tempstore']
|
||||
tags:
|
||||
- { name: paramconverter }
|
||||
node.page_cache_request_policy.deny_node_preview:
|
||||
class: Drupal\node\PageCache\DenyNodePreview
|
||||
arguments: ['@current_route_match']
|
||||
tags:
|
||||
- { name: page_cache_response_policy }
|
||||
|
|
|
@ -20,9 +20,6 @@ class NodePreviewController extends EntityViewController {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function view(EntityInterface $node_preview, $view_mode_id = 'full', $langcode = NULL) {
|
||||
// Do not cache this page.
|
||||
drupal_page_is_cacheable(FALSE);
|
||||
|
||||
$node_preview->preview_view_mode = $view_mode_id;
|
||||
$build = array('nodes' => parent::view($node_preview, $view_mode_id));
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\node\PageCache\DenyNodePreview.
|
||||
*/
|
||||
|
||||
namespace Drupal\node\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Cache policy for node preview page.
|
||||
*
|
||||
* This policy rule denies caching of responses generated by the
|
||||
* entity.node.preview route.
|
||||
*/
|
||||
class DenyNodePreview implements ResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a deny node preview page cache policy.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*/
|
||||
public function __construct(RouteMatchInterface $route_match) {
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Response $response, Request $request) {
|
||||
if ($this->routeMatch->getRouteName() === 'entity.node.preview') {
|
||||
return static::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\node\Unit\PageCache\DenyNodePreviewTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\node\Unit\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\node\PageCache\DenyNodePreview;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\node\PageCache\DenyNodePreview
|
||||
* @group node
|
||||
*/
|
||||
class DenyNodePreviewTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The response policy under test.
|
||||
*
|
||||
* @var \Drupal\node\PageCache\DenyNodePreview
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
/**
|
||||
* A request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* A response object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatch|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
public function setUp() {
|
||||
$this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
|
||||
$this->policy = new DenyNodePreview($this->routeMatch);
|
||||
$this->response = new Response();
|
||||
$this->request = new Request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that caching is denied on the node preview route.
|
||||
*
|
||||
* @dataProvider providerPrivateImageStyleDownloadPolicy
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testPrivateImageStyleDownloadPolicy($expected_result, $route_name) {
|
||||
$this->routeMatch->expects($this->once())
|
||||
->method('getRouteName')
|
||||
->will($this->returnValue($route_name));
|
||||
|
||||
$actual_result = $this->policy->check($this->response, $this->request);
|
||||
$this->assertSame($expected_result, $actual_result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data and expected results for the test method.
|
||||
*
|
||||
* @return array
|
||||
* Data and expected results.
|
||||
*/
|
||||
public function providerPrivateImageStyleDownloadPolicy() {
|
||||
return [
|
||||
[ResponsePolicyInterface::DENY, 'entity.node.preview'],
|
||||
[NULL, 'some.other.route'],
|
||||
[NULL, NULL],
|
||||
[NULL, FALSE],
|
||||
[NULL, TRUE],
|
||||
[NULL, new \StdClass()],
|
||||
[NULL, [1, 2, 3]],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -22,10 +22,22 @@ class ToolbarController extends ControllerBase {
|
|||
* @return \Symfony\Component\HttpFoundation\JsonResponse
|
||||
*/
|
||||
public function subtreesJsonp() {
|
||||
_toolbar_initialize_page_cache();
|
||||
$subtrees = toolbar_get_rendered_subtrees();
|
||||
$response = new JsonResponse($subtrees);
|
||||
$response->setCallback('Drupal.toolbar.setSubtrees.resolve');
|
||||
|
||||
// The Expires HTTP header is the heart of the client-side HTTP caching. The
|
||||
// additional server-side page cache only takes effect when the client
|
||||
// accesses the callback URL again (e.g., after clearing the browser cache
|
||||
// or when force-reloading a Drupal page).
|
||||
$max_age = 365 * 24 * 60 * 60;
|
||||
$response->setPrivate();
|
||||
$response->setMaxAge($max_age);
|
||||
|
||||
$expires = new \DateTime();
|
||||
$expires->setTimestamp(REQUEST_TIME + $max_age);
|
||||
$response->setExpires($expires);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\toolbar\PageCache\AllowToolbarPath.
|
||||
*/
|
||||
|
||||
namespace Drupal\toolbar\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Cache policy for the toolbar page cache service.
|
||||
*
|
||||
* This policy allows caching of requests directed to /toolbar/subtrees/{hash}
|
||||
* even for authenticated users.
|
||||
*/
|
||||
class AllowToolbarPath implements RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Request $request) {
|
||||
// Note that this regular expression matches the end of pathinfo in order to
|
||||
// support multilingual sites using path prefixes.
|
||||
if (preg_match('#/toolbar/subtrees/[^/]+(/[^/]+)?$#', $request->getPathInfo())) {
|
||||
return static::ALLOW;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\toolbar\Unit\PageCache\AllowToolbarPathTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\toolbar\Unit\PageCache;
|
||||
|
||||
use Drupal\toolbar\PageCache\AllowToolbarPath;
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\toolbar\PageCache\AllowToolbarPath
|
||||
* @group toolbar
|
||||
*/
|
||||
class AllowToolbarPathTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The toolbar path policy under test.
|
||||
*
|
||||
* @var \Drupal\toolbar\PageCache\AllowToolbarPath
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
public function setUp() {
|
||||
$this->policy = new AllowToolbarPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that caching is allowed if the request goes to toolbar subtree.
|
||||
*
|
||||
* @dataProvider providerTestAllowToolbarPath
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testAllowToolbarPath($expected_result, $path) {
|
||||
$request = Request::create($path);
|
||||
$result = $this->policy->check($request);
|
||||
$this->assertSame($expected_result, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data and expected results for the test method.
|
||||
*
|
||||
* @return array
|
||||
* Data and expected results.
|
||||
*/
|
||||
public function providerTestAllowToolbarPath() {
|
||||
return [
|
||||
[NULL, '/'],
|
||||
[NULL, '/other-path?q=/toolbar/subtrees/'],
|
||||
[NULL, '/toolbar/subtrees/'],
|
||||
[NULL, '/toolbar/subtrees/some-hash/langcode/additional-stuff'],
|
||||
[RequestPolicyInterface::ALLOW, '/de/toolbar/subtrees/abcd'],
|
||||
[RequestPolicyInterface::ALLOW, '/en-us/toolbar/subtrees/xyz'],
|
||||
[RequestPolicyInterface::ALLOW, '/en-us/toolbar/subtrees/xyz/de'],
|
||||
[RequestPolicyInterface::ALLOW, '/a/b/c/toolbar/subtrees/xyz/de'],
|
||||
[RequestPolicyInterface::ALLOW, '/toolbar/subtrees/some-hash'],
|
||||
[RequestPolicyInterface::ALLOW, '/toolbar/subtrees/some-hash/en'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -95,43 +95,6 @@ function toolbar_element_info() {
|
|||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use Drupal's page cache for toolbar/subtrees/*, even for authenticated users.
|
||||
*
|
||||
* This gets invoked after full bootstrap, so must duplicate some of what's
|
||||
* done by \Drupal\Core\DrupalKernel::handlePageCache().
|
||||
*
|
||||
* @todo Replace this hack with something better integrated with DrupalKernel
|
||||
* once Drupal's page caching itself is properly integrated.
|
||||
*/
|
||||
function _toolbar_initialize_page_cache() {
|
||||
$GLOBALS['conf']['system.performance']['cache']['page']['enabled'] = TRUE;
|
||||
drupal_page_is_cacheable(TRUE);
|
||||
|
||||
// If we have a cache, serve it.
|
||||
// @see \Drupal\Core\DrupalKernel::handlePageCache()
|
||||
$request = \Drupal::request();
|
||||
$response = drupal_page_get_cache($request);
|
||||
if ($response) {
|
||||
$response->headers->set('X-Drupal-Cache', 'HIT');
|
||||
|
||||
drupal_serve_page_from_cache($response, $request);
|
||||
|
||||
$response->prepare($request);
|
||||
$response->send();
|
||||
// We are done.
|
||||
exit;
|
||||
}
|
||||
|
||||
// The Expires HTTP header is the heart of the client-side HTTP caching. The
|
||||
// additional server-side page cache only takes effect when the client
|
||||
// accesses the callback URL again (e.g., after clearing the browser cache or
|
||||
// when force-reloading a Drupal page).
|
||||
$max_age = 3600 * 24 * 365;
|
||||
drupal_add_http_header('Expires', gmdate(DateTimePlus::RFC7231, REQUEST_TIME + $max_age));
|
||||
drupal_add_http_header('Cache-Control', 'private, max-age=' . $max_age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_page_build().
|
||||
*
|
||||
|
|
|
@ -6,3 +6,7 @@ services:
|
|||
factory_method: get
|
||||
factory_service: cache_factory
|
||||
arguments: [toolbar]
|
||||
toolbar.page_cache_request_policy.allow_toolbar_path:
|
||||
class: Drupal\toolbar\PageCache\AllowToolbarPath
|
||||
tags:
|
||||
- { name: page_cache_request_policy }
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Core\PageCache\ChainRequestPolicyTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Core\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Drupal\Core\PageCache\ChainRequestPolicy;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\PageCache\ChainRequestPolicy
|
||||
* @group PageCache
|
||||
*/
|
||||
class ChainRequestPolicyTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The chain request policy under test.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\ChainRequestPolicy
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
/**
|
||||
* A request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
public function setUp() {
|
||||
$this->policy = new ChainRequestPolicy();
|
||||
$this->request = new Request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns NULL if the chain is empty.
|
||||
*
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testEmptyChain() {
|
||||
$result = $this->policy->check($this->request);
|
||||
$this->assertSame(NULL, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns NULL if a rule returns NULL.
|
||||
*
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testNullRuleChain() {
|
||||
$rule = $this->getMock('Drupal\Core\PageCache\RequestPolicyInterface');
|
||||
$rule->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->request)
|
||||
->will($this->returnValue(NULL));
|
||||
|
||||
$this->policy->addPolicy($rule);
|
||||
|
||||
$result = $this->policy->check($this->request);
|
||||
$this->assertSame(NULL, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() throws an exception if a rule returns an invalid value.
|
||||
*
|
||||
* @expectedException \UnexpectedValueException
|
||||
* @dataProvider providerChainExceptionOnInvalidReturnValue
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testChainExceptionOnInvalidReturnValue($return_value) {
|
||||
$rule = $this->getMock('Drupal\Core\PageCache\RequestPolicyInterface');
|
||||
$rule->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->request)
|
||||
->will($this->returnValue($return_value));
|
||||
|
||||
$this->policy->addPolicy($rule);
|
||||
|
||||
$this->policy->check($this->request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testChainExceptionOnInvalidReturnValue.
|
||||
*
|
||||
* @return array
|
||||
* Test input and expected result.
|
||||
*/
|
||||
public function providerChainExceptionOnInvalidReturnValue() {
|
||||
return [
|
||||
[FALSE],
|
||||
[0],
|
||||
[1],
|
||||
[TRUE],
|
||||
[[1, 2, 3]],
|
||||
[new \stdClass()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns ALLOW if any of the rules returns ALLOW.
|
||||
*
|
||||
* @dataProvider providerAllowIfAnyRuleReturnedAllow
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testAllowIfAnyRuleReturnedAllow($return_values) {
|
||||
foreach ($return_values as $return_value) {
|
||||
$rule = $this->getMock('Drupal\Core\PageCache\RequestPolicyInterface');
|
||||
$rule->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->request)
|
||||
->will($this->returnValue($return_value));
|
||||
|
||||
$this->policy->addPolicy($rule);
|
||||
}
|
||||
|
||||
$actual_result = $this->policy->check($this->request);
|
||||
$this->assertSame(RequestPolicyInterface::ALLOW, $actual_result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testAllowIfAnyRuleReturnedAllow.
|
||||
*
|
||||
* @return array
|
||||
* Test input and expected result.
|
||||
*/
|
||||
public function providerAllowIfAnyRuleReturnedAllow() {
|
||||
return [
|
||||
[[RequestPolicyInterface::ALLOW]],
|
||||
[[NULL, RequestPolicyInterface::ALLOW]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns immediately when a rule returned DENY.
|
||||
*/
|
||||
public function testStopChainOnFirstDeny() {
|
||||
$rule1 = $this->getMock('Drupal\Core\PageCache\RequestPolicyInterface');
|
||||
$rule1->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->request)
|
||||
->will($this->returnValue(RequestPolicyInterface::ALLOW));
|
||||
$this->policy->addPolicy($rule1);
|
||||
|
||||
$deny_rule = $this->getMock('Drupal\Core\PageCache\RequestPolicyInterface');
|
||||
$deny_rule->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->request)
|
||||
->will($this->returnValue(RequestPolicyInterface::DENY));
|
||||
$this->policy->addPolicy($deny_rule);
|
||||
|
||||
$ignored_rule = $this->getMock('Drupal\Core\PageCache\RequestPolicyInterface');
|
||||
$ignored_rule->expects($this->never())
|
||||
->method('check');
|
||||
$this->policy->addPolicy($ignored_rule);
|
||||
|
||||
$actual_result = $this->policy->check($this->request);
|
||||
$this->assertsame(RequestPolicyInterface::DENY, $actual_result);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Core\PageCache\ChainResponsePolicyTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Core\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\Core\PageCache\ChainResponsePolicy;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\PageCache\ChainResponsePolicy
|
||||
* @group PageCache
|
||||
*/
|
||||
class ChainResponsePolicyTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The chain response policy under test.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\ChainResponsePolicy
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
/**
|
||||
* A request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* A response object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
public function setUp() {
|
||||
$this->policy = new ChainResponsePolicy();
|
||||
$this->response = new Response();
|
||||
$this->request = new Request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns NULL if the chain is empty.
|
||||
*
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testEmptyChain() {
|
||||
$result = $this->policy->check($this->response, $this->request);
|
||||
$this->assertSame(NULL, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns NULL if a rule returns NULL.
|
||||
*
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testNullRuleChain() {
|
||||
$rule = $this->getMock('Drupal\Core\PageCache\ResponsePolicyInterface');
|
||||
$rule->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->response, $this->request)
|
||||
->will($this->returnValue(NULL));
|
||||
|
||||
$this->policy->addPolicy($rule);
|
||||
|
||||
$result = $this->policy->check($this->response, $this->request);
|
||||
$this->assertSame(NULL, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() throws an exception if a rule returns an invalid value.
|
||||
*
|
||||
* @expectedException \UnexpectedValueException
|
||||
* @dataProvider providerChainExceptionOnInvalidReturnValue
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testChainExceptionOnInvalidReturnValue($return_value) {
|
||||
$rule = $this->getMock('Drupal\Core\PageCache\ResponsePolicyInterface');
|
||||
$rule->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->response, $this->request)
|
||||
->will($this->returnValue($return_value));
|
||||
|
||||
$this->policy->addPolicy($rule);
|
||||
|
||||
$actual_result = $this->policy->check($this->response, $this->request);
|
||||
$this->assertSame(NULL, $actual_result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testChainExceptionOnInvalidReturnValue.
|
||||
*
|
||||
* @return array
|
||||
* Test input and expected result.
|
||||
*/
|
||||
public function providerChainExceptionOnInvalidReturnValue() {
|
||||
return [
|
||||
[FALSE],
|
||||
[0],
|
||||
[1],
|
||||
[TRUE],
|
||||
[[1, 2, 3]],
|
||||
[new \stdClass()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns immediately when a rule returned DENY.
|
||||
*/
|
||||
public function testStopChainOnFirstDeny() {
|
||||
$rule1 = $this->getMock('Drupal\Core\PageCache\ResponsePolicyInterface');
|
||||
$rule1->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->response, $this->request);
|
||||
$this->policy->addPolicy($rule1);
|
||||
|
||||
$deny_rule = $this->getMock('Drupal\Core\PageCache\ResponsePolicyInterface');
|
||||
$deny_rule->expects($this->once())
|
||||
->method('check')
|
||||
->with($this->response, $this->request)
|
||||
->will($this->returnValue(ResponsePolicyInterface::DENY));
|
||||
$this->policy->addPolicy($deny_rule);
|
||||
|
||||
$ignored_rule = $this->getMock('Drupal\Core\PageCache\ResponsePolicyInterface');
|
||||
$ignored_rule->expects($this->never())
|
||||
->method('check');
|
||||
$this->policy->addPolicy($ignored_rule);
|
||||
|
||||
$actual_result = $this->policy->check($this->response, $this->request);
|
||||
$this->assertsame(ResponsePolicyInterface::DENY, $actual_result);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Core\PageCache\CommandLineOrUnsafeMethodTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Core\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\PageCache\RequestPolicy\CommandLineOrUnsafeMethod
|
||||
* @group PageCache
|
||||
*/
|
||||
class CommandLineOrUnsafeMethodTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The request policy under test.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\RequestPolicy\CommandLineOrUnsafeMethod|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
public function setUp() {
|
||||
// Note that it is necessary to partially mock the class under test in
|
||||
// order to disable the isCli-check.
|
||||
$this->policy = $this->getMock('Drupal\Core\PageCache\RequestPolicy\CommandLineOrUnsafeMethod', array('isCli'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns DENY for unsafe HTTP methods.
|
||||
*
|
||||
* @dataProvider providerTestHttpMethod
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testHttpMethod($expected_result, $method) {
|
||||
$this->policy->expects($this->once())
|
||||
->method('isCli')
|
||||
->will($this->returnValue(FALSE));
|
||||
|
||||
$request = Request::create('/', $method);
|
||||
$actual_result = $this->policy->check($request);
|
||||
$this->assertSame($expected_result, $actual_result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data and expected results for the HTTP method test.
|
||||
*
|
||||
* @return array
|
||||
* Test data and expected results.
|
||||
*/
|
||||
public function providerTestHttpMethod() {
|
||||
return [
|
||||
[NULL, 'GET'],
|
||||
[NULL, 'HEAD'],
|
||||
[RequestPolicyInterface::DENY, 'POST'],
|
||||
[RequestPolicyInterface::DENY, 'PUT'],
|
||||
[RequestPolicyInterface::DENY, 'DELETE'],
|
||||
[RequestPolicyInterface::DENY, 'OPTIONS'],
|
||||
[RequestPolicyInterface::DENY, 'TRACE'],
|
||||
[RequestPolicyInterface::DENY, 'CONNECT'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that check() returns DENY if running from the command line.
|
||||
*
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testIsCli() {
|
||||
$this->policy->expects($this->once())
|
||||
->method('isCli')
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$request = Request::create('/', 'GET');
|
||||
$actual_result = $this->policy->check($request);
|
||||
$this->assertSame(RequestPolicyInterface::DENY, $actual_result);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Core\PageCache\NoSessionOpenTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Core\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Drupal\Core\PageCache\RequestPolicy;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\PageCache\RequestPolicy\NoSessionOpen
|
||||
* @group PageCache
|
||||
*/
|
||||
class NoSessionOpenTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The session cookie name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionCookieName;
|
||||
|
||||
/**
|
||||
* The request policy under test.
|
||||
*
|
||||
* @var \Drupal\Core\PageCache\RequestPolicy\NoSessionOpen
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
public function setUp() {
|
||||
$this->sessionCookieName = 'B1ESkdf3V4F8u27myaSAShuuHc';
|
||||
$this->policy = new RequestPolicy\NoSessionOpen($this->sessionCookieName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that caching is allowed unless there is a session cookie present.
|
||||
*
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testNoAllowUnlessSessionCookiePresent() {
|
||||
$request = new Request();
|
||||
$result = $this->policy->check($request);
|
||||
$this->assertSame(RequestPolicyInterface::ALLOW, $result);
|
||||
|
||||
$request = Request::create('/', 'GET', [], [$this->sessionCookieName => 'some-session-id']);
|
||||
$result = $this->policy->check($request);
|
||||
$this->assertSame(NULL, $result);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue