Aiso_Store::insert_hit) can * fire. That guarantees one row per request and identical verified/"—"/spoof states. * * @package Aiso_AI_Crawler_Analytics */ if ( ! defined( 'ABSPATH' ) ) { exit; } class Aiso_Cache { public function __construct() { // WP Rocket: append our UA regexes to its cache-reject list. Rocket consults // this from its advanced-cache drop-in, so matching bots bypass the page cache // on both read (HIT) and write. Harmless when WP Rocket is not installed. add_filter( 'rocket_cache_reject_ua', array( $this, 'rocket_reject_ua' ) ); // W3TC / WP Super Cache / LiteSpeed: emit a per-request "do not cache" signal // when the current request is a known AI bot. Hooked at init:0 — after all // plugins have loaded (so the cache plugin's listeners exist) but well before // the response is buffered and stored. add_action( 'init', array( $this, 'signal_nocache' ), 0 ); // Keep WP Rocket's baked config in sync whenever our options change, so a new // UA (or toggling the setting) takes effect without a manual cache clear. add_action( 'update_option_' . Aiso_Settings::OPTION, array( __CLASS__, 'refresh_cache_rules' ) ); } /** * Append AI-crawler UA patterns to WP Rocket's reject-UA list. * * @param array $uas Existing reject-UA fragments. * @return array */ public function rocket_reject_ua( $uas ) { if ( ! $this->bypass_enabled() ) { return is_array( $uas ) ? $uas : array(); } $uas = is_array( $uas ) ? $uas : array(); return array_merge( $uas, Aiso_Bots::ua_patterns() ); } /** * For cache plugins without a UA-reject hook, signal "don't cache this request" * when the current visitor is a known AI crawler. */ public function signal_nocache() { if ( ! $this->should_bypass() ) { return; } // W3 Total Cache, WP Super Cache, and many other layers honour this constant // to skip storing the current response. if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } // LiteSpeed Cache: explicit no-cache control for the current request. do_action( 'litespeed_control_set_nocache', 'aiso ai bot' ); } /** * Regenerate WP Rocket's baked config so the reject-UA list is live in its * advanced-cache drop-in. No-op when WP Rocket is absent. Called on settings * save and on plugin activation. */ public static function refresh_cache_rules() { if ( function_exists( 'rocket_generate_config_file' ) ) { rocket_generate_config_file(); } } /** * Is the cache-bypass feature enabled? (Default on.) * * @return bool */ private function bypass_enabled() { return (bool) Aiso_Analytics::option( 'bypass_cache_for_ai_bots', true ); } /** * Should the current request bypass the page cache? Mirrors Aiso_Tracker's * should_track() exclusions (admin, cron, ajax, REST, XML-RPC, CLI; GET/HEAD * only) and additionally requires a known AI user-agent, so caching is never * disabled for human traffic. * * @return bool */ private function should_bypass() { if ( ! $this->bypass_enabled() ) { return false; } if ( is_admin() ) { return false; } if ( function_exists( 'wp_doing_cron' ) && wp_doing_cron() ) { return false; } if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) { return false; } if ( defined( 'WP_CLI' ) && WP_CLI ) { return false; } if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { return false; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return false; } $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET'; if ( ! in_array( $method, array( 'GET', 'HEAD' ), true ) ) { return false; } $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; if ( '' === $user_agent ) { return false; } return Aiso_Bots::is_ai_user_agent( $user_agent ); } }