! in_array( strtolower( $t ), $lower, true ) ) ); } /** Undo apply(): remove exactly the user agents we added, regenerate WP Rocket, forget the state. Never throws. */ public static function restore(): array { $state = get_option( self::STATE_OPTION, array() ); $added = is_array( $state ) && is_array( $state['added'] ?? null ) ? $state['added'] : array(); $log = array(); $strip = static function ( array $list, array $remove ): array { $rm = array_map( 'strtolower', $remove ); return array_values( array_filter( $list, static fn( $v ) => ! in_array( strtolower( (string) $v ), $rm, true ) ) ); }; if ( ! empty( $added['w3tc'] ) && class_exists( '\W3TC\Dispatcher' ) ) { try { $c = \W3TC\Dispatcher::config(); $c->set( 'pgcache.reject.ua', $strip( (array) $c->get_array( 'pgcache.reject.ua' ), $added['w3tc'] ) ); $c->save(); if ( function_exists( 'w3tc_flush_all' ) ) { w3tc_flush_all(); } $log[] = 'W3TC restored'; } catch ( \Throwable $e ) { $log[] = 'W3TC restore error: ' . $e->getMessage(); } } if ( ! empty( $added['litespeed'] ) && defined( 'LSCWP_V' ) ) { try { $cur = apply_filters( 'litespeed_conf', 'cache-exc_useragents' ); $cur = is_array( $cur ) ? $cur : preg_split( '/[\r\n]+/', (string) $cur ); do_action( 'litespeed_save_conf', array( 'cache-exc_useragents' => $strip( (array) $cur, $added['litespeed'] ) ) ); $log[] = 'LiteSpeed restored'; } catch ( \Throwable $e ) { $log[] = 'LiteSpeed restore error: ' . $e->getMessage(); } } if ( ! empty( $added['wp-super-cache'] ) && function_exists( 'wp_cache_setting' ) ) { try { global $cache_rejected_user_agent; wp_cache_setting( 'cache_rejected_user_agent', $strip( (array) $cache_rejected_user_agent, $added['wp-super-cache'] ) ); $log[] = 'WP Super Cache restored'; } catch ( \Throwable $e ) { $log[] = 'WP Super Cache restore error: ' . $e->getMessage(); } } delete_option( self::STATE_OPTION ); // WP Rocket: our filter now adds nothing (not applied); regenerate its rules. if ( defined( 'WP_ROCKET_VERSION' ) ) { try { if ( function_exists( 'flush_rocket_htaccess' ) ) { flush_rocket_htaccess(); } if ( function_exists( 'rocket_generate_config_file' ) ) { rocket_generate_config_file(); } $log[] = 'WP Rocket regenerated'; } catch ( \Throwable $e ) { $log[] = 'WP Rocket restore error: ' . $e->getMessage(); } } return $log; } /** Configure every detected cache plugin. Never throws. */ public static function apply(): array { $uas = Bots::cache_bypass_tokens(); $log = array(); $results = array(); $prev = get_option( self::STATE_OPTION, array() ); $added = is_array( $prev ) && is_array( $prev['added'] ?? null ) ? $prev['added'] : array(); // Mark as applied first so the WP Rocket filter below contributes our user agents. update_option( self::STATE_OPTION, array_merge( is_array( $prev ) ? $prev : array(), array( 'at' => Util::iso() ) ), false ); // W3 Total Cache. if ( class_exists( '\W3TC\Dispatcher' ) ) { try { $c = \W3TC\Dispatcher::config(); $added['w3tc'] = array_values( array_unique( array_merge( $added['w3tc'] ?? array(), self::missing( (array) $c->get_array( 'pgcache.reject.ua' ), $uas ) ) ) ); $merged = array_values( array_unique( array_merge( (array) $c->get_array( 'pgcache.reject.ua' ), $uas ) ) ); $c->set( 'pgcache.reject.ua', $merged ); $c->save(); if ( is_admin() ) { $env = \W3TC\Dispatcher::component( 'Root_Environment' ); if ( $env && method_exists( $env, 'fix_in_wpadmin' ) ) { $env->fix_in_wpadmin( $c, true ); } } if ( function_exists( 'w3tc_flush_all' ) ) { w3tc_flush_all(); } $results['w3tc'] = self::check_w3tc(); $log[] = 'W3TC: pgcache.reject.ua updated'; } catch ( \Throwable $e ) { $results['w3tc'] = false; $log[] = 'W3TC error: ' . $e->getMessage(); } } // LiteSpeed Cache. if ( defined( 'LSCWP_V' ) ) { try { $current = apply_filters( 'litespeed_conf', 'cache-exc_useragents' ); $current = is_array( $current ) ? $current : preg_split( '/[\r\n]+/', (string) $current ); $added['litespeed'] = array_values( array_unique( array_merge( $added['litespeed'] ?? array(), self::missing( (array) $current, $uas ) ) ) ); $merged = array_values( array_filter( array_unique( array_merge( (array) $current, $uas ) ) ) ); do_action( 'litespeed_save_conf', array( 'cache-exc_useragents' => $merged ) ); $results['litespeed'] = self::check_litespeed(); $log[] = 'LiteSpeed: cache-exc_useragents updated'; } catch ( \Throwable $e ) { $results['litespeed'] = false; $log[] = 'LiteSpeed error: ' . $e->getMessage(); } } // WP Rocket: our rocket_cache_reject_ua filter is always registered; regenerate. if ( defined( 'WP_ROCKET_VERSION' ) ) { try { if ( function_exists( 'flush_rocket_htaccess' ) ) { flush_rocket_htaccess(); } if ( function_exists( 'rocket_generate_config_file' ) ) { rocket_generate_config_file(); } if ( function_exists( 'rocket_clean_domain' ) ) { rocket_clean_domain(); } $results['wp-rocket'] = function_exists( 'rocket_generate_config_file' ); $log[] = 'WP Rocket: htaccess + config regenerated'; } catch ( \Throwable $e ) { $results['wp-rocket'] = false; $log[] = 'WP Rocket error: ' . $e->getMessage(); } } // WP Super Cache: only stops storing; existing supercache files are still served. Reported as false. if ( function_exists( 'wp_cache_setting' ) ) { try { global $cache_rejected_user_agent; $added['wp-super-cache'] = array_values( array_unique( array_merge( $added['wp-super-cache'] ?? array(), self::missing( (array) $cache_rejected_user_agent, $uas ) ) ) ); $merged = array_values( array_unique( array_merge( (array) $cache_rejected_user_agent, $uas ) ) ); wp_cache_setting( 'cache_rejected_user_agent', $merged ); $log[] = 'WP Super Cache: cache_rejected_user_agent set (partial: existing files still served)'; } catch ( \Throwable $e ) { $log[] = 'WP Super Cache error: ' . $e->getMessage(); } $results['wp-super-cache'] = false; } foreach ( self::detect() as $p ) { if ( ! array_key_exists( $p, $results ) ) { $results[ $p ] = false; // Unsupported cache plugin: cannot guarantee bypass. $log[] = $p . ': no supported user-agent exclusion'; } } $state = array( 'sig' => implode( ',', self::detect() ) . '|' . md5( implode( ',', $uas ) ), 'at' => Util::iso(), 'results' => $results, 'log' => $log, 'added' => $added, ); update_option( self::STATE_OPTION, $state, false ); return $state; } public static function check_w3tc(): bool { try { if ( ! class_exists( '\W3TC\Dispatcher' ) ) { return false; } $have = array_map( 'strtolower', (array) \W3TC\Dispatcher::config()->get_array( 'pgcache.reject.ua' ) ); foreach ( Bots::cache_bypass_tokens() as $t ) { if ( ! in_array( strtolower( $t ), $have, true ) ) { return false; } } return true; } catch ( \Throwable $e ) { return false; } } public static function check_litespeed(): bool { $cur = apply_filters( 'litespeed_conf', 'cache-exc_useragents' ); $cur = is_array( $cur ) ? $cur : preg_split( '/[\r\n]+/', (string) $cur ); $cur = array_map( 'strtolower', (array) $cur ); foreach ( Bots::cache_bypass_tokens() as $t ) { if ( ! in_array( strtolower( $t ), $cur, true ) ) { return false; } } return true; } /** True when every detected page cache is known to let AI bots through (or there is none). */ public static function bypass_ok(): bool { $detected = self::detect(); if ( ! $detected ) { return true; } $state = get_option( self::STATE_OPTION, array() ); $results = is_array( $state ) && is_array( $state['results'] ?? null ) ? $state['results'] : array(); foreach ( $detected as $p ) { if ( 'w3tc' === $p ) { if ( ! self::check_w3tc() ) { return false; } continue; } if ( 'litespeed' === $p ) { if ( ! self::check_litespeed() ) { return false; } continue; } if ( empty( $results[ $p ] ) ) { return false; } } return true; } /** Filter for WP Rocket (anchored .htaccess rule needs the leading .*). */ public static function rocket_reject_ua( $ua ) { $ua = is_array( $ua ) ? $ua : array(); if ( ! self::is_applied() ) { return $ua; } foreach ( Bots::cache_bypass_tokens() as $t ) { $ua[] = '.*' . $t; } return array_values( array_unique( $ua ) ); } }