# xspeed/1.0.4/includes/modules/BrowserCache/BrowserCacheModule.php

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

- Page: https://pluginprobe.com/plugins/xspeed/1.0.4/code/includes/modules/BrowserCache/BrowserCacheModule.php
- Raw: https://pluginprobe.com/plugins/xspeed/1.0.4/raw/includes/modules/BrowserCache/BrowserCacheModule.php
- Modified: 2026-06-14T07:01:26+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.4/code/includes/modules/BrowserCache/BrowserCacheModule.php#L10-L20`.

```php
<?php
/**
 * Browser cache headers module — writes Cache-Control + Expires
 * directives to .htaccess (Apache/LiteSpeed) or surfaces an nginx
 * snippet for manual paste on other servers.
 *
 * Tier: Free per FEATURES.md (LiteSpeed parity — Browser Cache
 * settings are all in LS free).
 *
 * @package XSpeed
 */

declare(strict_types=1);

namespace XSpeed\Modules\BrowserCache;

use XSpeed\Browser_Cache;
use XSpeed\Module;
use XSpeed\Server;

final class BrowserCacheModule extends Module {

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

	public function ui_metadata(): array {
		return array(
			'label'       => 'Browser Cache',
			'icon'        => 'Clock',
			'description' => 'Tell browsers (and intermediate CDNs) how long to cache static assets and HTML.',
		);
	}

	public function settings_schema(): array {
		return array(
			'enabled' => array(
				'type'        => 'bool',
				'default'     => false,
				'label'       => 'Enable browser cache headers',
				'description' => 'On Apache/LiteSpeed this writes Cache-Control + Expires rules into .htaccess. On nginx it just stores the settings — you paste the snippet into your server block manually.',
			),
			'asset_ttl' => array(
				'type'        => 'int',
				'default'     => Browser_Cache::DEFAULT_ASSET_TTL,
				'min'         => 0,
				'max'         => 31536000,
				'label'       => 'Static asset TTL (seconds)',
				'description' => 'Cache lifetime for CSS, JS, fonts, images. Defaults to 1 year + immutable (the industry-standard "fingerprinted assets never change" pattern).',
			),
			'html_ttl' => array(
				'type'        => 'int',
				'default'     => Browser_Cache::DEFAULT_HTML_TTL,
				'min'         => 0,
				'max'         => 31536000,
				'label'       => 'HTML TTL (seconds)',
				'description' => 'Cache lifetime for the HTML document itself. Keep short (default 1h) so post edits roll out same-day.',
			),
		);
	}

	public function boot(): void {
		add_action( 'update_option_xspeed_module_browser-cache', array( $this, 'on_settings_change' ), 10, 2 );
		add_action( 'add_option_xspeed_module_browser-cache', array( $this, 'on_settings_added' ), 10, 2 );
	}

	public function on_settings_change( $old, $new ): void {
		if ( ! is_array( $new ) ) {
			return;
		}
		Browser_Cache::apply( ! empty( $new['enabled'] ), $new );
		// Settings just changed — TTL values likely differ, so the
		// currently-cached "probe says headers active" answer is stale.
		// Clear the transient so the next dashboard load re-probes.
		delete_transient( 'xspeed_browser_cache_probe' );
	}

	public function on_settings_added( $name, $value ): void {
		if ( ! is_array( $value ) ) {
			return;
		}
		Browser_Cache::apply( ! empty( $value['enabled'] ), $value );
		delete_transient( 'xspeed_browser_cache_probe' );
	}

	public function ui_notices(): array {
		if ( ! class_exists( '\\XSpeed\\Server' ) || Server::supports_htaccess() ) {
			return array();
		}
		$opts = $this->get_settings();
		if ( empty( $opts['enabled'] ) ) {
			return array();
		}

		// Probe whether the snippet has actually been pasted + reloaded.
		// If the live HEAD shows Cache-Control: immutable on a known
		// static asset, the user is done — suppress the notice. Avoids
		// the false-alarm "do something" prompt we used to show forever.
		if ( Browser_Cache::probe_headers_present() ) {
			return array();
		}

		// nginx hosts can't auto-write Cache-Control / Expires headers.
		// The directives go into the unified server-block snippet on
		// the Cache panel — point users there instead of duplicating the
		// snippet here. Topology-aware wording lives in NginxServerBlock.
		return array(
			array(
				'tone'  => 'info',
				'title' => 'nginx server config required',
				'body'  => 'Browser-cache headers need to live in your nginx config. Your updated settings are included in the unified server-block snippet on the Cache panel — copy + paste it once into your nginx vhost (or container nginx config), and reload nginx.',
			),
		);
	}

	public function deactivate(): void {
		Browser_Cache::apply( false );
	}

	public function cli_commands(): array {
		return array(
			array(
				'name'      => 'xspeed browser-cache',
				'callback'  => array( $this, 'cli_handler' ),
				'shortdesc' => 'Print the Apache or nginx browser-cache snippet.',
				'synopsis'  => array(
					array(
						'type'     => 'positional',
						'name'     => 'flavor',
						'options'  => array( 'apache', 'nginx' ),
						'optional' => true,
					),
				),
			),
		);
	}

	public function cli_handler( array $args, array $assoc ): void {
		$flavor = $args[0] ?? 'apache';
		$opts   = $this->get_settings();
		if ( 'nginx' === $flavor ) {
			\WP_CLI::log( Browser_Cache::nginx_snippet( $opts ) );
			return;
		}
		foreach ( Browser_Cache::apache_rules( $opts ) as $line ) {
			\WP_CLI::log( $line );
		}
	}

	/**
	 * Cache-Control / Expires directives for the unified nginx
	 * server-block snippet. Null when the module is disabled — no
	 * directives to install.
	 */
	public function nginx_directives(): ?string {
		$opts = $this->get_settings();
		if ( empty( $opts['enabled'] ) ) {
			return null;
		}
		return Browser_Cache::nginx_snippet( $opts );
	}
}

```
