# xspeed/1.0.1/includes/modules/Cache/CacheModule.php

xSpeed Cache: AI-Powered Performance Hub with MCP, Caching &amp; CDN, version 1.0.1. 153 lines.

- Page: https://pluginprobe.com/plugins/xspeed/1.0.1/code/includes/modules/Cache/CacheModule.php
- Raw: https://pluginprobe.com/plugins/xspeed/1.0.1/raw/includes/modules/Cache/CacheModule.php
- Modified: 2026-06-01T17:33:22+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/xspeed/1.0.1/code/includes/modules/Cache/CacheModule.php#L10-L20`.

```php
<?php
/**
 * Cache module.
 *
 * Owns the cache_expiry and excluded_urls settings. cache_enabled is
 * deliberately NOT in this schema — flipping it triggers the
 * advanced-cache.php drop-in install + WP_CACHE constant edit in
 * wp-config.php, which is a sensitive single-purpose code path and lives
 * in Cache::toggle() with its own dedicated /xspeed/v1/cache/toggle REST
 * route. The dashboard's Cache page renders the special hero UI for it
 * above this module's schema-driven settings panel.
 *
 * Tier: Free.
 *
 * @package XSpeed
 */

declare(strict_types=1);

namespace XSpeed\Modules\Cache;

use XSpeed\Module;
use XSpeed\Settings_Manager;

final class CacheModule extends Module {

	public const SLUG    = 'cache';
	public const TIER    = self::TIER_FREE;
	public const VERSION = '1.0.0';

	public function ui_metadata(): array {
		return array(
			'label'       => '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 );
		}
	}
}

```
