Linting fixes

This commit is contained in:
Daniel Hensby 2018-06-12 12:50:37 +01:00 committed by Damian Mooyman
parent 442db3050c
commit bf90af4845
2 changed files with 520 additions and 522 deletions

View File

@ -39,36 +39,36 @@ class HTTP
*/ */
private static $cache_ajax_requests = true; private static $cache_ajax_requests = true;
/** /**
* @config * @config
* @var bool * @var bool
*/ */
private static $disable_http_cache = false; private static $disable_http_cache = false;
/** /**
* Mapping of extension to mime types * Mapping of extension to mime types
* *
* @var array * @var array
* @config * @config
*/ */
private static $MimeTypes = array(); private static $MimeTypes = [];
/** /**
* List of names to add to the Cache-Control header. * List of names to add to the Cache-Control header.
* *
* @see HTTPCacheControlMiddleware::__construct() * @see HTTPCacheControlMiddleware::__construct()
* @config * @config
* @var array Keys are cache control names, values are boolean flags * @var array Keys are cache control names, values are boolean flags
*/ */
private static $cache_control = array(); private static $cache_control = [];
/** /**
* Vary string; A comma separated list of var header names * Vary string; A comma separated list of var header names
* *
* @config * @config
* @var string|null * @var string|null
*/ */
private static $vary = null; private static $vary = null;
/** /**
* Turns a local system filename into a URL by comparing it to the script filename. * Turns a local system filename into a URL by comparing it to the script filename.
@ -146,7 +146,7 @@ class HTTP
} }
// Replace attributes // Replace attributes
$attribs = array("src", "background", "a" => "href", "link" => "href", "base" => "href"); $attribs = ["src", "background", "a" => "href", "link" => "href", "base" => "href"];
$regExps = []; $regExps = [];
foreach ($attribs as $tag => $attrib) { foreach ($attribs as $tag => $attrib) {
if (!is_numeric($tag)) { if (!is_numeric($tag)) {
@ -161,7 +161,7 @@ class HTTP
} }
// Replace css styles // Replace css styles
// @todo - http://www.css3.info/preview/multiple-backgrounds/ // @todo - http://www.css3.info/preview/multiple-backgrounds/
$styles = array('background-image', 'background', 'list-style-image', 'list-style', 'content'); $styles = ['background-image', 'background', 'list-style-image', 'list-style', 'content'];
foreach ($styles as $style) { foreach ($styles as $style) {
$regExps[] = "/($style:[^;]*url *\\(\")([^\"]+)(\"\\))/i"; $regExps[] = "/($style:[^;]*url *\\(\")([^\"]+)(\"\\))/i";
$regExps[] = "/($style:[^;]*url *\\(')([^']+)('\\))/i"; $regExps[] = "/($style:[^;]*url *\\(')([^']+)('\\))/i";
@ -222,7 +222,7 @@ class HTTP
} }
// Parse params and add new variable // Parse params and add new variable
$params = array(); $params = [];
if (isset($parts['query'])) { if (isset($parts['query'])) {
parse_str($parts['query'], $params); parse_str($parts['query'], $params);
} }
@ -230,7 +230,7 @@ class HTTP
// Generate URI segments and formatting // Generate URI segments and formatting
$scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
$user = (isset($parts['user']) && $parts['user'] != '') ? $parts['user'] : ''; $user = (isset($parts['user']) && $parts['user'] != '') ? $parts['user'] : '';
if ($user != '') { if ($user != '') {
// format in either user:pass@host.com or user@host.com // format in either user:pass@host.com or user@host.com
@ -242,13 +242,13 @@ class HTTP
$path = (isset($parts['path']) && $parts['path'] != '') ? $parts['path'] : ''; $path = (isset($parts['path']) && $parts['path'] != '') ? $parts['path'] : '';
// handle URL params which are existing / new // handle URL params which are existing / new
$params = ($params) ? '?' . http_build_query($params, null, $separator) : ''; $params = ($params) ? '?' . http_build_query($params, null, $separator) : '';
// keep fragments (anchors) intact. // keep fragments (anchors) intact.
$fragment = (isset($parts['fragment']) && $parts['fragment'] != '') ? '#' . $parts['fragment'] : ''; $fragment = (isset($parts['fragment']) && $parts['fragment'] != '') ? '#' . $parts['fragment'] : '';
// Recompile URI segments // Recompile URI segments
$newUri = $scheme . '://' . $user . $host . $port . $path . $params . $fragment; $newUri = $scheme . '://' . $user . $host . $port . $path . $params . $fragment;
if ($isRelative) { if ($isRelative) {
return Director::makeRelative($newUri); return Director::makeRelative($newUri);
@ -281,14 +281,14 @@ class HTTP
*/ */
public static function findByTagAndAttribute($content, $attributes) public static function findByTagAndAttribute($content, $attributes)
{ {
$regexes = array(); $regexes = [];
foreach ($attributes as $tag => $attribute) { foreach ($attributes as $tag => $attribute) {
$regexes[] = "/<{$tag} [^>]*$attribute *= *([\"'])(.*?)\\1[^>]*>/i"; $regexes[] = "/<{$tag} [^>]*$attribute *= *([\"'])(.*?)\\1[^>]*>/i";
$regexes[] = "/<{$tag} [^>]*$attribute *= *([^ \"'>]+)/i"; $regexes[] = "/<{$tag} [^>]*$attribute *= *([^ \"'>]+)/i";
} }
$result = array(); $result = [];
if ($regexes) { if ($regexes) {
foreach ($regexes as $regex) { foreach ($regexes as $regex) {
@ -308,7 +308,7 @@ class HTTP
*/ */
public static function getLinksIn($content) public static function getLinksIn($content)
{ {
return self::findByTagAndAttribute($content, array("a" => "href")); return self::findByTagAndAttribute($content, ["a" => "href"]);
} }
/** /**
@ -318,7 +318,7 @@ class HTTP
*/ */
public static function getImagesIn($content) public static function getImagesIn($content)
{ {
return self::findByTagAndAttribute($content, array("img" => "src")); return self::findByTagAndAttribute($content, ["img" => "src"]);
} }
/** /**
@ -423,148 +423,146 @@ class HTTP
} }
// Warn if already assigned cache-control headers // Warn if already assigned cache-control headers
if ($body && $body->getHeader('Cache-Control')) { if ($body && $body->getHeader('Cache-Control')) {
trigger_error( trigger_error(
'Cache-Control header has already been set. ' 'Cache-Control header has already been set. '
. 'Please use HTTPCacheControlMiddleware API to set caching options instead.', . 'Please use HTTPCacheControlMiddleware API to set caching options instead.',
E_USER_WARNING E_USER_WARNING
); );
return; return;
} }
$config = Config::forClass(__CLASS__); $config = Config::forClass(__CLASS__);
// Get current cache control state // Get current cache control state
$cacheControl = HTTPCacheControlMiddleware::singleton(); $cacheControl = HTTPCacheControlMiddleware::singleton();
// if http caching is disabled by config, disable it - used on dev environments due to frequently changing // if http caching is disabled by config, disable it - used on dev environments due to frequently changing
// templates and other data. will be overridden by forced publicCache() or privateCache() calls // templates and other data. will be overridden by forced publicCache() or privateCache() calls
if ($config->get('disable_http_cache')) { if ($config->get('disable_http_cache')) {
$cacheControl->disableCache(); $cacheControl->disableCache();
} }
// Populate $responseHeaders with all the headers that we want to build // Populate $responseHeaders with all the headers that we want to build
$responseHeaders = array(); $responseHeaders = [];
// if no caching ajax requests, disable ajax if is ajax request // if no caching ajax requests, disable ajax if is ajax request
if (!$config->get('cache_ajax_requests') && Director::is_ajax()) { if (!$config->get('cache_ajax_requests') && Director::is_ajax()) {
$cacheControl->disableCache(); $cacheControl->disableCache();
} }
// Errors disable cache (unless some errors are cached intentionally by usercode) // Errors disable cache (unless some errors are cached intentionally by usercode)
if ($body && $body->isError()) { if ($body && $body->isError()) {
// Even if publicCache(true) is specfied, errors will be uncachable // Even if publicCache(true) is specfied, errors will be uncachable
$cacheControl->disableCache(true); $cacheControl->disableCache(true);
} }
// split the current vary header into it's parts and merge it with the config settings // split the current vary header into it's parts and merge it with the config settings
// to create a list of unique vary values // to create a list of unique vary values
$configVary = $config->get('vary'); $configVary = $config->get('vary');
$bodyVary = $body ? $body->getHeader('Vary') : ''; $bodyVary = $body ? $body->getHeader('Vary') : '';
$vary = self::combineVary($configVary, $bodyVary); $vary = self::combineVary($configVary, $bodyVary);
if ($vary) { if ($vary) {
$responseHeaders['Vary'] = $vary; $responseHeaders['Vary'] = $vary;
} }
// deal with IE6-IE8 problems with https and no-cache // deal with IE6-IE8 problems with https and no-cache
$contentDisposition = null; $contentDisposition = null;
if($body) { if ($body) {
// Grab header for checking. Unfortunately HTTPRequest uses a mistyped variant. // Grab header for checking. Unfortunately HTTPRequest uses a mistyped variant.
$contentDisposition = $body->getHeader('Content-Disposition'); $contentDisposition = $body->getHeader('Content-Disposition');
} }
if( if ($body &&
$body && Director::is_https() &&
Director::is_https() && isset($_SERVER['HTTP_USER_AGENT']) &&
isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') == true &&
strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') == true && strstr($contentDisposition, 'attachment;') == true &&
strstr($contentDisposition, 'attachment;') == true && ($cacheControl->hasDirective('no-cache') || $cacheControl->hasDirective('no-store'))
($cacheControl->hasDirective('no-cache') || $cacheControl->hasDirective('no-store')) ) {
) { // IE6-IE8 have problems saving files when https and no-cache/no-store are used
// IE6-IE8 have problems saving files when https and no-cache/no-store are used // (http://support.microsoft.com/kb/323308)
// (http://support.microsoft.com/kb/323308) // Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options.
// Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options. $cacheControl->privateCache(true);
$cacheControl->privateCache(true); }
}
if (self::$modification_date) { if (self::$modification_date) {
$responseHeaders["Last-Modified"] = self::gmt_date(self::$modification_date); $responseHeaders["Last-Modified"] = self::gmt_date(self::$modification_date);
} }
// if we can store the cache responses we should generate and send etags // if we can store the cache responses we should generate and send etags
if (!$cacheControl->hasDirective('no-store')) { if (!$cacheControl->hasDirective('no-store')) {
// Chrome ignores Varies when redirecting back (http://code.google.com/p/chromium/issues/detail?id=79758) // Chrome ignores Varies when redirecting back (http://code.google.com/p/chromium/issues/detail?id=79758)
// which means that if you log out, you get redirected back to a page which Chrome then checks against // which means that if you log out, you get redirected back to a page which Chrome then checks against
// last-modified (which passes, getting a 304) // last-modified (which passes, getting a 304)
// when it shouldn't be trying to use that page at all because it's the "logged in" version. // when it shouldn't be trying to use that page at all because it's the "logged in" version.
// By also using and etag that includes both the modification date and all the varies // By also using and etag that includes both the modification date and all the varies
// values which we also check against we can catch this and not return a 304 // values which we also check against we can catch this and not return a 304
$etag = self::generateETag($body); $etag = self::generateETag($body);
if ($etag) { if ($etag) {
$responseHeaders['ETag'] = $etag; $responseHeaders['ETag'] = $etag;
// 304 response detection // 304 response detection
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
// As above, only 304 if the last request had all the same varies values // As above, only 304 if the last request had all the same varies values
// (or the etag isn't passed as part of the request - but with chrome it always is) // (or the etag isn't passed as part of the request - but with chrome it always is)
$matchesEtag = $_SERVER['HTTP_IF_NONE_MATCH'] == $etag; $matchesEtag = $_SERVER['HTTP_IF_NONE_MATCH'] == $etag;
if ($matchesEtag) { if ($matchesEtag) {
if ($body) { if ($body) {
$body->setStatusCode(304); $body->setStatusCode(304);
$body->setBody(''); $body->setBody('');
} else { } else {
// this is wrong, we need to send the same vary headers and so on // this is wrong, we need to send the same vary headers and so on
header('HTTP/1.0 304 Not Modified'); header('HTTP/1.0 304 Not Modified');
die(); die();
} }
} }
} }
} }
} }
if ($cacheControl->hasDirective('max-age')) { if ($cacheControl->hasDirective('max-age')) {
$expires = time() + $cacheControl->getDirective('max-age'); $expires = time() + $cacheControl->getDirective('max-age');
$responseHeaders["Expires"] = self::gmt_date($expires); $responseHeaders["Expires"] = self::gmt_date($expires);
} }
// etag needs to be a quoted string according to HTTP spec // etag needs to be a quoted string according to HTTP spec
if (!empty($responseHeaders['ETag']) && 0 !== strpos($responseHeaders['ETag'], '"')) { if (!empty($responseHeaders['ETag']) && 0 !== strpos($responseHeaders['ETag'], '"')) {
$responseHeaders['ETag'] = sprintf('"%s"', $responseHeaders['ETag']); $responseHeaders['ETag'] = sprintf('"%s"', $responseHeaders['ETag']);
} }
// Merge with cache control headers // Merge with cache control headers
$responseHeaders = array_merge($responseHeaders, $cacheControl->generateHeaders()); $responseHeaders = array_merge($responseHeaders, $cacheControl->generateHeaders());
// Now that we've generated them, either output them or attach them to the SS_HTTPResponse as appropriate // Now that we've generated them, either output them or attach them to the SS_HTTPResponse as appropriate
foreach($responseHeaders as $k => $v) { foreach ($responseHeaders as $k => $v) {
if($body) { if ($body) {
// Set the header now if it's not already set. // Set the header now if it's not already set.
if ($body->getHeader($k) === null) { if ($body->getHeader($k) === null) {
$body->addHeader($k, $v); $body->addHeader($k, $v);
} }
} elseif(!headers_sent()) { } elseif (!headers_sent()) {
header("$k: $v"); header("$k: $v");
} }
} }
} }
/**
* @param HTTPResponse|string $response
*
* @return string|false
*/
protected static function generateETag($response)
{
// Explicit etag
if (self::$etag) {
return self::$etag;
}
/** // Existing e-tag
* @param HTTPResponse|string $response if ($response instanceof HTTPResponse && $response->getHeader('ETag')) {
*
* @return string|false
*/
protected static function generateETag($response)
{
// Explicit etag
if (self::$etag) {
return self::$etag;
}
// Existing e-tag
if ($response instanceof HTTPResponse && $response->getHeader('ETag')) {
return $response->getHeader('ETag'); return $response->getHeader('ETag');
} }
@ -572,11 +570,11 @@ class HTTP
$body = $response instanceof HTTPResponse $body = $response instanceof HTTPResponse
? $response->getBody() ? $response->getBody()
: $response; : $response;
if ($body) { if ($body) {
return sprintf('"%s"', md5($response)); return sprintf('"%s"', md5($response));
} }
return false; return false;
} }
/** /**
@ -602,21 +600,21 @@ class HTTP
return self::$cache_age; return self::$cache_age;
} }
/** /**
* Combine vary strings * Combine vary strings
* *
* @param string $vary,... Each vary as a separate arg * @param string $vary,... Each vary as a separate arg
* @return string * @return string
*/ */
protected static function combineVary($vary) protected static function combineVary($vary)
{ {
$varies = array(); $varies = [];
foreach (func_get_args() as $arg) { foreach (func_get_args() as $arg) {
$argVaries = array_filter(preg_split("/\s*,\s*/", trim($arg))); $argVaries = array_filter(preg_split("/\s*,\s*/", trim($arg)));
if ($argVaries) { if ($argVaries) {
$varies = array_merge($varies, $argVaries); $varies = array_merge($varies, $argVaries);
} }
} }
return implode(', ', array_unique($varies)); return implode(', ', array_unique($varies));
} }
} }

View File

@ -42,33 +42,33 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
$response = $ex->getResponse(); $response = $ex->getResponse();
} }
// If sessions exist we assume that the responses should not be cached by CDNs / proxies as we are // If sessions exist we assume that the responses should not be cached by CDNs / proxies as we are
// likely to be supplying information relevant to the current user only // likely to be supplying information relevant to the current user only
if ($request->getSession()->getAll()) { if ($request->getSession()->getAll()) {
// Don't force in case user code chooses to opt in to public caching // Don't force in case user code chooses to opt in to public caching
$this->privateCache(); $this->privateCache();
} }
HTTP::add_cache_headers($response); HTTP::add_cache_headers($response);
return $response; return $response;
} }
/** /**
* List of states, each of which contains a key of standard directives. * List of states, each of which contains a key of standard directives.
* Each directive should either be a numeric value, true to enable, * Each directive should either be a numeric value, true to enable,
* or (bool)false or null to disable. * or (bool)false or null to disable.
* Top level key states include `disabled`, `private`, `public`, `enabled` * Top level key states include `disabled`, `private`, `public`, `enabled`
* in descending order of precedence. * in descending order of precedence.
* *
* This allows directives to be set independently for individual states. * This allows directives to be set independently for individual states.
* *
* @var array * @var array
*/ */
protected $stateDirectives = [ protected $stateDirectives = [
self::STATE_DISABLED => [ self::STATE_DISABLED => [
'no-cache' => true, 'no-cache' => true,
'no-store' => true, 'no-store' => true,
'must-revalidate' => true, 'must-revalidate' => true,
], ],
self::STATE_PRIVATE => [ self::STATE_PRIVATE => [
'private' => true, 'private' => true,
@ -88,61 +88,61 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
* *
* @var string * @var string
*/ */
protected $state = self::STATE_ENABLED; protected $state = self::STATE_ENABLED;
/** /**
* Forcing level of previous setting; higher number wins * Forcing level of previous setting; higher number wins
* Combination of consts belo * Combination of consts belo
*w *w
* @var int * @var int
*/ */
protected $forcingLevel = 0; protected $forcingLevel = 0;
/** /**
* Forcing level forced, optionally combined with one of the below. * Forcing level forced, optionally combined with one of the below.
*/ */
const LEVEL_FORCED = 10; const LEVEL_FORCED = 10;
/** /**
* Forcing level caching disabled. Overrides public/private. * Forcing level caching disabled. Overrides public/private.
*/ */
const LEVEL_DISABLED = 3; const LEVEL_DISABLED = 3;
/** /**
* Forcing level private-cached. Overrides public. * Forcing level private-cached. Overrides public.
*/ */
const LEVEL_PRIVATE = 2; const LEVEL_PRIVATE = 2;
/** /**
* Forcing level public cached. Lowest priority. * Forcing level public cached. Lowest priority.
*/ */
const LEVEL_PUBLIC = 1; const LEVEL_PUBLIC = 1;
/** /**
* Forcing level caching enabled. * Forcing level caching enabled.
*/ */
const LEVEL_ENABLED = 0; const LEVEL_ENABLED = 0;
/** /**
* A list of allowed cache directives for HTTPResponses * A list of allowed cache directives for HTTPResponses
* *
* This doesn't include any experimental directives, * This doesn't include any experimental directives,
* use the config system to add to these if you want to enable them * use the config system to add to these if you want to enable them
* *
* @config * @config
* @var array * @var array
*/ */
private static $allowed_directives = array( private static $allowed_directives = [
'public', 'public',
'private', 'private',
'no-cache', 'no-cache',
'max-age', 'max-age',
's-maxage', 's-maxage',
'must-revalidate', 'must-revalidate',
'proxy-revalidate', 'proxy-revalidate',
'no-store', 'no-store',
'no-transform', 'no-transform',
); ];
/** /**
* Set current state. Should only be invoked internally after processing precedence rules. * Set current state. Should only be invoked internally after processing precedence rules.
@ -150,7 +150,7 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
* @param string $state * @param string $state
* @return $this * @return $this
*/ */
protected function setState($state) protected function setState($state)
{ {
if (!array_key_exists($state, $this->stateDirectives)) { if (!array_key_exists($state, $this->stateDirectives)) {
throw new InvalidArgumentException("Invalid state {$state}"); throw new InvalidArgumentException("Invalid state {$state}");
@ -169,74 +169,74 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
return $this->state; return $this->state;
} }
/** /**
* Instruct the cache to apply a change with a given level, optionally * Instruct the cache to apply a change with a given level, optionally
* modifying it with a force flag to increase priority of this action. * modifying it with a force flag to increase priority of this action.
* *
* If the apply level was successful, the change is made and the internal level * If the apply level was successful, the change is made and the internal level
* threshold is incremented. * threshold is incremented.
* *
* @param int $level Priority of the given change * @param int $level Priority of the given change
* @param bool $force If usercode has requested this action is forced to a higher priority. * @param bool $force If usercode has requested this action is forced to a higher priority.
* Note: Even if $force is set to true, other higher-priority forced changes can still * Note: Even if $force is set to true, other higher-priority forced changes can still
* cause a change to be rejected if it is below the required threshold. * cause a change to be rejected if it is below the required threshold.
* @return bool True if the given change is accepted, and that the internal * @return bool True if the given change is accepted, and that the internal
* level threshold is updated (if necessary) to the new minimum level. * level threshold is updated (if necessary) to the new minimum level.
*/ */
protected function applyChangeLevel($level, $force) protected function applyChangeLevel($level, $force)
{ {
$forcingLevel = $level + ($force ? self::LEVEL_FORCED : 0); $forcingLevel = $level + ($force ? self::LEVEL_FORCED : 0);
if ($forcingLevel < $this->forcingLevel) { if ($forcingLevel < $this->forcingLevel) {
return false; return false;
} }
$this->forcingLevel = $forcingLevel; $this->forcingLevel = $forcingLevel;
return true; return true;
} }
/** /**
* Low level method for setting directives include any experimental or custom ones added via config. * Low level method for setting directives include any experimental or custom ones added via config.
* You need to specify the state (or states) to apply this directive to. * You need to specify the state (or states) to apply this directive to.
* Can also remove directives with false * Can also remove directives with false
* *
* @param array|string $states State(s) to apply this directive to * @param array|string $states State(s) to apply this directive to
* @param string $directive * @param string $directive
* @param int|string|bool $value Flag to set for this value. Set to false to remove, or true to set. * @param int|string|bool $value Flag to set for this value. Set to false to remove, or true to set.
* String or int value assign a specific value. * String or int value assign a specific value.
* @return $this * @return $this
*/ */
public function setStateDirective($states, $directive, $value = true) public function setStateDirective($states, $directive, $value = true)
{ {
if ($value === null) { if ($value === null) {
throw new InvalidArgumentException("Invalid directive value"); throw new InvalidArgumentException("Invalid directive value");
} }
// make sure the directive is in the list of allowed directives // make sure the directive is in the list of allowed directives
$allowedDirectives = $this->config()->get('allowed_directives'); $allowedDirectives = $this->config()->get('allowed_directives');
$directive = strtolower($directive); $directive = strtolower($directive);
if (!in_array($directive, $allowedDirectives)) { if (!in_array($directive, $allowedDirectives)) {
throw new InvalidArgumentException('Directive ' . $directive . ' is not allowed'); throw new InvalidArgumentException('Directive ' . $directive . ' is not allowed');
} }
foreach ((array)$states as $state) { foreach ((array)$states as $state) {
if (!array_key_exists($state, $this->stateDirectives)) { if (!array_key_exists($state, $this->stateDirectives)) {
throw new InvalidArgumentException("Invalid state {$state}"); throw new InvalidArgumentException("Invalid state {$state}");
} }
// Set or unset directive // Set or unset directive
if ($value === false) { if ($value === false) {
unset($this->stateDirectives[$state][$directive]); unset($this->stateDirectives[$state][$directive]);
} else { } else {
$this->stateDirectives[$state][$directive] = $value; $this->stateDirectives[$state][$directive] = $value;
} }
} }
return $this; return $this;
} }
/** /**
* Low level method to set directives from an associative array * Low level method to set directives from an associative array
* *
* @param array|string $states State(s) to apply this directive to * @param array|string $states State(s) to apply this directive to
* @param array $directives * @param array $directives
* @return $this * @return $this
*/ */
public function setStateDirectivesFromArray($states, $directives) public function setStateDirectivesFromArray($states, $directives)
{ {
foreach ($directives as $directive => $value) { foreach ($directives as $directive => $value) {
$this->setStateDirective($states, $directive, $value); $this->setStateDirective($states, $directive, $value);
@ -244,31 +244,31 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
return $this; return $this;
} }
/** /**
* Low level method for removing directives * Low level method for removing directives
* *
* @param array|string $states State(s) to remove this directive from * @param array|string $states State(s) to remove this directive from
* @param string $directive * @param string $directive
* @return $this * @return $this
*/ */
public function removeStateDirective($states, $directive) public function removeStateDirective($states, $directive)
{ {
$this->setStateDirective($states, $directive, false); $this->setStateDirective($states, $directive, false);
return $this; return $this;
} }
/** /**
* Low level method to check if a directive is currently set * Low level method to check if a directive is currently set
* *
* @param string $state State(s) to apply this directive to * @param string $state State(s) to apply this directive to
* @param string $directive * @param string $directive
* @return bool * @return bool
*/ */
public function hasStateDirective($state, $directive) public function hasStateDirective($state, $directive)
{ {
$directive = strtolower($directive); $directive = strtolower($directive);
return isset($this->stateDirectives[$state][$directive]); return isset($this->stateDirectives[$state][$directive]);
} }
/** /**
* Check if the current state has the given directive. * Check if the current state has the given directive.
@ -276,28 +276,28 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
* @param string $directive * @param string $directive
* @return bool * @return bool
*/ */
public function hasDirective($directive) public function hasDirective($directive)
{ {
return $this->hasStateDirective($this->getState(), $directive); return $this->hasStateDirective($this->getState(), $directive);
} }
/** /**
* Low level method to get the value of a directive for a state. * Low level method to get the value of a directive for a state.
* Returns false if there is no directive. * Returns false if there is no directive.
* True means the flag is set, otherwise the value of the directive. * True means the flag is set, otherwise the value of the directive.
* *
* @param string $state * @param string $state
* @param string $directive * @param string $directive
* @return int|string|bool * @return int|string|bool
*/ */
public function getStateDirective($state, $directive) public function getStateDirective($state, $directive)
{ {
$directive = strtolower($directive); $directive = strtolower($directive);
if (isset($this->stateDirectives[$state][$directive])) { if (isset($this->stateDirectives[$state][$directive])) {
return $this->stateDirectives[$state][$directive]; return $this->stateDirectives[$state][$directive];
} }
return false; return false;
} }
/** /**
* Get the value of the given directive for the current state * Get the value of the given directive for the current state
@ -307,7 +307,7 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
*/ */
public function getDirective($directive) public function getDirective($directive)
{ {
return $this->getStateDirective($this->getState(), $directive); return $this->getStateDirective($this->getState(), $directive);
} }
/** /**
@ -331,227 +331,227 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable
return $this->getStateDirectives($this->getState()); return $this->getStateDirectives($this->getState());
} }
/** /**
* The cache should not store anything about the client request or server response. * The cache should not store anything about the client request or server response.
* Affects all non-disabled states. Use setStateDirective() instead to set for a single state. * Affects all non-disabled states. Use setStateDirective() instead to set for a single state.
* Set the no-store directive (also removes max-age and s-maxage for consistency purposes) * Set the no-store directive (also removes max-age and s-maxage for consistency purposes)
* *
* @param bool $noStore * @param bool $noStore
* *
* @return $this * @return $this
*/ */
public function setNoStore($noStore = true) public function setNoStore($noStore = true)
{ {
// Affect all non-disabled states // Affect all non-disabled states
$applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC];
if ($noStore) { if ($noStore) {
$this->setStateDirective($applyTo, 'no-store'); $this->setStateDirective($applyTo, 'no-store');
$this->removeStateDirective($applyTo, 'max-age'); $this->removeStateDirective($applyTo, 'max-age');
$this->removeStateDirective($applyTo, 's-maxage'); $this->removeStateDirective($applyTo, 's-maxage');
} else { } else {
$this->removeStateDirective($applyTo, 'no-store'); $this->removeStateDirective($applyTo, 'no-store');
} }
return $this; return $this;
} }
/** /**
* Forces caches to submit the request to the origin server for validation before releasing a cached copy. * Forces caches to submit the request to the origin server for validation before releasing a cached copy.
* Affects all non-disabled states. Use setStateDirective() instead to set for a single state. * Affects all non-disabled states. Use setStateDirective() instead to set for a single state.
* *
* @param bool $noCache * @param bool $noCache
* @return $this * @return $this
*/ */
public function setNoCache($noCache = true) public function setNoCache($noCache = true)
{ {
// Affect all non-disabled states // Affect all non-disabled states
$applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC];
$this->setStateDirective($applyTo, 'no-cache', $noCache); $this->setStateDirective($applyTo, 'no-cache', $noCache);
return $this; return $this;
} }
/** /**
* Specifies the maximum amount of time (seconds) a resource will be considered fresh. * Specifies the maximum amount of time (seconds) a resource will be considered fresh.
* This directive is relative to the time of the request. * This directive is relative to the time of the request.
* Affects all non-disabled states. Use setStateDirective() instead to set for a single state. * Affects all non-disabled states. Use setStateDirective() instead to set for a single state.
*
* @param int $age
* @return $this
*/
public function setMaxAge($age)
{
// Affect all non-disabled states
$applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC];
$this->setStateDirective($applyTo, 'max-age', $age);
return $this;
}
/**
* Overrides max-age or the Expires header, but it only applies to shared caches (e.g., proxies)
* and is ignored by a private cache.
* Affects all non-disabled states. Use setStateDirective() instead to set for a single state.
* *
* @param int $age * @param int $age
* @return $this * @return $this
*/ */
public function setSharedMaxAge($age) public function setMaxAge($age)
{ {
// Affect all non-disabled states // Affect all non-disabled states
$applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC];
$this->setStateDirective($applyTo, 's-maxage', $age); $this->setStateDirective($applyTo, 'max-age', $age);
return $this; return $this;
} }
/** /**
* The cache must verify the status of the stale resources before using it and expired ones should not be used. * Overrides max-age or the Expires header, but it only applies to shared caches (e.g., proxies)
* Affects all non-disabled states. Use setStateDirective() instead to set for a single state. * and is ignored by a private cache.
* Affects all non-disabled states. Use setStateDirective() instead to set for a single state.
* *
* @param bool $mustRevalidate * @param int $age
* @return $this * @return $this
*/ */
public function setMustRevalidate($mustRevalidate = true) public function setSharedMaxAge($age)
{ {
$applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; // Affect all non-disabled states
$this->setStateDirective($applyTo, 'must-revalidate', $mustRevalidate); $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC];
return $this; $this->setStateDirective($applyTo, 's-maxage', $age);
} return $this;
}
/** /**
* Simple way to set cache control header to a cacheable state. * The cache must verify the status of the stale resources before using it and expired ones should not be used.
* Affects all non-disabled states. Use setStateDirective() instead to set for a single state.
*
* @param bool $mustRevalidate
* @return $this
*/
public function setMustRevalidate($mustRevalidate = true)
{
$applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC];
$this->setStateDirective($applyTo, 'must-revalidate', $mustRevalidate);
return $this;
}
/**
* Simple way to set cache control header to a cacheable state.
* *
* The resulting cache-control headers will be chosen from the 'enabled' set of directives. * The resulting cache-control headers will be chosen from the 'enabled' set of directives.
* *
* Does not set `public` directive. Usually, `setMaxAge()` is sufficient. Use `publicCache()` if this is explicitly required. * Does not set `public` directive. Usually, `setMaxAge()` is sufficient. Use `publicCache()` if this is explicitly required.
* See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#public_vs_private * See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#public_vs_private
* *
* @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/ * @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/
* @param bool $force Force the cache to public even if its unforced private or public * @param bool $force Force the cache to public even if its unforced private or public
* @return $this * @return $this
*/ */
public function enableCache($force = false) public function enableCache($force = false)
{ {
// Only execute this if its forcing level is high enough // Only execute this if its forcing level is high enough
if ($this->applyChangeLevel(self::LEVEL_ENABLED, $force)) { if ($this->applyChangeLevel(self::LEVEL_ENABLED, $force)) {
$this->setState(self::STATE_ENABLED); $this->setState(self::STATE_ENABLED);
} }
return $this; return $this;
} }
/** /**
* Simple way to set cache control header to a non-cacheable state. * Simple way to set cache control header to a non-cacheable state.
* Use this method over `privateCache()` if you are unsure about caching details. * Use this method over `privateCache()` if you are unsure about caching details.
* Takes precendence over unforced `enableCache()`, `privateCache()` or `publicCache()` calls. * Takes precendence over unforced `enableCache()`, `privateCache()` or `publicCache()` calls.
* *
* The resulting cache-control headers will be chosen from the 'disabled' set of directives. * The resulting cache-control headers will be chosen from the 'disabled' set of directives.
*
* Removes all state and replaces it with `no-cache, no-store, must-revalidate`. Although `no-store` is sufficient
* the others are added under recommendation from Mozilla (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Examples)
*
* Does not set `private` directive, use `privateCache()` if this is explicitly required.
* See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#public_vs_private
*
* @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/
* @param bool $force Force the cache to diabled even if it's forced private or public
* @return $this
*/
public function disableCache($force = false)
{
// Only execute this if its forcing level is high enough
if ($this->applyChangeLevel(self::LEVEL_DISABLED, $force )) {
$this->setState(self::STATE_DISABLED);
}
return $this;
}
/**
* Advanced way to set cache control header to a non-cacheable state.
* Indicates that the response is intended for a single user and must not be stored by a shared cache.
* A private cache (e.g. Web Browser) may store the response.
* *
* The resulting cache-control headers will be chosen from the 'private' set of directives. * Removes all state and replaces it with `no-cache, no-store, must-revalidate`. Although `no-store` is sufficient
* * the others are added under recommendation from Mozilla (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Examples)
* @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/
* @param bool $force Force the cache to private even if it's forced public
* @return $this
*/
public function privateCache($force = false)
{
// Only execute this if its forcing level is high enough
if ($this->applyChangeLevel(self::LEVEL_PRIVATE, $force)) {
$this->setState(self::STATE_PRIVATE);
}
return $this;
}
/**
* Advanced way to set cache control header to a cacheable state.
* Indicates that the response may be cached by any cache. (eg: CDNs, Proxies, Web browsers)
* *
* The resulting cache-control headers will be chosen from the 'private' set of directives. * Does not set `private` directive, use `privateCache()` if this is explicitly required.
* * See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#public_vs_private
* @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/ *
* @param bool $force Force the cache to public even if it's private, unless it's been forced private * @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/
* @return $this * @param bool $force Force the cache to diabled even if it's forced private or public
*/ * @return $this
public function publicCache($force = false) */
{ public function disableCache($force = false)
// Only execute this if its forcing level is high enough
if (!$this->applyChangeLevel(self::LEVEL_PUBLIC, $force)) {
$this->setState(self::STATE_PUBLIC);
}
return $this;
}
/**
* Generate and add the `Cache-Control` header to a response object
*
* @param HTTPResponse $response
*
* @return $this
*/
public function applyToResponse($response)
{
$headers = $this->generateHeaders();
foreach ($headers as $name => $value) {
$response->addHeader($name, $value);
}
return $this;
}
/**
* Generate the cache header
*
* @return string
*/
protected function generateCacheHeader()
{
$cacheControl = [];
foreach ($this->getDirectives() as $directive => $value) {
if ($value === true) {
$cacheControl[] = $directive;
} else {
$cacheControl[] = $directive . '=' . $value;
}
}
return implode(', ', $cacheControl);
}
/**
* Generate all headers to output
*
* @return array
*/
public function generateHeaders()
{
return array(
'Cache-Control' => $this->generateCacheHeader(),
);
}
/**
* Reset registered http cache control and force a fresh instance to be built
*/
public static function reset()
{ {
Injector::inst()->unregisterNamedObject(__CLASS__); // Only execute this if its forcing level is high enough
} if ($this->applyChangeLevel(self::LEVEL_DISABLED, $force)) {
$this->setState(self::STATE_DISABLED);
}
return $this;
}
/**
* Advanced way to set cache control header to a non-cacheable state.
* Indicates that the response is intended for a single user and must not be stored by a shared cache.
* A private cache (e.g. Web Browser) may store the response.
*
* The resulting cache-control headers will be chosen from the 'private' set of directives.
*
* @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/
* @param bool $force Force the cache to private even if it's forced public
* @return $this
*/
public function privateCache($force = false)
{
// Only execute this if its forcing level is high enough
if ($this->applyChangeLevel(self::LEVEL_PRIVATE, $force)) {
$this->setState(self::STATE_PRIVATE);
}
return $this;
}
/**
* Advanced way to set cache control header to a cacheable state.
* Indicates that the response may be cached by any cache. (eg: CDNs, Proxies, Web browsers)
*
* The resulting cache-control headers will be chosen from the 'private' set of directives.
*
* @see https://docs.silverstripe.org/en/developer_guides/performance/http_cache_headers/
* @param bool $force Force the cache to public even if it's private, unless it's been forced private
* @return $this
*/
public function publicCache($force = false)
{
// Only execute this if its forcing level is high enough
if (!$this->applyChangeLevel(self::LEVEL_PUBLIC, $force)) {
$this->setState(self::STATE_PUBLIC);
}
return $this;
}
/**
* Generate and add the `Cache-Control` header to a response object
*
* @param HTTPResponse $response
*
* @return $this
*/
public function applyToResponse($response)
{
$headers = $this->generateHeaders();
foreach ($headers as $name => $value) {
$response->addHeader($name, $value);
}
return $this;
}
/**
* Generate the cache header
*
* @return string
*/
protected function generateCacheHeader()
{
$cacheControl = [];
foreach ($this->getDirectives() as $directive => $value) {
if ($value === true) {
$cacheControl[] = $directive;
} else {
$cacheControl[] = $directive . '=' . $value;
}
}
return implode(', ', $cacheControl);
}
/**
* Generate all headers to output
*
* @return array
*/
public function generateHeaders()
{
return [
'Cache-Control' => $this->generateCacheHeader(),
];
}
/**
* Reset registered http cache control and force a fresh instance to be built
*/
public static function reset()
{
Injector::inst()->unregisterNamedObject(__CLASS__);
}
} }