' . \__( 'This screen allows you to run site health checks and Google PageSpeed analysis.', '0-day-analytics' ) . '

'; $help_text .= '

' . \__( 'The Site Performance tab runs 32 automated checks across Security, Speed, and Migration readiness.', '0-day-analytics' ) . '

'; $help_text .= '

' . \__( 'The PageSpeed tab analyses Core Web Vitals for both mobile and desktop.', '0-day-analytics' ) . '

'; return $help_text; } /** * Handles the cache-bust redirect for site-health checks. * * Hooked to `load-{page}` so it runs before any output is sent. * Rather than only deleting the transient and redirecting, we now also * run the scanner here so that any loopback HTTP requests (and any * resulting session-token rotation) happen BEFORE admin_print_styles * generates the REST nonce via wp_localize_script. If the scanner * rotated the session mid-render the freshly generated nonce would * immediately become stale, causing 403s on every subsequent REST call. * * @return void * * @since 4.9.0 */ public static function handle_refresh_checks(): void { if ( isset( $_GET['refresh_checks'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended && \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'advan_refresh_checks' ) ) { // Delete stale cache, run fresh checks now (before any nonce is // printed to the page), store the result, then redirect cleanly. \delete_transient( self::CHECKS_TRANSIENT ); self::run_and_cache_checks(); \wp_safe_redirect( \network_admin_url( 'admin.php?page=' . self::MENU_SLUG . '&tab=checks' ) ); exit; } } /** * Ensures site-check results are cached before the page renders. * * Hooked to `load-{page}`. When the transient is missing (first visit or * after expiry) the scanner is invoked here, safely before * admin_print_styles runs and generates the REST nonce. Calling * Scanner::run_all() later — inside the page callback — would let loopback * HTTP requests rotate the session token after the nonce had already been * embedded in the page, making every subsequent browser REST/AJAX call fail * with a 403. * * @return void * * @since 4.9.0 */ public static function ensure_checks_cached(): void { // Only relevant on the checks tab; skip all other tabs. $tab = isset( $_GET['tab'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended ? \sanitize_key( $_GET['tab'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended : 'checks'; if ( 'checks' !== $tab && '' !== $tab ) { return; } if ( false === \get_transient( self::CHECKS_TRANSIENT ) ) { self::run_and_cache_checks(); } } /** * Runs Scanner::run_all(), calculates scores, and stores results in the * transient. Extracted so both handle_refresh_checks() and * ensure_checks_cached() share a single implementation. * * @return void * * @since 4.9.0 */ private static function run_and_cache_checks(): void { $results = Scanner::run_all(); $scores = Score_Calculator::calculate( $results ); $percentages = array( 'pass' => 0, 'warning' => 0, 'info' => 0, 'fail' => 0, ); if ( ! empty( $scores['counts']['total'] ) ) { $total = (int) $scores['counts']['total']; foreach ( array( 'pass', 'warning', 'info', 'fail' ) as $key ) { $percentages[ $key ] = (int) round( ( $scores['counts'][ $key ] / $total ) * 100 ); } } \set_transient( self::CHECKS_TRANSIENT, array( 'results' => $results, 'scores' => $scores, 'percentages' => $percentages, 'cached_at' => time(), ), DAY_IN_SECONDS ); } /** * Displays the performance page. * * @return void * * @since 4.9.0 */ public static function analytics_performance_page(): void { ?>

View documentation


'; echo \esc_html__( 'Site health data could not be loaded. Please reload the page to try again.', '0-day-analytics' ); echo '

'; return; } $results = $cached['results']; $scores = $cached['scores']; $percentages = $cached['percentages']; $cached_at = (int) $cached['cached_at']; // Human-readable "last checked" string. $seconds_ago = max( 0, time() - $cached_at ); if ( $seconds_ago < 60 ) { $last_checked = \__( 'just now', '0-day-analytics' ); } elseif ( $seconds_ago < 3600 ) { $mins = (int) floor( $seconds_ago / 60 ); /* translators: %d: minutes ago */ $last_checked = sprintf( \_n( '%d minute ago', '%d minutes ago', $mins, '0-day-analytics' ), $mins ); } else { /* translators: %s: formatted date/time */ $last_checked = sprintf( \__( 'on %s', '0-day-analytics' ), \wp_date( \get_option( 'date_format' ) . ' ' . \get_option( 'time_format' ), $cached_at ) ); } ?>
/100

:

/100

'rgb(12, 206, 107)', 'warning' => 'rgb(255, 205, 86)', 'info' => 'rgb(55, 136, 235)', 'fail' => 'rgb(255, 99, 132)', ); foreach ( $results[ $category ] as $result ) { $colour = isset( $icon_colours[ $result['status'] ] ) ? $icon_colours[ $result['status'] ] : ''; ?>
>
array( 'href' => array(), 'target' => array(), 'rel' => array() ) ) ); ?>
= 80 ) { return 'good'; } if ( $score >= 50 ) { return 'ok'; } return 'bad'; } /** * Returns a Unicode status icon character. * * @param string $status - 'pass' | 'info' | 'warning' | 'fail'. * * @return string * * @since 4.9.0 */ private static function status_icon( string $status ): string { switch ( $status ) { case 'pass': return '✓'; case 'info': return 'ℹ'; case 'warning': return '⚠'; case 'fail': return '✗'; default: return '?'; } } /** * Adds the performance module to the main plugin menu. * * @return void * * @since 4.9.0 */ public static function menu_add(): void { $performance_hook = \add_submenu_page( Logs_List::MENU_SLUG, ADVAN_INNER_NAME, \esc_html__( 'Performance', '0-day-analytics' ), 'manage_options', self::MENU_SLUG, array( self::class, 'analytics_performance_page' ), 9 ); Urls_List::add_screen_options( $performance_hook ); \add_action( 'load-' . $performance_hook, array( Settings::class, 'aadvana_common_help' ) ); \add_action( 'load-' . $performance_hook, array( Urls_List::class, 'process_actions_load' ) ); \add_action( 'load-' . $performance_hook, array( self::class, 'handle_refresh_checks' ) ); \add_action( 'load-' . $performance_hook, array( self::class, 'ensure_checks_cached' ) ); } } }