PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / trunk
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN vtrunk
1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.2.0 1.2.1 1.2.2 1.2.3
xspeed / includes / modules / BrowserCache / BrowserCacheModule.php

BrowserCacheModule.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN trunk, at includes/modules/BrowserCache/BrowserCacheModule.php

188 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Browser cache headers module — writes Cache-Control + Expires
4 * directives to .htaccess (Apache/LiteSpeed) or surfaces an nginx
5 * snippet for manual paste on other servers.
6 *
7 * Tier: Free per FEATURES.md (LiteSpeed parity — Browser Cache
8 * settings are all in LS free).
9 *
10 * @package XSpeed
11 */
12
13 declare(strict_types=1);
14
15 namespace XSpeed\Modules\BrowserCache;
16
17 defined( 'ABSPATH' ) || exit;
18
19 use XSpeed\Browser_Cache;
20 use XSpeed\Deep_Link;
21 use XSpeed\Module;
22 use XSpeed\Server;
23
24 final class BrowserCacheModule extends Module {
25
26 public const SLUG = 'browser-cache';
27 public const TIER = self::TIER_FREE;
28 public const VERSION = '1.0.0';
29
30 public function ui_metadata(): array {
31 return array(
32 'label' => 'Browser Cache',
33 'icon' => 'Clock',
34 'description' => 'Tell browsers (and intermediate CDNs) how long to cache static assets and HTML.',
35 );
36 }
37
38 public function settings_schema(): array {
39 return array(
40 'enabled' => array(
41 'type' => 'bool',
42 'default' => false,
43 'label' => 'Enable browser cache headers',
44 '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.',
45 ),
46 'asset_ttl' => array(
47 'type' => 'int',
48 'default' => Browser_Cache::DEFAULT_ASSET_TTL,
49 'min' => 0,
50 'max' => 31536000,
51 'label' => 'Static asset TTL (seconds)',
52 'unit' => 'seconds',
53 'description' => 'Cache lifetime for CSS, JS, fonts, images. Defaults to 1 year + immutable (the industry-standard "fingerprinted assets never change" pattern).',
54 'dependsOn' => array( 'field' => 'enabled' ),
55 ),
56 'html_ttl' => array(
57 'type' => 'int',
58 'default' => Browser_Cache::DEFAULT_HTML_TTL,
59 'min' => 0,
60 'max' => 31536000,
61 'label' => 'HTML TTL (seconds)',
62 'unit' => 'seconds',
63 'description' => 'Cache lifetime for the HTML document itself. Keep short (default 1h) so post edits roll out same-day.',
64 'dependsOn' => array( 'field' => 'enabled' ),
65 ),
66 );
67 }
68
69 public function boot(): void {
70 add_action( 'update_option_xspeed_module_browser-cache', array( $this, 'on_settings_change' ), 10, 2 );
71 add_action( 'add_option_xspeed_module_browser-cache', array( $this, 'on_settings_added' ), 10, 2 );
72 }
73
74 public function on_settings_change( $old, $new ): void {
75 if ( ! is_array( $new ) ) {
76 return;
77 }
78 Browser_Cache::apply( ! empty( $new['enabled'] ), $new );
79 // Settings just changed — TTL values likely differ, so the
80 // currently-cached "probe says headers active" answer is stale.
81 // Clear the transient so the next dashboard load re-probes.
82 delete_transient( 'xspeed_browser_cache_probe' );
83 }
84
85 public function on_settings_added( $name, $value ): void {
86 if ( ! is_array( $value ) ) {
87 return;
88 }
89 Browser_Cache::apply( ! empty( $value['enabled'] ), $value );
90 delete_transient( 'xspeed_browser_cache_probe' );
91 }
92
93 public function ui_notices(): array {
94 if ( ! class_exists( '\\XSpeed\\Server' ) || Server::supports_htaccess() ) {
95 return array();
96 }
97 $opts = $this->get_settings();
98 if ( empty( $opts['enabled'] ) ) {
99 return array();
100 }
101
102 // Probe the live response for caching headers on a known static
103 // asset. Suppress the notice unless the probe PROVES nothing is
104 // being sent:
105 //
106 // true → headers present, from our snippet or from the host's own
107 // vhost — either way the feature's job is done (issue #329)
108 // null → the loopback never completed, so we know nothing; a
109 // broken probe is not evidence of a broken server and must
110 // not raise a warning the operator cannot act on (issue #18)
111 // false → proven absent, fall through and show the notice
112 if ( false !== Browser_Cache::probe_headers_present() ) {
113 return array();
114 }
115
116 // nginx hosts can't auto-write Cache-Control / Expires headers, and the
117 // live probe just confirmed they are NOT being served — so the feature
118 // reads "enabled" in the dashboard while doing nothing. That is a warning,
119 // not a passive info note (a token that a user missed on this exact
120 // account, issue #117): escalate the tone and say plainly that the config
121 // is configured-but-not-live until the snippet is pasted + nginx reloaded.
122 // The directives go into the unified server-block snippet on the Cache
123 // panel — point users there instead of duplicating the snippet here.
124 return array(
125 array(
126 'tone' => 'warn',
127 'title' => __( 'Browser cache is enabled but not active on the server', 'xspeed' ),
128 'body' => __( 'Browser-cache headers need to live in your nginx config, and the live response shows they are not being sent yet — so this is on in settings but doing nothing. Your settings are included in the unified server-block snippet on the Cache panel: paste it once into your nginx vhost (or container nginx config) and reload nginx.', 'xspeed' ),
129 // Lands on the snippet itself rather than on the Cache panel,
130 // where it is one collapsed section among several (issue #49).
131 'action' => Deep_Link::action(
132 __( 'Go to the snippet', 'xspeed' ),
133 'cache',
134 'nginx_snippet'
135 ),
136 ),
137 );
138 }
139
140 public function deactivate(): void {
141 Browser_Cache::apply( false );
142 }
143
144 public function cli_commands(): array {
145 return array(
146 array(
147 'name' => 'xspeed browser-cache',
148 'callback' => array( $this, 'cli_handler' ),
149 'shortdesc' => 'Print the Apache or nginx browser-cache snippet.',
150 'ai_hint' => 'Get the server config snippet that sets browser cache-control headers for static assets. Use when PageSpeed reports "serve static assets with an efficient cache policy", or when the user needs the rules to paste into Apache/nginx.',
151 'synopsis' => array(
152 array(
153 'type' => 'positional',
154 'name' => 'flavor',
155 'options' => array( 'apache', 'nginx' ),
156 'optional' => true,
157 ),
158 ),
159 ),
160 );
161 }
162
163 public function cli_handler( array $args, array $assoc ): void {
164 $flavor = $args[0] ?? 'apache';
165 $opts = $this->get_settings();
166 if ( 'nginx' === $flavor ) {
167 \WP_CLI::log( Browser_Cache::nginx_snippet( $opts ) );
168 return;
169 }
170 foreach ( Browser_Cache::apache_rules( $opts ) as $line ) {
171 \WP_CLI::log( $line );
172 }
173 }
174
175 /**
176 * Cache-Control / Expires directives for the unified nginx
177 * server-block snippet. Null when the module is disabled — no
178 * directives to install.
179 */
180 public function nginx_directives(): ?string {
181 $opts = $this->get_settings();
182 if ( empty( $opts['enabled'] ) ) {
183 return null;
184 }
185 return Browser_Cache::nginx_snippet( $opts );
186 }
187 }
188