oont-contents/plugins/jetpack-boost/app/modules/optimizations/page-cache/pre-wordpress/class-request.php
2025-04-06 08:34:48 +02:00

348 lines
10 KiB
PHP

<?php
/*
* This file may be called before WordPress is fully initialized. See the README file for info.
*/
namespace Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress;
class Request {
/**
* @var Request - The request instance for current request.
*/
private static $current_request = null;
/**
* @var string - The normalized path for the current request. This is not sanitized. Only to be used for comparison purposes.
*/
private $request_uri = false;
/**
* @var array - The GET parameters and cookies for the current request. Everything considered in the cache key.
*/
private $request_parameters;
/**
* Gets the singleton request instance.
*
* @return Request The instance of the class.
*/
public static function current() {
if ( self::$current_request === null ) {
self::$current_request = new self(
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
isset( $_SERVER['REQUEST_URI'] ) ? Boost_Cache_Utils::normalize_request_uri( $_SERVER['REQUEST_URI'] ) : false,
// Set the cookies and get parameters for the current request. Sometimes these arrays are modified by WordPress or other plugins.
// We need to cache them here so they can be used for the cache key later. We don't need to sanitize them, as they are only used for comparison.
array(
'cookies' => $_COOKIE, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
'get' => $_GET, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
)
);
}
return self::$current_request;
}
public function __construct( $uri, $parameters ) {
$this->request_uri = $uri;
$this->request_parameters = $parameters;
}
public function get_uri() {
return $this->request_uri;
}
/**
* Returns the parameters for the current request.
*
* @return array The parameters for the current request, made up of cookies and get parameters.
*/
public function get_parameters() {
/**
* Filters the parameters for the current request to identify the cache key.
*
* @since 3.8.0
*
* @param array $parameters The parameters for the current request, made up of cookies and get parameters.
*/
return apply_filters( 'jetpack_boost_cache_parameters', $this->request_parameters );
}
/**
* Returns true if the current request has a fatal error.
*
* @return bool
*/
private function is_fatal_error() {
$error = error_get_last();
if ( $error === null ) {
return false;
}
$fatal_errors = array(
E_ERROR,
E_PARSE,
E_CORE_ERROR,
E_COMPILE_ERROR,
E_USER_ERROR,
);
return in_array( $error['type'], $fatal_errors, true );
}
public function is_url_excluded( $request_uri = '' ) {
if ( $request_uri === '' ) {
$request_uri = $this->request_uri;
}
// Check if the query parameters `jb-disable-modules` or `jb-generate-critical-css` exist.
$request_parameters = $this->get_parameters();
$query_params = isset( $request_parameters['get'] ) ? $request_parameters['get'] : array();
if ( isset( $query_params ) &&
( isset( $query_params['jb-disable-modules'] ) || isset( $query_params['jb-generate-critical-css'] ) )
) {
return true;
}
$bypass_patterns = Boost_Cache_Settings::get_instance()->get_bypass_patterns();
/**
* Filters the bypass patterns for the page cache.
* If you need to sanitize them, do it before passing them to this filter,
* as there's no sanitization done after this filter.
*
* @since 3.2.0
*
* @param array $bypass_patterns An array of regex patterns that define URLs that bypass caching.
*/
$bypass_patterns = apply_filters( 'jetpack_boost_cache_bypass_patterns', $bypass_patterns );
$bypass_patterns[] = 'wp-.*\.php';
foreach ( $bypass_patterns as $expr ) {
if ( ! empty( $expr ) && preg_match( "~^$expr/?$~", $request_uri ) ) {
return true;
}
}
return false;
}
/**
* Returns true if the request is cacheable.
*
* If a request is in the backend, or is a POST request, or is not an
* html request, it is not cacheable.
* The filter boost_cache_cacheable can be used to override this.
*
* @return bool
*/
public function is_cacheable() {
/**
* Determines if the request is considered cacheable.
*
* Can be used to prevent a request from being cached.
*
* @since 3.2.0
*
* @param bool $default_status The default cacheability status (true for cacheable).
* @param string $request_uri The request URI to be evaluated for cacheability.
*/
if ( ! apply_filters( 'jetpack_boost_cache_request_cacheable', true, $this->request_uri ) ) {
return false;
}
if ( defined( 'DONOTCACHEPAGE' ) ) {
return false;
}
// do not cache post previews or customizer previews
if ( ! empty( $_GET ) && ( isset( $_GET['preview'] ) || isset( $_GET['customize_changeset_uuid'] ) ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
return false;
}
if ( $this->is_fatal_error() ) {
return false;
}
if ( function_exists( 'is_user_logged_in' ) && is_user_logged_in() ) {
return false;
}
if ( $this->is_404() ) {
return false;
}
if ( $this->is_feed() ) {
return false;
}
if ( $this->is_backend() ) {
return false;
}
if ( $this->is_bypassed_extension() ) {
return false;
}
if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] !== 'GET' ) {
return false;
}
if ( $this->is_url_excluded() ) {
Logger::debug( 'Url excluded, not cached!' );
return false;
}
if ( $this->is_module_disabled() ) {
return false;
}
/**
* Filters the accept headers to determine if the request should be cached.
*
* This filter allows modification of the content types that browsers send
* to the server during a request. If the acceptable browser content type header (HTTP_ACCEPT)
* matches one of these content types the request will not be cached,
* or a cached file served to this visitor.
*
* @since 3.2.0
*
* @param array $accept_headers An array of header values that should prevent a request from being cached.
*/
$accept_headers = apply_filters( 'jetpack_boost_cache_accept_headers', array( 'application/json', 'application/activity+json', 'application/ld+json' ) );
$accept_headers = array_map( 'strtolower', $accept_headers );
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- $accept is checked and set below.
$accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? strtolower( filter_var( $_SERVER['HTTP_ACCEPT'] ) ) : '';
if ( $accept !== '' ) {
foreach ( $accept_headers as $header ) {
if ( str_contains( $accept, $header ) ) {
return false;
}
}
}
return true;
}
/**
* Returns true if the request appears to be for something with a known file extension that is not
* usually HTML. e.g.:
* - *.txt (including robots.txt, license.txt)
* - *.ico (favicon.ico)
* - *.jpg, *.png, *.webm (image files).
*/
public function is_bypassed_extension() {
$file_extension = pathinfo( $this->request_uri, PATHINFO_EXTENSION );
return in_array(
$file_extension,
array(
'txt',
'ico',
'jpg',
'jpeg',
'png',
'webp',
'gif',
),
true
);
}
/**
* Returns true if the current request is one of the following:
* 1. wp-admin
* 2. wp-login.php, xmlrpc.php or wp-cron.php/cron request
* 3. WP_CLI
* 4. REST request.
*
* @return bool
*/
public function is_backend() {
$is_backend = is_admin();
if ( $is_backend ) {
return $is_backend;
}
$script = isset( $_SERVER['PHP_SELF'] ) ? basename( $_SERVER['PHP_SELF'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( $script !== 'index.php' ) {
if ( in_array( $script, array( 'wp-login.php', 'xmlrpc.php', 'wp-cron.php' ), true ) ) {
$is_backend = true;
}
}
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
$is_backend = true;
}
if ( PHP_SAPI === 'cli' || ( defined( 'WP_CLI' ) && constant( 'WP_CLI' ) ) ) {
$is_backend = true;
}
if ( defined( 'REST_REQUEST' ) ) {
$is_backend = true;
}
return $is_backend;
}
/**
* "Safe" version of WordPress' is_404 method. When called before WordPress' query is run, returns
* `null` (a falsey value) instead of outputting a _doing_it_wrong warning.
*/
public function is_404() {
global $wp_query;
if ( ! isset( $wp_query ) || ! function_exists( '\is_404' ) ) {
return null;
}
return \is_404();
}
/**
* "Safe" version of WordPress' is_feed method. When called before WordPress' query is run, returns
* `null` (a falsey value) instead of outputting a _doing_it_wrong warning.
*/
public function is_feed() {
global $wp_query;
if ( ! isset( $wp_query ) || ! function_exists( '\is_feed' ) ) {
return null;
}
return \is_feed();
}
/**
* Return true if the Page Cache module is disabled, or null if we don't know yet.
*
* If Status and Page_Cache are not available, it means the plugin is not loaded.
* This function will be called later when writing a cache file to disk.
* It's then that we can check if the module is active.
*
* @return null|bool
*/
public function is_module_disabled() {
// A simple check to make sure we're in the output buffer callback.
if ( ! function_exists( '\is_feed' ) ) {
return null;
}
if (
class_exists( '\Automattic\Jetpack_Boost\Lib\Status' ) &&
class_exists( '\Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Page_Cache' )
) {
$page_cache_status = new \Automattic\Jetpack_Boost\Lib\Status(
\Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Page_Cache::get_slug()
);
return ! $page_cache_status->get();
} else {
return true; // if the classes aren't available, the plugin isn't loaded.
}
}
}