diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index dd537296a8655..57fb92a67a5a5 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -3378,22 +3378,20 @@ public function is_development_environment() { } /** - * Returns a list of headers and its verification callback to verify if page cache is enabled or not. - * - * Note: key is header name and value could be callable function to verify header value. - * Empty value mean existence of header detect page cache is enabled. + * Returns a mapping to response headers to an optional callback to verify if page cache is enabled or not. * * @since 6.1.0 * - * @return array List of client caching headers and their (optional) verification callbacks. + * @return array Mapping of page caching headers and their (optional) verification callbacks. */ - public function get_page_cache_headers() { + public function get_page_cache_headers(): array { $cache_hit_callback = static function ( $header_value ) { - return str_contains( strtolower( $header_value ), 'hit' ); + return (bool) preg_match( '/\bhit\b/i', $header_value ); }; $cache_headers = array( + // Standard HTTP caching headers. 'cache-control' => static function ( $header_value ) { return (bool) preg_match( '/max-age=[1-9]/', $header_value ); }, @@ -3403,26 +3401,54 @@ public function get_page_cache_headers() { 'age' => static function ( $header_value ) { return is_numeric( $header_value ) && $header_value > 0; }, - 'last-modified' => '', - 'etag' => '', + 'last-modified' => null, + 'etag' => null, + + // Custom caching headers. 'x-cache-enabled' => static function ( $header_value ) { return 'true' === strtolower( $header_value ); }, 'x-cache-disabled' => static function ( $header_value ) { return ( 'on' !== strtolower( $header_value ) ); }, - 'x-srcache-store-status' => $cache_hit_callback, + + /** + * OpenResty srcache-nginx-module. + * + * The `x-srcache-store-status` header indicates if the response was stored in the cache. + * Valid values include `STORE` and `BYPASS`. + * + * The `x-srcache-fetch-status` header indicates if the response was fetched from the cache. + * Valid values include `HIT`, `MISS`, and `BYPASS`. + * + * @link https://github.com/openresty/srcache-nginx-module + */ + 'x-srcache-store-status' => static function ( $header_value ) { + return 'store' === strtolower( $header_value ); + }, 'x-srcache-fetch-status' => $cache_hit_callback, - // Generic caching proxies (Nginx, Varnish, etc.) - 'x-cache' => $cache_hit_callback, - 'x-cache-status' => $cache_hit_callback, - 'x-litespeed-cache' => $cache_hit_callback, - 'x-proxy-cache' => $cache_hit_callback, - 'via' => '', + // Generic caching proxies (Nginx, Varnish, Squid, Go, Fastly, LiteSpeed, etc.). + 'x-cache' => $cache_hit_callback, + 'x-cache-status' => $cache_hit_callback, + 'x-litespeed-cache' => $cache_hit_callback, + 'x-proxy-cache' => $cache_hit_callback, + + /** + * Varnish Cache. + * + * For a cache hit, it includes both the ID of the current request and the ID of the request + * that populated the cache. For a miss, it only includes the current request ID. + * + * @link https://vinyl-cache.org/docs/2.1/faq/http.html + */ + 'x-varnish' => static function ( $header_value ) { + return (bool) preg_match( '/\d+ \d+/', $header_value ); + }, + 'via' => null, - // Cloudflare - 'cf-cache-status' => $cache_hit_callback, + // Cloudflare. + 'cf-cache-status' => $cache_hit_callback, ); /** @@ -3430,9 +3456,9 @@ public function get_page_cache_headers() { * * @since 6.1.0 * - * @param array $cache_headers Array of supported cache headers. + * @param array $cache_headers Mapping of page caching headers and their (optional) verification callbacks. */ - return apply_filters( 'site_status_page_cache_supported_cache_headers', $cache_headers ); + return (array) apply_filters( 'site_status_page_cache_supported_cache_headers', $cache_headers ); } /** diff --git a/tests/phpunit/tests/admin/wpSiteHealth.php b/tests/phpunit/tests/admin/wpSiteHealth.php index 2d32bbb14ec4d..05761765b327c 100644 --- a/tests/phpunit/tests/admin/wpSiteHealth.php +++ b/tests/phpunit/tests/admin/wpSiteHealth.php @@ -12,10 +12,8 @@ class Tests_Admin_wpSiteHealth extends WP_UnitTestCase { * An instance of the class to test. * * @since 6.1.0 - * - * @var WP_Site_Health */ - private $instance; + private WP_Site_Health $instance; public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { // Include the `WP_Site_Health` file. @@ -172,7 +170,7 @@ public function data_cron_health_checks() { * @covers ::get_page_cache_headers() * @covers ::check_for_page_caching() */ - public function test_get_page_cache( $responses, $expected_status, $expected_label, $good_basic_auth = null, $delay_the_response = false ) { + public function test_get_page_cache( array $responses, string $expected_status, string $expected_label, bool $good_basic_auth = false, bool $delay_the_response = false ) { $expected_props = array( 'badge' => array( 'label' => __( 'Performance' ), @@ -183,7 +181,7 @@ public function test_get_page_cache( $responses, $expected_status, $expected_lab 'label' => $expected_label, ); - if ( null !== $good_basic_auth ) { + if ( $good_basic_auth ) { $_SERVER['PHP_AUTH_USER'] = 'admin'; $_SERVER['PHP_AUTH_PW'] = 'password'; } @@ -219,7 +217,7 @@ function ( $response, $parsed_args ) use ( &$responses, &$is_unauthorized, $good ); } - if ( null !== $good_basic_auth ) { + if ( $good_basic_auth ) { $this->assertArrayHasKey( 'Authorization', $parsed_args['headers'] @@ -263,9 +261,15 @@ function ( $response, $parsed_args ) use ( &$responses, &$is_unauthorized, $good * * @ticket 56041 * - * @return array[] + * @return array>, + * expected_status: 'recommended'|'critical'|'good', + * expected_label: string, + * good_basic_auth?: bool, + * delay_the_response?: bool, + * }> */ - public function data_get_page_cache() { + public function data_get_page_cache(): array { $recommended_label = 'Page cache is not detected but the server response time is OK'; $good_label = 'Page cache is detected and the server response time is good'; $critical_label = 'Page cache is not detected and the server response time is slow'; @@ -278,13 +282,13 @@ public function data_get_page_cache() { ), 'expected_status' => 'recommended', 'expected_label' => $error_label, - 'good_basic_auth' => false, + 'good_basic_auth' => true, ), 'no-cache-control' => array( 'responses' => array_fill( 0, 3, array() ), 'expected_status' => 'critical', 'expected_label' => $critical_label, - 'good_basic_auth' => null, + 'good_basic_auth' => false, 'delay_the_response' => true, ), 'no-cache' => array( @@ -310,7 +314,7 @@ public function data_get_page_cache() { 'responses' => array_fill( 0, 3, array( 'cache-control' => 'no-cache' ) ), 'expected_status' => 'critical', 'expected_label' => $critical_label, - 'good_basic_auth' => null, + 'good_basic_auth' => false, 'delay_the_response' => true, ), 'age' => array( @@ -366,7 +370,7 @@ public function data_get_page_cache() { ), 'expected_status' => 'critical', 'expected_label' => $critical_label, - 'good_basic_auth' => null, + 'good_basic_auth' => false, 'delay_the_response' => true, ), 'cache-control-with-basic-auth' => array( @@ -396,7 +400,7 @@ public function data_get_page_cache() { ), 'expected_status' => 'critical', 'expected_label' => $critical_label, - 'good_basic_auth' => null, + 'good_basic_auth' => false, 'delay_the_response' => true, ), 'x-cache-disabled' => array( @@ -408,6 +412,78 @@ public function data_get_page_cache() { 'expected_status' => 'good', 'expected_label' => $good_label, ), + 'false-positive-hit-in-word' => array( + 'responses' => array_fill( + 0, + 3, + array( 'x-cache' => 'shit' ) + ), + 'expected_status' => 'recommended', + 'expected_label' => $recommended_label, + ), + 'varnish-header' => array( + 'responses' => array_fill( + 0, + 3, + array( 'x-varnish' => '123 456' ) + ), + 'expected_status' => 'good', + 'expected_label' => $good_label, + ), + 'varnish-header-miss' => array( + 'responses' => array_fill( + 0, + 3, + array( 'x-varnish' => '123' ) + ), + 'expected_status' => 'recommended', + 'expected_label' => $recommended_label, + ), + 'srcache-store-status' => array( + 'responses' => array_fill( + 0, + 3, + array( 'x-srcache-store-status' => 'STORE' ) + ), + 'expected_status' => 'good', + 'expected_label' => $good_label, + ), + 'srcache-store-status-bypass' => array( + 'responses' => array_fill( + 0, + 3, + array( 'x-srcache-store-status' => 'BYPASS' ) + ), + 'expected_status' => 'recommended', + 'expected_label' => $recommended_label, + ), + 'srcache-fetch-status' => array( + 'responses' => array_fill( + 0, + 3, + array( 'x-srcache-fetch-status' => 'HIT' ) + ), + 'expected_status' => 'good', + 'expected_label' => $good_label, + ), + 'last-modified' => array( + 'responses' => array_fill( + 0, + 3, + array( 'last-modified' => 'Wed, 21 Oct 2015 07:28:00 GMT' ) + ), + 'expected_status' => 'good', + 'expected_label' => $good_label, + ), + 'via' => array( + 'responses' => array_fill( + 0, + 3, + array( 'via' => '1.1 varnish' ) + ), + 'expected_status' => 'good', + 'expected_label' => $good_label, + ), ); }