Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
463486e
First commit, adding amp tests, start refactoring
manuelRod Mar 18, 2022
f0c7beb
some refactoring + coding standards
manuelRod Mar 22, 2022
19609d4
refactoring in helper file + wp standards
manuelRod Mar 22, 2022
6f8ca76
Adding first unit tests
manuelRod Mar 23, 2022
c295ace
Merge branch 'release/1.0.0-beta.3' into add/sitehealth-test-full-pag…
manuelRod Mar 23, 2022
0a78791
Adding tests
manuelRod Mar 24, 2022
b9c704f
test refactor and bugfix
manuelRod Mar 24, 2022
f6098e7
ups
manuelRod Mar 24, 2022
fea0974
fixing test
manuelRod Mar 25, 2022
ce59323
Fixing array header scenario per AMP approach https://github.com/ampp…
manuelRod Mar 30, 2022
b548bfb
fixing multisite test
manuelRod Mar 30, 2022
d1fb05a
adding aruba cache
manuelRod Apr 12, 2022
30a35b6
removing escaping html, its exported to json and will cause problems
manuelRod Apr 12, 2022
bce19f0
little refactor + nitpicks
manuelRod Apr 19, 2022
6ef0b44
lint
manuelRod Apr 19, 2022
3f8f505
Merge branch 'trunk' into add/sitehealth-test-full-page-cache
manuelRod May 4, 2022
7a239fa
Adding CODEOWNERS
manuelRod May 4, 2022
dfad675
replacing @since to n.e.x.t
manuelRod May 4, 2022
2e6d1d9
moving things around
manuelRod May 4, 2022
fded890
updating loopback error test
manuelRod May 5, 2022
a8c3f7f
Adding @westonruter as code owner
manuelRod May 16, 2022
e137e28
Update modules/site-health/audit-full-page-cache/load.php
manuelRod May 30, 2022
6f5eb4a
Update modules/site-health/audit-full-page-cache/load.php
manuelRod May 30, 2022
c1dbc3b
Update modules/site-health/audit-full-page-cache/load.php
manuelRod May 30, 2022
e130163
Update modules/site-health/audit-full-page-cache/load.php
manuelRod May 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@
/modules/object-cache/persistent-object-cache-health-check @tillkruss @spacedmonkey
/tests/modules/object-cache/persistent-object-cache-health-check @tillkruss @spacedmonkey
/tests/testdata/modules/object-cache/persistent-object-cache-health-check @tillkruss @spacedmonkey

# Module: Audit Enqueued Assets
/modules/site-health/audit-full-page-cache @manuelRod @westonruter
/tests/modules/site-health/audit-full-page-cache @manuelRod @westonruter
209 changes: 209 additions & 0 deletions modules/site-health/audit-full-page-cache/helper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<?php
/**
* Helper functions used by module.
*
* @package performance-lab
* @since n.e.x.t
*/

/**
* 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.
*
* @since n.e.x.t
*
* @return array List of client caching headers and their (optional) verification callbacks.
*/
function perflab_afpc_get_page_cache_headers() {

$cache_hit_callback = static function ( $header_value ) {
return false !== strpos( strtolower( $header_value ), 'hit' );
};

return array(
'cache-control' => static function ( $header_value ) {
return (bool) preg_match( '/max-age=[1-9]/', $header_value );
},
'expires' => static function ( $header_value ) {
return strtotime( $header_value ) > time();
},
'age' => static function ( $header_value ) {
return is_numeric( $header_value ) && $header_value > 0;
},
'last-modified' => '',
'etag' => '',
'x-cache' => $cache_hit_callback,
'x-proxy-cache' => $cache_hit_callback,
'cf-cache-status' => $cache_hit_callback,
'x-kinsta-cache' => $cache_hit_callback,
'x-aruba-cache' => $cache_hit_callback,
'x-cache-enabled' => static function ( $header_value ) {
return 'true' === strtolower( $header_value );
},
'x-cache-disabled' => static function ( $header_value ) {
return ( 'on' !== strtolower( $header_value ) );
},
'cf-apo-via' => static function ( $header_value ) {
return false !== strpos( strtolower( $header_value ), 'tcache' );
},
'x-srcache-store-status' => $cache_hit_callback,
'x-srcache-fetch-status' => $cache_hit_callback,
'cf-edge-cache' => static function ( $header_value ) {
return false !== strpos( strtolower( $header_value ), 'cache' );
},
);
}

/**
* Checks if site has page cache enabled or not.
*
* @since n.e.x.t
*
* @return WP_Error|array {
* Page caching detection details or else error information.
*
* @type bool $advanced_cache_present Whether a page caching plugin is present.
* @type array[] $page_caching_response_headers Sets of client caching headers for the responses.
* @type float[] $response_timing Response timings.
* }
*/
function perflab_afpc_check_for_page_caching() {

/** This filter is documented in wp-includes/class-wp-http-streams.php */
$sslverify = apply_filters( 'https_local_ssl_verify', false );

$headers = array();

// Include basic auth in loopback requests. Note that this will only pass along basic auth when user is
// initiating the test. If a site requires basic auth, the test will fail when it runs in WP Cron as part of
// wp_site_health_scheduled_check. This logic is copied from WP_Site_Health::can_perform_loopback() in core.
if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { // phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.BasicAuthentication
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPressVIPMinimum.Variables.ServerVariables.BasicAuthentication
$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
}

$caching_headers = perflab_afpc_get_page_cache_headers();
$page_caching_response_headers = array();
$response_timing = array();

for ( $i = 1; $i <= 3; $i++ ) {
$start_time = microtime( true );
$http_response = wp_remote_get( home_url( '/' ), compact( 'sslverify', 'headers' ) );
$end_time = microtime( true );

if ( is_wp_error( $http_response ) ) {
return $http_response;
}
if ( wp_remote_retrieve_response_code( $http_response ) !== 200 ) {
return new WP_Error(
'http_' . wp_remote_retrieve_response_code( $http_response ),
wp_remote_retrieve_response_message( $http_response )
);
}

$response_headers = array();

foreach ( $caching_headers as $header => $callback ) {
$header_values = wp_remote_retrieve_header( $http_response, $header );
if ( empty( $header_values ) ) {
continue;
}
$header_values = (array) $header_values;
if (
empty( $callback )
||
(
is_callable( $callback )
&&
count( array_filter( $header_values, $callback ) ) > 0
)
) {
$response_headers[ $header ] = $header_values;
}
}

$page_caching_response_headers[] = $response_headers;
$response_timing[] = ( $end_time - $start_time ) * 1000;
}

return array(
'advanced_cache_present' => (
file_exists( WP_CONTENT_DIR . '/advanced-cache.php' )
&&
( defined( 'WP_CACHE' ) && WP_CACHE )
&&
/** This filter is documented in wp-settings.php */
apply_filters( 'enable_loading_advanced_cache_dropin', true )
),
'page_caching_response_headers' => $page_caching_response_headers,
'response_timing' => $response_timing,
);
}

/**
* Get page cache details.
*
* @since n.e.x.t
*
* @return WP_Error|array {
* Page cache detail or else a WP_Error if unable to determine.
*
* @type string $status Page cache status. Good, Recommended or Critical.
* @type bool $advanced_cache_present Whether page cache plugin is available or not.
* @type string[] $headers Client caching response headers detected.
* @type float $response_time Response time of site.
* }
*/
function perflab_afpc_get_page_cache_detail() {
$page_cache_detail = perflab_afpc_check_for_page_caching();
if ( is_wp_error( $page_cache_detail ) ) {
return $page_cache_detail;
}

// Use the median server response time.
$response_timings = $page_cache_detail['response_timing'];
rsort( $response_timings );
$page_speed = $response_timings[ floor( count( $response_timings ) / 2 ) ];

// Obtain unique set of all client caching response headers.
$headers = array();
foreach ( $page_cache_detail['page_caching_response_headers'] as $page_caching_response_headers ) {
$headers = array_merge( $headers, array_keys( $page_caching_response_headers ) );
}
$headers = array_unique( $headers );

// Page caching is detected if there are response headers or a page caching plugin is present.
$has_page_caching = ( count( $headers ) > 0 || $page_cache_detail['advanced_cache_present'] );

if ( $page_speed && $page_speed < perflab_afpc_get_good_response_time_threshold() ) {
$result = $has_page_caching ? 'good' : 'recommended';
} else {
$result = 'critical';
}

return array(
'status' => $result,
'advanced_cache_present' => $page_cache_detail['advanced_cache_present'],
'headers' => $headers,
'response_time' => $page_speed,
);
}

/**
* Get the threshold below which a response time is considered good.
*
* @since n.e.x.t
*
* @return int Threshold in milliseconds.
*/
function perflab_afpc_get_good_response_time_threshold() {
/**
* Filters the threshold below which a response time is considered good.
*
* @since n.e.x.t
* @param int $threshold Threshold in milliseconds. Default 600.
*/
return (int) apply_filters( 'perflab_afpc_page_cache_good_response_time_threshold', 600 );
}
174 changes: 174 additions & 0 deletions modules/site-health/audit-full-page-cache/load.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php
/**
* Module Name: Audit Full Page Cache
* Description: Adds a check for full page cache in Site Health status.
* Experimental: No
*
* @package performance-lab
* @since n.e.x.t
*/

/**
* Require helper functions.
*/
require_once __DIR__ . '/helper.php';

/**
* Adds tests to site health.
*
* @since n.e.x.t
*
* @param array $tests Site Health Tests.
* @return array Modified Site Health tests including Page caching test.
*/
function perflab_afpc_add_full_page_cache_test( $tests ) {
$tests['async']['perflab_page_cache'] = array(
'label' => __( 'Page caching', 'performance-lab' ),
'test' => rest_url( 'performance-lab/v1/tests/page-cache' ),
'has_rest' => true,
'async_direct_test' => 'perflab_afpc_page_cache_test',
);
return $tests;
}
add_filter( 'site_status_tests', 'perflab_afpc_add_full_page_cache_test' );

/**
* Callback for perflab_page_cache test.
*
* @since n.e.x.t
*
* @return array The test results.
*/
function perflab_afpc_page_cache_test() {
$description = '<p>' . __( 'Page caching enhances the speed and performance of your site by saving and serving static pages instead of calling for a page every time a user visits.', 'performance-lab' ) . '</p>';
$description .= '<p>' . __( 'Page caching is detected by looking for an active page caching plugin as well as making three requests to the homepage and looking for one or more of the following HTTP client caching response headers:', 'performance-lab' ) . '</p>';
$description .= '<code>' . implode( '</code>, <code>', array_keys( perflab_afpc_get_page_cache_headers() ) ) . '.</code>';

$result = array(
'badge' => array(
'label' => __( 'Performance', 'performance-lab' ),
'color' => 'green',
),
'description' => wp_kses_post( $description ),
'test' => 'perflab_page_cache',
'status' => 'good',
'label' => '',
'actions' => sprintf(
'<p><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
esc_url( 'https://wordpress.org/support/article/optimization/#Caching' ),
__( 'Learn more about page caching', 'performance-lab' ),
/* translators: The accessibility text. */
__( '(opens in a new tab)', 'performance-lab' )
),
);

$page_cache_detail = perflab_afpc_get_page_cache_detail();

if ( is_wp_error( $page_cache_detail ) ) {
$result['badge']['color'] = 'orange';
$result['label'] = __( 'Unable to detect the presence of page caching', 'performance-lab' );
$result['status'] = 'recommended';
$error_info = sprintf(
/* translators: 1 is error message, 2 is error code */
__( 'Unable to detect page caching due to possible loopback request problem. Please verify that the loopback request test is passing. Error: %1$s (Code: %2$s)', 'performance-lab' ),
$page_cache_detail->get_error_message(),
$page_cache_detail->get_error_code()
);
$result['description'] = wp_kses_post( "<p>$error_info</p>" ) . $result['description'];
return $result;
}

$result['status'] = $page_cache_detail['status'];

switch ( $page_cache_detail['status'] ) {
case 'recommended':
$result['badge']['color'] = 'orange';
$result['label'] = __( 'Page caching is not detected but the server response time is OK', 'performance-lab' );
break;
case 'good':
$result['badge']['color'] = 'green';
$result['label'] = __( 'Page caching is detected and the server response time is good', 'performance-lab' );
break;
default:
$result['badge']['color'] = 'red';
if ( empty( $page_cache_detail['headers'] ) && ! $page_cache_detail['advanced_cache_present'] ) {
$result['label'] = __( 'Page caching is not detected and the server response time is slow', 'performance-lab' );
} else {
$result['label'] = __( 'Page caching is detected but the server response time is still slow', 'performance-lab' );
}
}

$page_cache_test_summary = array();

if ( empty( $page_cache_detail['response_time'] ) ) {
$page_cache_test_summary[] = '<span class="dashicons dashicons-dismiss"></span> ' . __( 'Server response time could not be determined. Verify that loopback requests are working.', 'performance-lab' );
} else {

$threshold = perflab_afpc_get_good_response_time_threshold();
if ( $page_cache_detail['response_time'] < $threshold ) {
$page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt"></span> ' . sprintf(
/* translators: %d is the response time in milliseconds */
__( 'Median server response time was %1$s milliseconds. This is less than the recommended %2$s millisecond threshold.', 'performance-lab' ),
number_format_i18n( $page_cache_detail['response_time'] ),
number_format_i18n( $threshold )
);
} else {
$page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . sprintf(
/* translators: %d is the response time in milliseconds */
__( 'Median server response time was %1$s milliseconds. It should be less than the recommended %2$s milliseconds threshold.', 'performance-lab' ),
number_format_i18n( $page_cache_detail['response_time'] ),
number_format_i18n( $threshold )
);
}

if ( empty( $page_cache_detail['headers'] ) ) {
$page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . __( 'No client caching response headers were detected.', 'performance-lab' );
} else {
$headers_summary = '<span class="dashicons dashicons-yes-alt"></span>';
$headers_summary .= sprintf(
/* translators: Placeholder is number of caching headers */
_n(
' There was %d client caching response header detected: ',
' There were %d client caching response headers detected: ',
count( $page_cache_detail['headers'] ),
'performance-lab'
),
count( $page_cache_detail['headers'] )
);
$headers_summary .= '<code>' . implode( '</code>, <code>', $page_cache_detail['headers'] ) . '</code>.';
$page_cache_test_summary[] = $headers_summary;
}
}

if ( $page_cache_detail['advanced_cache_present'] ) {
$page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt"></span> ' . __( 'A page caching plugin was detected.', 'performance-lab' );
} elseif ( ! ( is_array( $page_cache_detail ) && ! empty( $page_cache_detail['headers'] ) ) ) {
// Note: This message is not shown if client caching response headers were present since an external caching layer may be employed.
$page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . __( 'A page caching plugin was not detected.', 'performance-lab' );
}

$result['description'] .= '<ul><li>' . implode( '</li><li>', $page_cache_test_summary ) . '</li></ul>';
return $result;
}

/**
* Register async test REST endpoint.
*
* @since n.e.x.t
*/
function perflab_afpc_register_async_test_endpoints() {
register_rest_route(
'performance-lab/v1',
'/tests/page-cache',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'perflab_afpc_page_cache_test',
'permission_callback' => static function() {
return current_user_can( 'view_site_health_checks' );
},
),
)
);
}
add_action( 'rest_api_init', 'perflab_afpc_register_async_test_endpoints' );
Loading