diff --git a/src/Control/HTTP.php b/src/Control/HTTP.php index daf5f5cec..1227dda52 100644 --- a/src/Control/HTTP.php +++ b/src/Control/HTTP.php @@ -39,36 +39,36 @@ class HTTP */ private static $cache_ajax_requests = true; - /** - * @config - * @var bool - */ - private static $disable_http_cache = false; + /** + * @config + * @var bool + */ + private static $disable_http_cache = false; - /** - * Mapping of extension to mime types - * - * @var array - * @config - */ - private static $MimeTypes = array(); + /** + * Mapping of extension to mime types + * + * @var array + * @config + */ + private static $MimeTypes = []; - /** - * List of names to add to the Cache-Control header. - * - * @see HTTPCacheControlMiddleware::__construct() - * @config - * @var array Keys are cache control names, values are boolean flags - */ - private static $cache_control = array(); + /** + * List of names to add to the Cache-Control header. + * + * @see HTTPCacheControlMiddleware::__construct() + * @config + * @var array Keys are cache control names, values are boolean flags + */ + private static $cache_control = []; - /** - * Vary string; A comma separated list of var header names - * - * @config - * @var string|null - */ - private static $vary = null; + /** + * Vary string; A comma separated list of var header names + * + * @config + * @var string|null + */ + private static $vary = null; /** * Turns a local system filename into a URL by comparing it to the script filename. @@ -146,7 +146,7 @@ class HTTP } // Replace attributes - $attribs = array("src", "background", "a" => "href", "link" => "href", "base" => "href"); + $attribs = ["src", "background", "a" => "href", "link" => "href", "base" => "href"]; $regExps = []; foreach ($attribs as $tag => $attrib) { if (!is_numeric($tag)) { @@ -161,7 +161,7 @@ class HTTP } // Replace css styles // @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) { $regExps[] = "/($style:[^;]*url *\\(\")([^\"]+)(\"\\))/i"; $regExps[] = "/($style:[^;]*url *\\(')([^']+)('\\))/i"; @@ -222,7 +222,7 @@ class HTTP } // Parse params and add new variable - $params = array(); + $params = []; if (isset($parts['query'])) { parse_str($parts['query'], $params); } @@ -230,7 +230,7 @@ class HTTP // Generate URI segments and formatting $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 != '') { // 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'] : ''; // 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. - $fragment = (isset($parts['fragment']) && $parts['fragment'] != '') ? '#' . $parts['fragment'] : ''; + $fragment = (isset($parts['fragment']) && $parts['fragment'] != '') ? '#' . $parts['fragment'] : ''; // Recompile URI segments - $newUri = $scheme . '://' . $user . $host . $port . $path . $params . $fragment; + $newUri = $scheme . '://' . $user . $host . $port . $path . $params . $fragment; if ($isRelative) { return Director::makeRelative($newUri); @@ -281,14 +281,14 @@ class HTTP */ public static function findByTagAndAttribute($content, $attributes) { - $regexes = array(); + $regexes = []; foreach ($attributes as $tag => $attribute) { $regexes[] = "/<{$tag} [^>]*$attribute *= *([\"'])(.*?)\\1[^>]*>/i"; $regexes[] = "/<{$tag} [^>]*$attribute *= *([^ \"'>]+)/i"; } - $result = array(); + $result = []; if ($regexes) { foreach ($regexes as $regex) { @@ -308,7 +308,7 @@ class HTTP */ 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) { - 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 - if ($body && $body->getHeader('Cache-Control')) { - trigger_error( - 'Cache-Control header has already been set. ' - . 'Please use HTTPCacheControlMiddleware API to set caching options instead.', - E_USER_WARNING - ); - return; - } + if ($body && $body->getHeader('Cache-Control')) { + trigger_error( + 'Cache-Control header has already been set. ' + . 'Please use HTTPCacheControlMiddleware API to set caching options instead.', + E_USER_WARNING + ); + return; + } - $config = Config::forClass(__CLASS__); + $config = Config::forClass(__CLASS__); - // Get current cache control state - $cacheControl = HTTPCacheControlMiddleware::singleton(); + // Get current cache control state + $cacheControl = HTTPCacheControlMiddleware::singleton(); - // 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 - if ($config->get('disable_http_cache')) { - $cacheControl->disableCache(); - } + // 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 + if ($config->get('disable_http_cache')) { + $cacheControl->disableCache(); + } - // Populate $responseHeaders with all the headers that we want to build - $responseHeaders = array(); + // Populate $responseHeaders with all the headers that we want to build + $responseHeaders = []; - // if no caching ajax requests, disable ajax if is ajax request - if (!$config->get('cache_ajax_requests') && Director::is_ajax()) { - $cacheControl->disableCache(); - } + // if no caching ajax requests, disable ajax if is ajax request + if (!$config->get('cache_ajax_requests') && Director::is_ajax()) { + $cacheControl->disableCache(); + } - // Errors disable cache (unless some errors are cached intentionally by usercode) - if ($body && $body->isError()) { - // Even if publicCache(true) is specfied, errors will be uncachable - $cacheControl->disableCache(true); - } + // Errors disable cache (unless some errors are cached intentionally by usercode) + if ($body && $body->isError()) { + // Even if publicCache(true) is specfied, errors will be uncachable + $cacheControl->disableCache(true); + } - // split the current vary header into it's parts and merge it with the config settings - // to create a list of unique vary values - $configVary = $config->get('vary'); - $bodyVary = $body ? $body->getHeader('Vary') : ''; - $vary = self::combineVary($configVary, $bodyVary); - if ($vary) { - $responseHeaders['Vary'] = $vary; - } + // split the current vary header into it's parts and merge it with the config settings + // to create a list of unique vary values + $configVary = $config->get('vary'); + $bodyVary = $body ? $body->getHeader('Vary') : ''; + $vary = self::combineVary($configVary, $bodyVary); + if ($vary) { + $responseHeaders['Vary'] = $vary; + } - // deal with IE6-IE8 problems with https and no-cache - $contentDisposition = null; - if($body) { - // Grab header for checking. Unfortunately HTTPRequest uses a mistyped variant. - $contentDisposition = $body->getHeader('Content-Disposition'); - } + // deal with IE6-IE8 problems with https and no-cache + $contentDisposition = null; + if ($body) { + // Grab header for checking. Unfortunately HTTPRequest uses a mistyped variant. + $contentDisposition = $body->getHeader('Content-Disposition'); + } - if( - $body && - Director::is_https() && - isset($_SERVER['HTTP_USER_AGENT']) && - strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') == true && - strstr($contentDisposition, 'attachment;') == true && - ($cacheControl->hasDirective('no-cache') || $cacheControl->hasDirective('no-store')) - ) { - // IE6-IE8 have problems saving files when https and no-cache/no-store are used - // (http://support.microsoft.com/kb/323308) - // Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options. - $cacheControl->privateCache(true); - } + if ($body && + Director::is_https() && + isset($_SERVER['HTTP_USER_AGENT']) && + strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') == true && + strstr($contentDisposition, 'attachment;') == true && + ($cacheControl->hasDirective('no-cache') || $cacheControl->hasDirective('no-store')) + ) { + // IE6-IE8 have problems saving files when https and no-cache/no-store are used + // (http://support.microsoft.com/kb/323308) + // Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options. + $cacheControl->privateCache(true); + } - if (self::$modification_date) { - $responseHeaders["Last-Modified"] = self::gmt_date(self::$modification_date); - } + if (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 (!$cacheControl->hasDirective('no-store')) { - // 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 - // 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. - // 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 - $etag = self::generateETag($body); - if ($etag) { - $responseHeaders['ETag'] = $etag; + // if we can store the cache responses we should generate and send etags + if (!$cacheControl->hasDirective('no-store')) { + // 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 + // 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. + // 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 + $etag = self::generateETag($body); + if ($etag) { + $responseHeaders['ETag'] = $etag; - // 304 response detection - if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { - // 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) - $matchesEtag = $_SERVER['HTTP_IF_NONE_MATCH'] == $etag; + // 304 response detection + if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + // 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) + $matchesEtag = $_SERVER['HTTP_IF_NONE_MATCH'] == $etag; - if ($matchesEtag) { - if ($body) { - $body->setStatusCode(304); - $body->setBody(''); - } else { - // this is wrong, we need to send the same vary headers and so on - header('HTTP/1.0 304 Not Modified'); - die(); - } - } - } - } - } + if ($matchesEtag) { + if ($body) { + $body->setStatusCode(304); + $body->setBody(''); + } else { + // this is wrong, we need to send the same vary headers and so on + header('HTTP/1.0 304 Not Modified'); + die(); + } + } + } + } + } - if ($cacheControl->hasDirective('max-age')) { - $expires = time() + $cacheControl->getDirective('max-age'); - $responseHeaders["Expires"] = self::gmt_date($expires); - } + if ($cacheControl->hasDirective('max-age')) { + $expires = time() + $cacheControl->getDirective('max-age'); + $responseHeaders["Expires"] = self::gmt_date($expires); + } - // etag needs to be a quoted string according to HTTP spec - if (!empty($responseHeaders['ETag']) && 0 !== strpos($responseHeaders['ETag'], '"')) { - $responseHeaders['ETag'] = sprintf('"%s"', $responseHeaders['ETag']); - } + // etag needs to be a quoted string according to HTTP spec + if (!empty($responseHeaders['ETag']) && 0 !== strpos($responseHeaders['ETag'], '"')) { + $responseHeaders['ETag'] = sprintf('"%s"', $responseHeaders['ETag']); + } - // Merge with cache control headers - $responseHeaders = array_merge($responseHeaders, $cacheControl->generateHeaders()); + // Merge with cache control headers + $responseHeaders = array_merge($responseHeaders, $cacheControl->generateHeaders()); - // Now that we've generated them, either output them or attach them to the SS_HTTPResponse as appropriate - foreach($responseHeaders as $k => $v) { - if($body) { - // Set the header now if it's not already set. - if ($body->getHeader($k) === null) { - $body->addHeader($k, $v); - } - } elseif(!headers_sent()) { - header("$k: $v"); - } - } + // Now that we've generated them, either output them or attach them to the SS_HTTPResponse as appropriate + foreach ($responseHeaders as $k => $v) { + if ($body) { + // Set the header now if it's not already set. + if ($body->getHeader($k) === null) { + $body->addHeader($k, $v); + } + } elseif (!headers_sent()) { + header("$k: $v"); + } + } } + /** + * @param HTTPResponse|string $response + * + * @return string|false + */ + protected static function generateETag($response) + { + // Explicit etag + if (self::$etag) { + return self::$etag; + } - /** - * @param HTTPResponse|string $response - * - * @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')) { + // Existing e-tag + if ($response instanceof HTTPResponse && $response->getHeader('ETag')) { return $response->getHeader('ETag'); } @@ -572,11 +570,11 @@ class HTTP $body = $response instanceof HTTPResponse ? $response->getBody() : $response; - if ($body) { - return sprintf('"%s"', md5($response)); - } - return false; - } + if ($body) { + return sprintf('"%s"', md5($response)); + } + return false; + } /** @@ -602,21 +600,21 @@ class HTTP return self::$cache_age; } - /** - * Combine vary strings - * - * @param string $vary,... Each vary as a separate arg - * @return string - */ - protected static function combineVary($vary) - { - $varies = array(); - foreach (func_get_args() as $arg) { - $argVaries = array_filter(preg_split("/\s*,\s*/", trim($arg))); - if ($argVaries) { - $varies = array_merge($varies, $argVaries); - } - } - return implode(', ', array_unique($varies)); - } + /** + * Combine vary strings + * + * @param string $vary,... Each vary as a separate arg + * @return string + */ + protected static function combineVary($vary) + { + $varies = []; + foreach (func_get_args() as $arg) { + $argVaries = array_filter(preg_split("/\s*,\s*/", trim($arg))); + if ($argVaries) { + $varies = array_merge($varies, $argVaries); + } + } + return implode(', ', array_unique($varies)); + } } diff --git a/src/Control/Middleware/HTTPCacheControlMiddleware.php b/src/Control/Middleware/HTTPCacheControlMiddleware.php index 2f92f4a01..9db482432 100644 --- a/src/Control/Middleware/HTTPCacheControlMiddleware.php +++ b/src/Control/Middleware/HTTPCacheControlMiddleware.php @@ -42,33 +42,33 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable $response = $ex->getResponse(); } - // 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 - if ($request->getSession()->getAll()) { - // Don't force in case user code chooses to opt in to public caching - $this->privateCache(); - } + // 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 + if ($request->getSession()->getAll()) { + // Don't force in case user code chooses to opt in to public caching + $this->privateCache(); + } HTTP::add_cache_headers($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, * or (bool)false or null to disable. * Top level key states include `disabled`, `private`, `public`, `enabled` * in descending order of precedence. * * This allows directives to be set independently for individual states. - * - * @var array - */ - protected $stateDirectives = [ - self::STATE_DISABLED => [ - 'no-cache' => true, - 'no-store' => true, - 'must-revalidate' => true, + * + * @var array + */ + protected $stateDirectives = [ + self::STATE_DISABLED => [ + 'no-cache' => true, + 'no-store' => true, + 'must-revalidate' => true, ], self::STATE_PRIVATE => [ 'private' => true, @@ -88,61 +88,61 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable * * @var string */ - protected $state = self::STATE_ENABLED; + protected $state = self::STATE_ENABLED; - /** - * Forcing level of previous setting; higher number wins - * Combination of consts belo - *w - * @var int - */ - protected $forcingLevel = 0; + /** + * Forcing level of previous setting; higher number wins + * Combination of consts belo + *w + * @var int + */ + protected $forcingLevel = 0; - /** - * Forcing level forced, optionally combined with one of the below. - */ - const LEVEL_FORCED = 10; + /** + * Forcing level forced, optionally combined with one of the below. + */ + const LEVEL_FORCED = 10; - /** - * Forcing level caching disabled. Overrides public/private. - */ - const LEVEL_DISABLED = 3; + /** + * Forcing level caching disabled. Overrides public/private. + */ + const LEVEL_DISABLED = 3; - /** - * Forcing level private-cached. Overrides public. - */ - const LEVEL_PRIVATE = 2; + /** + * Forcing level private-cached. Overrides public. + */ + const LEVEL_PRIVATE = 2; - /** - * Forcing level public cached. Lowest priority. - */ - const LEVEL_PUBLIC = 1; + /** + * Forcing level public cached. Lowest priority. + */ + const LEVEL_PUBLIC = 1; - /** - * Forcing level caching enabled. - */ - const LEVEL_ENABLED = 0; + /** + * Forcing level caching enabled. + */ + const LEVEL_ENABLED = 0; - /** - * A list of allowed cache directives for HTTPResponses - * - * This doesn't include any experimental directives, - * use the config system to add to these if you want to enable them - * - * @config - * @var array - */ - private static $allowed_directives = array( - 'public', - 'private', - 'no-cache', - 'max-age', - 's-maxage', - 'must-revalidate', - 'proxy-revalidate', - 'no-store', - 'no-transform', - ); + /** + * A list of allowed cache directives for HTTPResponses + * + * This doesn't include any experimental directives, + * use the config system to add to these if you want to enable them + * + * @config + * @var array + */ + private static $allowed_directives = [ + 'public', + 'private', + 'no-cache', + 'max-age', + 's-maxage', + 'must-revalidate', + 'proxy-revalidate', + 'no-store', + 'no-transform', + ]; /** * Set current state. Should only be invoked internally after processing precedence rules. @@ -150,7 +150,7 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable * @param string $state * @return $this */ - protected function setState($state) + protected function setState($state) { if (!array_key_exists($state, $this->stateDirectives)) { throw new InvalidArgumentException("Invalid state {$state}"); @@ -169,74 +169,74 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable return $this->state; } - /** - * Instruct the cache to apply a change with a given level, optionally - * 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 - * threshold is incremented. - * - * @param int $level Priority of the given change - * @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 - * 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 - * level threshold is updated (if necessary) to the new minimum level. - */ - protected function applyChangeLevel($level, $force) - { - $forcingLevel = $level + ($force ? self::LEVEL_FORCED : 0); - if ($forcingLevel < $this->forcingLevel) { - return false; - } - $this->forcingLevel = $forcingLevel; - return true; - } + /** + * Instruct the cache to apply a change with a given level, optionally + * 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 + * threshold is incremented. + * + * @param int $level Priority of the given change + * @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 + * 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 + * level threshold is updated (if necessary) to the new minimum level. + */ + protected function applyChangeLevel($level, $force) + { + $forcingLevel = $level + ($force ? self::LEVEL_FORCED : 0); + if ($forcingLevel < $this->forcingLevel) { + return false; + } + $this->forcingLevel = $forcingLevel; + 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. * Can also remove directives with false - * + * * @param array|string $states State(s) to apply this directive to - * @param string $directive - * @param int|string|bool $value Flag to set for this value. Set to false to remove, or true to set. + * @param string $directive + * @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. - * @return $this - */ - public function setStateDirective($states, $directive, $value = true) - { - if ($value === null) { - throw new InvalidArgumentException("Invalid directive value"); + * @return $this + */ + public function setStateDirective($states, $directive, $value = true) + { + if ($value === null) { + throw new InvalidArgumentException("Invalid directive value"); } - // make sure the directive is in the list of allowed directives - $allowedDirectives = $this->config()->get('allowed_directives'); - $directive = strtolower($directive); - if (!in_array($directive, $allowedDirectives)) { + // make sure the directive is in the list of allowed directives + $allowedDirectives = $this->config()->get('allowed_directives'); + $directive = strtolower($directive); + if (!in_array($directive, $allowedDirectives)) { throw new InvalidArgumentException('Directive ' . $directive . ' is not allowed'); } 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}"); } // Set or unset directive if ($value === false) { - unset($this->stateDirectives[$state][$directive]); + unset($this->stateDirectives[$state][$directive]); } else { $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 $directives - * @return $this - */ - public function setStateDirectivesFromArray($states, $directives) + * @param array $directives + * @return $this + */ + public function setStateDirectivesFromArray($states, $directives) { foreach ($directives as $directive => $value) { $this->setStateDirective($states, $directive, $value); @@ -244,31 +244,31 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable 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 string $directive - * @return $this - */ - public function removeStateDirective($states, $directive) + * @param string $directive + * @return $this + */ + public function removeStateDirective($states, $directive) { $this->setStateDirective($states, $directive, false); 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 $directive - * @return bool - */ - public function hasStateDirective($state, $directive) - { + * @param string $directive + * @return bool + */ + public function hasStateDirective($state, $directive) + { $directive = strtolower($directive); return isset($this->stateDirectives[$state][$directive]); - } + } /** * Check if the current state has the given directive. @@ -276,28 +276,28 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable * @param string $directive * @return bool */ - public function hasDirective($directive) + public function hasDirective($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. * True means the flag is set, otherwise the value of the directive. - * + * * @param string $state - * @param string $directive - * @return int|string|bool - */ - public function getStateDirective($state, $directive) - { - $directive = strtolower($directive); + * @param string $directive + * @return int|string|bool + */ + public function getStateDirective($state, $directive) + { + $directive = strtolower($directive); if (isset($this->stateDirectives[$state][$directive])) { return $this->stateDirectives[$state][$directive]; } return false; - } + } /** * Get the value of the given directive for the current state @@ -307,7 +307,7 @@ class HTTPCacheControlMiddleware implements HTTPMiddleware, Resettable */ 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()); } - /** - * 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. - * Set the no-store directive (also removes max-age and s-maxage for consistency purposes) - * - * @param bool $noStore - * - * @return $this - */ - public function setNoStore($noStore = true) - { - // Affect all non-disabled states + /** + * 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. + * Set the no-store directive (also removes max-age and s-maxage for consistency purposes) + * + * @param bool $noStore + * + * @return $this + */ + public function setNoStore($noStore = true) + { + // Affect all non-disabled states $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; if ($noStore) { - $this->setStateDirective($applyTo, 'no-store'); - $this->removeStateDirective($applyTo, 'max-age'); - $this->removeStateDirective($applyTo, 's-maxage'); - } else { - $this->removeStateDirective($applyTo, 'no-store'); - } - return $this; - } + $this->setStateDirective($applyTo, 'no-store'); + $this->removeStateDirective($applyTo, 'max-age'); + $this->removeStateDirective($applyTo, 's-maxage'); + } else { + $this->removeStateDirective($applyTo, 'no-store'); + } + 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. - * - * @param bool $noCache - * @return $this - */ - public function setNoCache($noCache = true) - { - // Affect all non-disabled states - $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; - $this->setStateDirective($applyTo, 'no-cache', $noCache); - return $this; - } + * + * @param bool $noCache + * @return $this + */ + public function setNoCache($noCache = true) + { + // Affect all non-disabled states + $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; + $this->setStateDirective($applyTo, 'no-cache', $noCache); + return $this; + } - /** - * Specifies the maximum amount of time (seconds) a resource will be considered fresh. - * This directive is relative to the time of the request. + /** + * Specifies the maximum amount of time (seconds) a resource will be considered fresh. + * This directive is relative to the time of the request. * 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 - * @return $this - */ - public function setSharedMaxAge($age) - { - // Affect all non-disabled states - $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; - $this->setStateDirective($applyTo, 's-maxage', $age); - return $this; - } + * @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; + } - /** - * 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. + /** + * 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 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; - } + * @param int $age + * @return $this + */ + public function setSharedMaxAge($age) + { + // Affect all non-disabled states + $applyTo = [self::STATE_ENABLED, self::STATE_PRIVATE, self::STATE_PUBLIC]; + $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. - * - * 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://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 - * @return $this - */ - public function enableCache($force = false) - { - // Only execute this if its forcing level is high enough - if ($this->applyChangeLevel(self::LEVEL_ENABLED, $force)) { + * + * 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://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 + * @return $this + */ + public function enableCache($force = false) + { + // Only execute this if its forcing level is high enough + if ($this->applyChangeLevel(self::LEVEL_ENABLED, $force)) { $this->setState(self::STATE_ENABLED); } return $this; - } + } - /** + /** * 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. * * 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. - * - * @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) + * 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) * - * 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 array( - 'Cache-Control' => $this->generateCacheHeader(), - ); - } - - /** - * Reset registered http cache control and force a fresh instance to be built - */ - public static function reset() + * 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) { - 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__); + } }