PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.7
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.7
1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 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 All 33 releases
xspeed / includes / modules / BrowserCache / BrowserCacheModule.php

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

190 lines 6.8 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', 'xspeed' ),
33 'icon' => 'Clock',
34 'description' => __( 'Tells browsers and CDNs how long to keep your files and pages.', 'xspeed' ),
35 'group' => 'cache',
36 );
37 }
38
39 public function settings_schema(): array {
40 return array(
41 'enabled' => array(
42 'type' => 'bool',
43 'default' => false,
44 'label' => __( 'Enable browser cache headers', 'xspeed' ),
45 'description' => __( 'On Apache and LiteSpeed, xSpeed adds the rules to .htaccess for you. On nginx, you copy the snippet into your server config.', 'xspeed' ),
46 ),
47 'asset_ttl' => array(
48 'type' => 'int',
49 'default' => Browser_Cache::DEFAULT_ASSET_TTL,
50 'min' => 0,
51 'max' => 31536000,
52 'label' => __( 'File lifetime (seconds)', 'xspeed' ),
53 'unit' => 'seconds',
54 'description' => __( 'How long browsers keep CSS, JS, fonts and images. The default is 1 year, which suits files that change name when they change.', 'xspeed' ),
55 'dependsOn' => array( 'field' => 'enabled' ),
56 ),
57 'html_ttl' => array(
58 'type' => 'int',
59 'default' => Browser_Cache::DEFAULT_HTML_TTL,
60 'min' => 0,
61 'max' => 31536000,
62 'label' => __( 'Page lifetime (seconds)', 'xspeed' ),
63 'unit' => 'seconds',
64 'description' => __( 'How long browsers keep the page itself. Keep it short (default 1 hour) so visitors see your edits the same day.', 'xspeed' ),
65 'dependsOn' => array( 'field' => 'enabled' ),
66 ),
67 );
68 }
69
70 public function boot(): void {
71 add_action( 'update_option_xspeed_module_browser-cache', array( $this, 'on_settings_change' ), 10, 2 );
72 add_action( 'add_option_xspeed_module_browser-cache', array( $this, 'on_settings_added' ), 10, 2 );
73 }
74
75 public function on_settings_change( $old, $new ): void {
76 if ( ! is_array( $new ) ) {
77 return;
78 }
79 Browser_Cache::apply( ! empty( $new['enabled'] ), $new );
80 // Settings just changed — TTL values likely differ, so the
81 // currently-cached "probe says headers active" answer is stale.
82 // Clear the transient so the next dashboard load re-probes.
83 delete_transient( 'xspeed_browser_cache_probe' );
84 }
85
86 public function on_settings_added( $name, $value ): void {
87 if ( ! is_array( $value ) ) {
88 return;
89 }
90 Browser_Cache::apply( ! empty( $value['enabled'] ), $value );
91 delete_transient( 'xspeed_browser_cache_probe' );
92 }
93
94 public function ui_notices(): array {
95 if ( ! class_exists( '\\XSpeed\\Server' ) || Server::supports_htaccess() ) {
96 return array();
97 }
98 $opts = $this->get_settings();
99 if ( empty( $opts['enabled'] ) ) {
100 return array();
101 }
102
103 // Probe the live response for caching headers on a known static
104 // asset. Suppress the notice unless the probe PROVES nothing is
105 // being sent:
106 //
107 // true → headers present, from our snippet or from the host's own
108 // vhost — either way the feature's job is done (issue #329)
109 // null → the loopback never completed, so we know nothing; a
110 // broken probe is not evidence of a broken server and must
111 // not raise a warning the operator cannot act on (issue #18)
112 // false → proven absent, fall through and show the notice
113 if ( false !== Browser_Cache::probe_headers_present() ) {
114 return array();
115 }
116
117 // nginx hosts can't auto-write Cache-Control / Expires headers, and the
118 // live probe just confirmed they are NOT being served — so the feature
119 // reads "enabled" in the dashboard while doing nothing. That is a warning,
120 // not a passive info note (a token that a user missed on this exact
121 // account, issue #117): escalate the tone and say plainly that the config
122 // is configured-but-not-live until the snippet is pasted + nginx reloaded.
123 // The directives go into the unified server-block snippet on the Cache
124 // panel — point users there instead of duplicating the snippet here.
125 return array(
126 array(
127 'tone' => 'warn',
128 'title' => __( 'Browser cache is enabled but not active on the server', 'xspeed' ),
129 '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' ),
130 // Lands on the snippet itself rather than on the Cache panel,
131 // where it is one collapsed section among several (issue #49).
132 'action' => Deep_Link::action(
133 __( 'Go to the snippet', 'xspeed' ),
134 'cache',
135 'nginx_snippet'
136 ),
137 ),
138 );
139 }
140
141 public function deactivate(): void {
142 Browser_Cache::apply( false );
143 }
144
145 public function cli_commands(): array {
146 return array(
147 array(
148 'name' => 'xspeed browser-cache',
149 'callback' => array( $this, 'cli_handler' ),
150 'shortdesc' => 'Print the Apache or nginx browser-cache snippet.',
151 '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.',
152 'synopsis' => array(
153 array(
154 'type' => 'positional',
155 'name' => 'flavor',
156 'options' => array( 'apache', 'nginx' ),
157 'optional' => true,
158 ),
159 ),
160 ),
161 );
162 }
163
164 public function cli_handler( array $args, array $assoc ): void {
165 $flavor = $args[0] ?? 'apache';
166 $opts = $this->get_settings();
167 if ( 'nginx' === $flavor ) {
168 \WP_CLI::log( Browser_Cache::nginx_snippet( $opts ) );
169 return;
170 }
171 foreach ( Browser_Cache::apache_rules( $opts ) as $line ) {
172 \WP_CLI::log( $line );
173 }
174 }
175
176 /**
177 * Cache-Control directives for the unified nginx server-block
178 * snippet. nginx sends no Expires — `expires off;` pins the block so
179 * only the explicit `add_header` speaks (#259). Null when the module is disabled — no
180 * directives to install.
181 */
182 public function nginx_directives(): ?string {
183 $opts = $this->get_settings();
184 if ( empty( $opts['enabled'] ) ) {
185 return null;
186 }
187 return Browser_Cache::nginx_snippet( $opts );
188 }
189 }
190