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
← All changes | includes/modules/Cache/CacheModule.php +93 -21 1.3.4 → 1.3.7 View file →
@@ -107,9 +107,48 @@
107 107 'usqp',
108 108 '~utm_[a-zA-Z0-9_-]+',
109 109 );
110 110
111 + /**
112 + * Click and campaign IDs added to the defaults in 1.3.6.
113 + *
114 + * Each is unique per click and never changes the page, yet each one
115 + * bypassed the page cache and, with xSpeed Pro, was a new CSS entry to
116 + * build: Google Merchant's `srsltid` and Ads' `gad_*`/`gbraid`/`wbraid`,
117 + * GA4's cross-domain `_gl`, TikTok, X, Instagram, Yandex, HubSpot email
118 + * and LinkedIn IDs. Kept as their own list so the upgrade can add exactly
119 + * these to a saved list without re-adding anything a site removed.
120 + *
121 + * @var string[]
122 + */
123 + public const TRACKING_PARAMS_1_3_6 = array(
124 + '_gl',
125 + '_hsenc',
126 + '_hsmi',
127 + 'gad_campaignid',
128 + 'gad_source',
129 + 'gbraid',
130 + 'igshid',
131 + 'li_fat_id',
132 + 'srsltid',
133 + 'ttclid',
134 + 'twclid',
135 + 'wbraid',
136 + 'yclid',
137 + );
111 138
139 +
140 + /**
141 + * The shipped default list: the base list plus later additions, sorted.
142 + *
143 + * @return string[]
144 + */
145 + public static function default_ignored_query_params(): array {
146 + $all = array_values( array_unique( array_merge( self::DEFAULT_IGNORED_QUERY_PARAMS, self::TRACKING_PARAMS_1_3_6 ) ) );
147 + sort( $all );
148 + return $all;
149 + }
150 +
112 151 public const SLUG = 'cache';
113 152 public const TIER = self::TIER_FREE;
114 153 public const VERSION = '1.0.0';
115 154
@@ -128,9 +167,10 @@
128 167 public function ui_metadata(): array {
129 168 return array(
130 169 'label' => __( 'Page Cache', 'xspeed' ),
131 170 'icon' => 'Database',
132 - 'description' => __( 'Page caching for non-logged-in visitors.', 'xspeed' ),
171 + 'description' => __( 'Saves each page as a file and serves it to logged-out visitors.', 'xspeed' ),
172 + 'group' => 'cache',
133 173 );
134 174 }
135 175
136 176 /**
@@ -145,9 +185,9 @@
145 185 return array();
146 186 }
147 187
148 188 public function settings_schema(): array {
149 - return array(
189 + $schema = array(
150 190 'cache_expiry' => array(
151 191 'type' => 'int',
152 192 // Matches the wizard's Balanced preset, which is what a fresh
153 193 // install starts on — a shorter module default meant the two
@@ -154,11 +194,11 @@
154 194 // disagreed about what "default" means. (#284)
155 195 'default' => self::DEFAULT_EXPIRY_HOURS,
156 196 'min' => 1,
157 197 'max' => 720,
158 - 'label' => __( 'Cache Expiry (hours)', 'xspeed' ),
198 + 'label' => __( 'Cache expiry (hours)', 'xspeed' ),
159 199 'unit' => 'hours',
160 - 'description' => __( 'How long cached pages live before regenerating. 1 to 720 hours (30 days).', 'xspeed' ),
200 + 'description' => __( 'How long a cached page is kept before xSpeed builds it again. 1 to 720 hours (30 days).', 'xspeed' ),
161 201 ),
162 202 'excluded_urls' => array(
163 203 'type' => 'list',
164 204 // Comprehensive LiteSpeed / WP Rocket-parity default URL
@@ -187,9 +227,9 @@
187 227 '/wp-login',
188 228 ),
189 229 'item_type' => 'string',
190 230 'label' => __( 'Excluded URLs', 'xspeed' ),
191 - 'description' => __( 'One pattern per line. Plain text matches anywhere in the URL (e.g. /cart). Use glob for anchored matches (/cart/* matches /cart/items but not /foo/cart/bar; *.pdf matches PDFs). Prefix with ~ for a raw regex (e.g. ~wp-.*\.php).', 'xspeed' ),
231 + 'description' => __( 'Pages whose URL matches a line here are never cached. Plain text matches anywhere (/cart). A pattern with * matches from the start of the path: /cart/* matches /cart/items but not /shop/cart/items. ~ starts a regex.', 'xspeed' ),
192 232 ),
193 233 'excluded_cookies' => array(
194 234 'type' => 'list',
195 235 // Cookies that signal a logged-in / transactional visitor
@@ -196,17 +236,17 @@
196 236 // whose response must not be served from a shared cache.
197 237 // `~` prefix = raw regex (e.g. ~wordpress_[a-f0-9]+). (FBS-82181)
198 238 'default' => self::DEFAULT_EXCLUDED_COOKIES,
199 239 'item_type' => 'string',
200 - 'label' => __( 'Excluded Cookies', 'xspeed' ),
201 - 'description' => __( 'Skip cache for any visitor whose request carries a cookie whose NAME matches one of these patterns. Plain text = "contains"; glob (woocommerce_*) and ~regex (~wordpress_[a-f0-9]+) supported. One per line.', 'xspeed' ),
240 + 'label' => __( 'Excluded cookies', 'xspeed' ),
241 + 'description' => __( 'Visitors with a cookie whose name matches a line here always get a fresh page. One per line; * and ~regex work.', 'xspeed' ),
202 242 ),
203 243 'bypass_user_agents' => array(
204 244 'type' => 'list',
205 245 'default' => array(),
206 246 'item_type' => 'string',
207 - 'label' => __( 'Bypass User Agents', 'xspeed' ),
208 - 'description' => __( 'Substring match against the visitor User-Agent. Matched UAs bypass cache (useful for screenshot bots, internal previews, monitoring). Glob + ~regex supported. One per line.', 'xspeed' ),
247 + 'label' => __( 'Excluded browsers and bots', 'xspeed' ),
248 + 'description' => __( 'Visitors whose user agent contains a line here always get a fresh page. Useful for screenshot bots and uptime monitors.', 'xspeed' ),
209 249 ),
210 250 'ignored_query_params' => array(
211 251 'type' => 'list',
212 252 // Analytics / ad / session query keys stripped before the
@@ -213,24 +253,25 @@
213 253 // cache key is computed, so /post?utm_source=x and /post
214 254 // share one entry. `~` prefix = raw regex. (FBS-82181)
215 255 // Matched whole-name, so every entry here means the param
216 256 // it names and nothing that merely contains it.
217 - 'default' => self::DEFAULT_IGNORED_QUERY_PARAMS,
257 + 'default' => self::default_ignored_query_params(),
218 258 'item_type' => 'string',
219 - 'label' => __( 'Ignored Query Parameters', 'xspeed' ),
220 - '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 + session params. Each entry matches a whole param name — plain text is an exact name, and glob (utm_*) or ~regex are anchored too, so "ref" does not also match "preference". One per line.', 'xspeed' ),
259 + 'label' => __( 'Ignored query parameters', 'xspeed' ),
260 + 'advanced' => true,
261 + 'description' => __( 'URL parameters to ignore, so /post?utm_source=x gets the same cached page as /post. Each line matches a whole parameter name.', 'xspeed' ),
221 262 ),
222 263 'purge_on_upgrade' => array(
223 264 'type' => 'bool',
224 265 'default' => true,
225 - 'label' => __( 'Purge After Updates', 'xspeed' ),
226 - 'description' => __( 'Clear the page cache when a plugin, theme or WordPress core is updated. Cached HTML is produced by the code being replaced, so leaving it in place serves pre-update markup — and links to minified assets that no longer exist — until the cache expires. Translation updates are ignored, since a language pack changes no markup a cached page depends on. Updates to xSpeed itself always purge, regardless of this setting.', 'xspeed' ),
266 + 'label' => __( 'Clear cache after updates', 'xspeed' ),
267 + 'description' => __( 'Clear the page cache when a plugin, theme or WordPress is updated, so visitors never get old pages with broken asset links.', 'xspeed' ),
227 268 ),
228 269 'mobile_separate' => array(
229 270 'type' => 'bool',
230 271 'default' => false,
231 - 'label' => __( 'Separate Mobile Cache', 'xspeed' ),
232 - '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.', 'xspeed' ),
272 + 'label' => __( 'Separate mobile cache', 'xspeed' ),
273 + 'description' => __( 'Keep a separate cached copy for phones. Turn on only if your theme or plugins show different pages on mobile.', 'xspeed' ),
233 274 ),
234 275 'edge_provider' => array(
235 276 'type' => 'enum',
236 277 'default' => 'auto',
@@ -260,10 +301,11 @@
260 301 'incapsula' => 'Imperva / Incapsula (needs CDN configuration)',
261 302 'generic' => 'Something else',
262 303 'custom' => 'Custom headers',
263 304 ),
264 - 'label' => __( 'Cache In Front Of This Site', 'xspeed' ),
265 - 'description' => __( 'Ask a CDN or proxy in front of your site not to store pages xSpeed refused to cache. Leave it on Detect automatically unless you know what is in front of you; the marked providers ignore origin headers until you configure them to respect it.', 'xspeed' ),
305 + 'label' => __( 'CDN or proxy in front', 'xspeed' ),
306 + 'description' => __( 'Tells your CDN or proxy not to store pages xSpeed does not cache. Leave on Detect automatically unless you know your provider.', 'xspeed' ),
307 + 'advanced' => true,
266 308 'info_title' => __( 'Cache in front of this site', 'xspeed' ),
267 309 'info' => __( 'Naming your provider narrows the headers to the one it reads. Detect automatically works it out per request and otherwise sends a set every cache ignores unless it understands it, so it is safe not to know. Run "wp xspeed cache edge" to see what was detected and what gets sent.', 'xspeed' )
268 310 ),
269 311 'edge_custom_headers' => array(
@@ -269,15 +311,36 @@
269 311 'edge_custom_headers' => array(
270 312 'type' => 'list',
271 313 'default' => array(),
272 314 'item_type' => 'string',
273 - 'label' => __( 'Custom Edge Headers', 'xspeed' ),
315 + 'label' => __( 'Custom CDN headers', 'xspeed' ),
316 + 'advanced' => true,
274 317 'dependsOn' => array( 'field' => 'edge_provider', 'value' => 'custom' ),
275 - 'description' => __( 'One header per line, as Name: value — for example "Surrogate-Control: no-store". Lines starting with # are ignored.', 'xspeed' ),
318 + 'description' => __( 'One header per line, as Name: value, for example "Surrogate-Control: no-store". Lines starting with # are ignored.', 'xspeed' ),
276 319 'info_title' => __( 'Custom edge headers', 'xspeed' ),
277 320 'info' => __( 'These replace the headers xSpeed would have picked for your CDN. Two baselines are still added underneath: a Cache-Control, and "X-Accel-Expires: 0" for a page cache running in nginx on your own server. Name either one yourself and yours is used instead. Values containing $, % or a backslash are dropped — the same pairs go into nginx and Apache directives, where those cannot be escaped safely. Content-Length, Content-Encoding, Content-Type, Transfer-Encoding, Set-Cookie and Location are refused.', 'xspeed' )
278 321 ),
279 322 );
323 +
324 + // LiteSpeed-only opt-in (#509): meaningless on any other server, so
325 + // the field only exists in the schema where it can act — elsewhere the
326 + // stored value survives via preserved_keys(). Inserted right after
327 + // mobile_separate, its sibling static-fast-path trade-off.
328 + if ( \XSpeed\Server::LITESPEED === \XSpeed\Server::type() ) {
329 + $litespeed = array(
330 + 'litespeed_static_rewrite' => array(
331 + 'type' => 'bool',
332 + 'default' => false,
333 + 'label' => __( 'LiteSpeed static fast path', 'xspeed' ),
334 + 'advanced' => true,
335 + 'description' => __( 'LiteSpeed serves cached pages without running PHP, which is faster on slow hosts. These visits are not counted in the dashboard hit ratio.', 'xspeed' ),
336 + ),
337 + );
338 + $pos = (int) array_search( 'mobile_separate', array_keys( $schema ), true ) + 1;
339 + $schema = array_slice( $schema, 0, $pos, true ) + $litespeed + array_slice( $schema, $pos, null, true );
340 + }
341 +
342 + return $schema;
280 343 }
281 344
282 345 /**
283 346 * `mobile_separate_review` lives outside the schema: migration sets it
@@ -290,9 +353,18 @@
290 353 *
291 354 * @return string[]
292 355 */
293 356 public function preserved_keys(): array {
294 - return array( 'mobile_separate_review' );
357 + $keys = array( 'mobile_separate_review' );
358 + // On non-LiteSpeed servers the litespeed_static_rewrite field is not
359 + // in the schema (see settings_schema()), so a schema-driven save
360 + // would silently drop a value chosen while the site ran LiteSpeed.
361 + // Preserve it so moving LiteSpeed → other → LiteSpeed keeps the
362 + // user's choice. On LiteSpeed itself the schema owns the key.
363 + if ( \XSpeed\Server::LITESPEED !== \XSpeed\Server::type() ) {
364 + $keys[] = 'litespeed_static_rewrite';
365 + }
366 + return $keys;
295 367 }
296 368
297 369 /**
298 370 * Seed per-module option from the legacy xspeed_options blob if we