, * server_cache: list, * cdn: list, * object_cache: array{enabled:bool,driver:string}, * advanced_cache: array{exists:bool,wp_cache_constant:bool,created_by:string}, * active_test: array{headers_checked:bool,cache_hit:bool}, * } * * @since 4.9.0 */ class Cache_Detector { /** * Note: this is not an exhaustive list of all caching plugins, but * covers the most popular ones and those with distinctive advanced-cache.php signatures. * * The presence of a plugin in this list is not required for the detector to identify caching — it's just a helper for known plugins. */ private const CACHE_PLUGINS = array( 'wp-rocket/wp-rocket.php' => 'WP Rocket', 'w3-total-cache/w3-total-cache.php' => 'W3 Total Cache', 'wp-super-cache/wp-cache.php' => 'WP Super Cache', 'litespeed-cache/litespeed-cache.php' => 'LiteSpeed Cache', 'cache-enabler/cache-enabler.php' => 'Cache Enabler', 'swift-performance-lite/performance.php' => 'Swift Performance Lite', 'swift-performance/performance.php' => 'Swift Performance', 'sg-cachepress/sg-cachepress.php' => 'SG Optimizer', 'hummingbird-performance/wp-hummingbird.php' => 'Hummingbird', 'breeze/breeze.php' => 'Breeze (Cloudways)', 'comet-cache/comet-cache.php' => 'Comet Cache', 'hyper-cache/plugin.php' => 'Hyper Cache', 'powered-cache/powered-cache.php' => 'Powered Cache', 'wp-fastest-cache/wpFastestCache.php' => 'WP Fastest Cache', 'flying-pages/flying-pages.php' => 'Flying Pages', 'nginx-helper/nginx-helper.php' => 'Nginx Helper', 'redis-cache/redis-cache.php' => 'Redis Object Cache', 'wp-redis/wp-redis.php' => 'WP Redis', ); /** * Note: this is not an exhaustive list of all CDNs, but covers the most common ones with identifiable headers. * The presence of a CDN in this list is not required for detection — it's just a helper for known CDNs. */ private const CDN_HEADERS = array( 'x-served-by' => 'Fastly', 'x-cache' => 'CDN', 'cf-ray' => 'Cloudflare', 'x-amz-cf-id' => 'Amazon CloudFront', 'x-cdn' => 'CDN', 'x-sucuri-id' => 'Sucuri', 'x-stackpath-edge' => 'StackPath', 'x-bunny-cached' => 'BunnyCDN', ); /** * Cached HEAD response for the home URL, shared between detect_server_cache() and detect_cdn() * to avoid issuing the same HTTP request twice. * * @var \WP_Error|array|null */ private static $head_response = null; /** * Run the full cache detection and return a structured result. * * @return array * * @since 4.9.0 */ public static function detect(): array { $plugins = static::detect_plugins(); $advanced_cache = static::detect_advanced_cache(); $server_cache = static::detect_server_cache(); $cdn = static::detect_cdn(); $object_cache = static::detect_object_cache(); $active_test = static::run_active_test(); $status = static::determine_status( $plugins, $advanced_cache, $server_cache, $cdn, $active_test ); $score = static::calculate_score( $status, $plugins, $server_cache, $cdn, $object_cache ); return array( 'status' => $status, 'score' => $score, 'plugins' => $plugins, 'server_cache' => $server_cache, 'cdn' => $cdn, 'object_cache' => $object_cache, 'advanced_cache' => $advanced_cache, 'active_test' => $active_test, ); } // ------------------------------------------------------------------ // Detection sub-tasks // ------------------------------------------------------------------ /** * Detect installed / active caching plugins and whether they have * caching actually enabled. * * @return array[] * * @since 4.9.0 */ private static function detect_plugins(): array { // Ensure the is_plugin_active() helper is available in all contexts. if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $found = array(); foreach ( self::CACHE_PLUGINS as $slug => $name ) { $plugin_dir = \WP_PLUGIN_DIR . '/' . dirname( $slug ); $installed = is_dir( $plugin_dir ); $activated = $installed && \is_plugin_active( $slug ); if ( ! $installed ) { continue; } $found[] = array( 'name' => $name, 'slug' => $slug, 'installed' => $installed, 'activated' => $activated, 'enabled' => $activated ? static::is_caching_enabled( $slug ) : false, ); } return $found; } /** * Check whether a specific plugin's caching feature is actually enabled. * * Returns true/false when determinable, 'unknown' otherwise. * * @param string $slug - Plugin file slug. * * @return bool|string * * @since 4.9.0 */ private static function is_caching_enabled( string $slug ): bool|string { switch ( $slug ) { case 'wp-rocket/wp-rocket.php': if ( function_exists( 'get_rocket_option' ) ) { return (bool) get_rocket_option( 'cache_mobile', false ) || true; // WP Rocket always caches when active. } return 'unknown'; case 'w3-total-cache/w3-total-cache.php': if ( function_exists( 'w3tc_pgcache_flush' ) ) { // W3TC is active and pgcache module available. $config = get_option( 'w3tc_config' ); if ( is_array( $config ) ) { return ! empty( $config['pgcache.enabled'] ); } } return 'unknown'; case 'wp-super-cache/wp-cache.php': $supercache_enabled = get_option( 'wpsupercache_enabled' ); if ( false !== $supercache_enabled ) { return (bool) $supercache_enabled; } return 'unknown'; case 'litespeed-cache/litespeed-cache.php': if ( class_exists( '\LiteSpeed\Conf' ) ) { return (bool) \LiteSpeed\Conf::val( \LiteSpeed\Base::O_CACHE ); } $ls_option = get_option( 'litespeed.conf.cache' ); if ( false !== $ls_option ) { return (bool) $ls_option; } return 'unknown'; case 'cache-enabler/cache-enabler.php': $ce_options = get_option( 'cache_enabler' ); if ( is_array( $ce_options ) ) { return ! empty( $ce_options['cache_enabled'] ); } return 'unknown'; case 'wp-fastest-cache/wpFastestCache.php': $wpfc_options = get_option( 'WpFastestCacheOptions' ); if ( $wpfc_options ) { return true; } return 'unknown'; default: return 'unknown'; } } /** * Inspect the advanced-cache.php drop-in and the WP_CACHE constant. * * @return array{exists:bool,wp_cache_constant:bool,created_by:string} * * @since 4.9.0 */ private static function detect_advanced_cache(): array { $dropin_path = WP_CONTENT_DIR . '/advanced-cache.php'; $exists = file_exists( $dropin_path ); $wp_cache_const = defined( 'WP_CACHE' ) && WP_CACHE; $created_by = ''; if ( $exists ) { // Read a small chunk of the file to identify the creator. $header = (string) file_get_contents( $dropin_path, false, null, 0, 4096 ); $signatures = array( 'WP Rocket' => 'WP Rocket', 'W3 Total Cache' => 'W3TC', 'WP Super Cache' => 'WP Super Cache', 'LiteSpeed' => 'LiteSpeed Cache', 'Cache Enabler' => 'Cache Enabler', 'Breeze' => 'Breeze', 'Hummingbird' => 'Hummingbird', 'Swift Performance' => 'Swift Performance', ); foreach ( $signatures as $needle => $label ) { if ( false !== stripos( $header, $needle ) ) { $created_by = $label; break; } } } return array( 'exists' => $exists, 'wp_cache_constant' => $wp_cache_const, 'created_by' => $created_by, ); } /** * Detect server-level caching (Nginx FastCGI, Varnish, etc.) via * response headers of a cached request to the home URL. * * @return array[] * * @since 4.9.0 */ private static function detect_server_cache(): array { $found = array(); // Attempt a HEAD request to the home URL (result is cached for reuse by detect_cdn()). if ( null === static::$head_response ) { static::$head_response = \wp_remote_head( \home_url( '/' ), array( 'timeout' => 5, 'user-agent' => 'advanced-analytics-cache-detector/1.0', 'headers' => Scanner::get_loopback_headers(), ) ); } $response = static::$head_response; if ( \is_wp_error( $response ) ) { return $found; } $headers = \wp_remote_retrieve_headers( $response ); // Nginx FastCGI / proxy cache. $nginx_status = (string) ( $headers['x-nginx-cache-status'] ?? $headers['x-cache-status'] ?? '' ); if ( '' !== $nginx_status ) { $found[] = array( 'type' => 'Nginx Cache', 'status' => in_array( strtoupper( $nginx_status ), array( 'HIT', 'ACTIVE' ), true ) ? 'active' : 'present', ); } // Varnish. $via = (string) ( $headers['via'] ?? '' ); if ( false !== stripos( $via, 'varnish' ) ) { $age = (int) ( $headers['age'] ?? 0 ); $found[] = array( 'type' => 'Varnish', 'status' => $age > 0 ? 'active' : 'present', ); } // CloudLinux LiteSpeed Enterprise. if ( isset( $headers['x-litespeed-cache'] ) ) { $found[] = array( 'type' => 'LiteSpeed Server Cache', 'status' => strtolower( (string) $headers['x-litespeed-cache'] ) === 'miss' ? 'present' : 'active', ); } return $found; } /** * Detect a CDN presence via response headers. * * @return array[] * * @since 4.9.0 */ private static function detect_cdn(): array { $found = array(); // Reuse the cached HEAD response from detect_server_cache() if available. if ( null === static::$head_response ) { static::$head_response = \wp_remote_head( \home_url( '/' ), array( 'timeout' => 5, 'user-agent' => 'advanced-analytics-cache-detector/1.0', 'headers' => Scanner::get_loopback_headers(), ) ); } $response = static::$head_response; if ( \is_wp_error( $response ) ) { return $found; } $headers = \wp_remote_retrieve_headers( $response ); foreach ( self::CDN_HEADERS as $header => $name ) { if ( isset( $headers[ $header ] ) ) { $found[] = array( 'name' => $name ); break; // One CDN is enough. } } return $found; } /** * Detect object cache (Redis, Memcached, APCu, and the WP drop-in). * * @return array{enabled:bool,driver:string} */ private static function detect_object_cache(): array { $dropin = \WP_CONTENT_DIR . '/object-cache.php'; $enabled = file_exists( $dropin ); $driver = ''; if ( $enabled ) { $header = (string) file_get_contents( $dropin, false, null, 0, 2048 ); $signatures = array( 'Redis' => 'Redis', 'Memcached' => 'Memcached', 'APCu' => 'APCu', 'Relay' => 'Relay', ); foreach ( $signatures as $needle => $label ) { if ( false !== stripos( $header, $needle ) ) { $driver = $label; break; } } } // Also check wp_using_ext_object_cache() (available since WP 3.7). if ( ! $enabled && function_exists( '\wp_using_ext_object_cache' ) ) { $enabled = \wp_using_ext_object_cache(); } return array( 'enabled' => $enabled, 'driver' => $driver, ); } /** * Attempt an active cache-hit test: fetch the home page twice and * look for a cache HIT header on the second request. * * @return array{headers_checked:bool,cache_hit:bool} * * @since 4.9.0 */ private static function run_active_test(): array { // Warm-up request. \wp_remote_get( \home_url( '/' ), array( 'timeout' => 5, 'user-agent' => 'advanced-analytics-cache-detector/1.0', 'headers' => Scanner::get_loopback_headers(), ) ); // Second request — should hit cache. $response = \wp_remote_get( \home_url( '/' ), array( 'timeout' => 5, 'user-agent' => 'advanced-analytics-cache-detector/1.0', 'headers' => Scanner::get_loopback_headers(), ) ); if ( \is_wp_error( $response ) ) { return array( 'headers_checked' => false, 'cache_hit' => false, ); } $headers = \wp_remote_retrieve_headers( $response ); $hit_value = strtoupper( (string) ( $headers['x-cache'] ?? $headers['x-cache-status'] ?? $headers['x-nginx-cache-status'] ?? $headers['x-litespeed-cache'] ?? '' ) ); $cache_hit = in_array( $hit_value, array( 'HIT', 'ACTIVE', 'HIT, HIT' ), true ); return array( 'headers_checked' => true, 'cache_hit' => $cache_hit, ); } // ------------------------------------------------------------------ // Status / score logic // ------------------------------------------------------------------ /** * Determine the overall caching status. * * @param array[] $plugins Plugin detection results. * @param array $advanced_cache advanced-cache.php data. * @param array[] $server_cache Server cache results. * @param array[] $cdn CDN results. * @param array $active_test Active test result. * * @return string 'active'|'inactive'|'none' * * @since 4.9.0 */ private static function determine_status( array $plugins, array $advanced_cache, array $server_cache, array $cdn, array $active_test ): string { // Active test hit = caching is definitely working. if ( ! empty( $active_test['cache_hit'] ) ) { return 'active'; } // Check plugin-level: activated + enabled. foreach ( $plugins as $plugin ) { if ( $plugin['activated'] ) { if ( true === $plugin['enabled'] || 'unknown' === $plugin['enabled'] ) { return 'active'; } // Activated but explicitly disabled. return 'inactive'; } } // advanced-cache.php drop-in present = active via some mechanism. if ( $advanced_cache['exists'] && $advanced_cache['wp_cache_constant'] ) { return 'active'; } // Server-level caching active. foreach ( $server_cache as $cache ) { if ( 'active' === $cache['status'] ) { return 'active'; } } // advanced-cache.php exists but WP_CACHE not set = inactive. if ( $advanced_cache['exists'] && ! $advanced_cache['wp_cache_constant'] ) { return 'inactive'; } // Plugin installed but not activated. foreach ( $plugins as $plugin ) { if ( $plugin['installed'] && ! $plugin['activated'] ) { return 'inactive'; } } return 'none'; } /** * Compute a 0-100 score for the caching configuration. * * @param string $status - Overall status: 'active', 'inactive', or 'none'. * @param array[] $plugins - Detected caching plugins and their states. * @param array[] $server_cache - Detected server-level caching solutions. * @param array[] $cdn - Detected CDNs. * @param array $object_cache - Detected object cache status and driver. * * @return int * * @since 4.9.0 */ private static function calculate_score( string $status, array $plugins, array $server_cache, array $cdn, array $object_cache ): int { if ( 'none' === $status ) { return 0; } if ( 'inactive' === $status ) { return 20; } // Base for 'active'. $score = 60; // Bonus: dedicated caching plugin active. foreach ( $plugins as $p ) { if ( $p['activated'] && true === $p['enabled'] ) { $score += 15; break; } } // Bonus: server-level cache on top. if ( ! empty( $server_cache ) ) { $score += 10; } // Bonus: CDN. if ( ! empty( $cdn ) ) { $score += 10; } // Bonus: object cache. if ( $object_cache['enabled'] ) { $score += 5; } return min( 100, $score ); } } }