PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.2.0
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.2.0
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 1.1.5 1.1.6 1.1.7 1.1.8 1.2.0 All 28 releases
xspeed / includes / modules / BrowserCache / BrowserCacheModule.php

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

181 lines 6.1 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 whether the snippet has actually been pasted + reloaded.
103 // If the live HEAD shows Cache-Control: immutable on a known
104 // static asset, the user is done — suppress the notice. Avoids
105 // the false-alarm "do something" prompt we used to show forever.
106 if ( Browser_Cache::probe_headers_present() ) {
107 return array();
108 }
109
110 // nginx hosts can't auto-write Cache-Control / Expires headers, and the
111 // live probe just confirmed they are NOT being served — so the feature
112 // reads "enabled" in the dashboard while doing nothing. That is a warning,
113 // not a passive info note (a token that a user missed on this exact
114 // account, issue #117): escalate the tone and say plainly that the config
115 // is configured-but-not-live until the snippet is pasted + nginx reloaded.
116 // The directives go into the unified server-block snippet on the Cache
117 // panel — point users there instead of duplicating the snippet here.
118 return array(
119 array(
120 'tone' => 'warn',
121 'title' => __( 'Browser cache is enabled but not active on the server', 'xspeed' ),
122 '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' ),
123 // Lands on the snippet itself rather than on the Cache panel,
124 // where it is one collapsed section among several (issue #49).
125 'action' => Deep_Link::action(
126 __( 'Go to the snippet', 'xspeed' ),
127 'cache',
128 'nginx_snippet'
129 ),
130 ),
131 );
132 }
133
134 public function deactivate(): void {
135 Browser_Cache::apply( false );
136 }
137
138 public function cli_commands(): array {
139 return array(
140 array(
141 'name' => 'xspeed browser-cache',
142 'callback' => array( $this, 'cli_handler' ),
143 'shortdesc' => 'Print the Apache or nginx browser-cache snippet.',
144 'synopsis' => array(
145 array(
146 'type' => 'positional',
147 'name' => 'flavor',
148 'options' => array( 'apache', 'nginx' ),
149 'optional' => true,
150 ),
151 ),
152 ),
153 );
154 }
155
156 public function cli_handler( array $args, array $assoc ): void {
157 $flavor = $args[0] ?? 'apache';
158 $opts = $this->get_settings();
159 if ( 'nginx' === $flavor ) {
160 \WP_CLI::log( Browser_Cache::nginx_snippet( $opts ) );
161 return;
162 }
163 foreach ( Browser_Cache::apache_rules( $opts ) as $line ) {
164 \WP_CLI::log( $line );
165 }
166 }
167
168 /**
169 * Cache-Control / Expires directives for the unified nginx
170 * server-block snippet. Null when the module is disabled — no
171 * directives to install.
172 */
173 public function nginx_directives(): ?string {
174 $opts = $this->get_settings();
175 if ( empty( $opts['enabled'] ) ) {
176 return null;
177 }
178 return Browser_Cache::nginx_snippet( $opts );
179 }
180 }
181