'Cache', 'icon' => 'Database', 'description' => 'Page caching for non-logged-in visitors.', ); } public function settings_schema(): array { return array( 'cache_expiry' => array( 'type' => 'int', 'default' => 24, 'min' => 1, 'max' => 720, 'label' => 'Cache Expiry (hours)', 'description' => 'How long cached pages live before regenerating. 1 to 720 hours (30 days).', ), 'excluded_urls' => array( 'type' => 'list', 'default' => array(), 'item_type' => 'string', 'label' => 'Excluded URLs', 'description' => 'One path per line. Plain text matches anywhere in the URL (e.g. /cart). Use glob syntax for anchored matches: /cart/* matches /cart/items but not /foo/cart/bar; *.pdf matches PDFs.', ), 'excluded_cookies' => array( 'type' => 'list', 'default' => array(), 'item_type' => 'string', 'label' => 'Excluded Cookies', 'description' => 'Skip cache for any visitor whose request carries a cookie whose NAME matches one of these patterns. Glob supported (comment_author_*, woocommerce_*). One per line.', ), 'bypass_user_agents' => array( 'type' => 'list', 'default' => array(), 'item_type' => 'string', 'label' => 'Bypass User Agents', 'description' => 'Substring match against the visitor User-Agent. Matched UAs bypass cache (useful for screenshot bots, internal previews, monitoring). One per line.', ), 'ignored_query_params' => array( 'type' => 'list', 'default' => array( 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid' ), 'item_type' => 'string', 'label' => 'Ignored Query Parameters', 'description' => 'Query keys removed from the URL before computing the cache key, so /post?utm_source=x and /post share a cache entry. Defaults cover the common analytics + ad params. One per line.', ), 'mobile_separate' => array( 'type' => 'bool', 'default' => false, 'label' => 'Separate Mobile Cache', 'description' => 'Keep mobile and desktop responses in separate cache buckets. Turn on for AMP, mobile-specific themes (WPtouch / Jetpack mobile theme), or any setup that serves different HTML by device.', ), ); } /** * Seed per-module option from the legacy xspeed_options blob if we * haven't done so yet. Idempotent — once xspeed_module_cache exists * or the legacy keys are gone, this is a no-op. Runs on both boot * and activate so installs on every code path are covered. */ public function boot(): void { $this->seed_from_legacy_if_needed(); } public function activate(): void { $this->seed_from_legacy_if_needed(); } private function seed_from_legacy_if_needed(): void { if ( null !== get_option( 'xspeed_module_cache', null ) ) { return; } $legacy = get_option( 'xspeed_options', array() ); if ( ! is_array( $legacy ) ) { return; } $seed = array( '_version' => self::VERSION ); $dirty = false; if ( array_key_exists( 'cache_expiry', $legacy ) ) { $seed['cache_expiry'] = max( 1, min( 720, (int) $legacy['cache_expiry'] ) ); unset( $legacy['cache_expiry'] ); $dirty = true; } if ( array_key_exists( 'excluded_urls', $legacy ) ) { $seed['excluded_urls'] = is_array( $legacy['excluded_urls'] ) ? array_values( array_filter( $legacy['excluded_urls'], 'is_string' ) ) : array(); unset( $legacy['excluded_urls'] ); $dirty = true; } if ( $dirty ) { update_option( 'xspeed_module_cache', $seed ); update_option( 'xspeed_options', $legacy ); } } public function cli_commands(): array { return array( array( 'name' => 'xspeed cache', 'callback' => array( $this, 'cli_handler' ), 'shortdesc' => 'Inspect Cache module settings (purge / toggle use the dedicated commands).', 'synopsis' => array( array( 'type' => 'positional', 'name' => 'action', 'options' => array( 'status' ), 'optional' => true, ), ), ), ); } public function cli_handler( array $args, array $assoc ): void { $opts = Settings_Manager::get( self::SLUG ); \WP_CLI::log( 'cache_expiry ' . $opts['cache_expiry'] . 'h' ); \WP_CLI::log( 'excluded_urls ' . count( $opts['excluded_urls'] ) . ' entries' ); foreach ( $opts['excluded_urls'] as $u ) { \WP_CLI::log( ' - ' . $u ); } } /** * Static-rewrite directives for the unified nginx server-block * snippet. Returns null when cache is disabled — there's no rewrite * to install in that state. Delegates to \XSpeed\Cache::nginx_snippet() * which already produces nginx-detection-gated output. */ public function nginx_directives(): ?string { $opts = get_option( 'xspeed_options', array() ); if ( empty( $opts['cache_enabled'] ) ) { return null; } return \XSpeed\Cache::nginx_snippet(); } }