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/class-plugin.php +77 -6 1.2.4 → 1.3.7 View file →
@@ -14,9 +14,9 @@
14 14 /**
15 15 * Data-schema version for one-time migrations, independent of the
16 16 * plugin version header. Bump when adding a step to maybe_upgrade().
17 17 */
18 - public const DATA_VERSION = '1.1.6';
18 + public const DATA_VERSION = '1.3.6';
19 19
20 20 private static $instance = null;
21 21
22 22 /** @var Usage_Tracker|null */
@@ -76,8 +76,12 @@
76 76 // Per-post cache rules (Phase 3.4) — registers postmeta with
77 77 // REST + meta box on edit screens.
78 78 Cache_Meta_Box::boot();
79 79
80 + // Single-URL purge entry points — row actions, the edit-screen
81 + // button and the admin-post handler the admin-bar item also uses.
82 + Purge_Ui::boot();
83 +
80 84 // Phase 0 architecture — managers + Free modules. v1 services
81 85 // (Cache/Minifier/Gzip) are NOT yet Modules; they'll be refactored
82 86 // in a follow-up PR with parity tests.
83 87 Conflict_Registry::boot();
@@ -89,9 +93,23 @@
89 93 // Integrations that clear OTHER plugins' caches of rendered output
90 94 // (Elementor's element cache + generated CSS). Registers a listener
91 95 // only; nothing runs until Cache::purge_render_caches() asks.
92 96 Render_Caches::boot();
97 + // Forwarding needs no boot(): Cache::dispatch_purge_event() calls
98 + // Server_Caches::forward() directly so a throwing third-party listener
99 + // cannot skip it. Both built-in server-cache adapters live behind it —
100 + // LiteSpeed, and the nginx FastCGI cache reached through the host's
101 + // Nginx Helper install (Host_Page_Caches), which is why neither is
102 + // booted here. This boot() registers one listener only: the single
103 + // purge that runs after an import, which the nginx adapter's import
104 + // gate stands down in favour of.
105 + Server_Caches::boot();
93 106
107 + // Tell WP Statistics and Slimstat not to count the warmer, the
108 + // benchmark and the verifier as visitors. Record-time filters only;
109 + // see the class for why the tracking snippet itself is left alone.
110 + Self_Traffic::boot();
111 +
94 112 // Register Free modules via the same action xspeed-pro uses, so
95 113 // the bootstrap path is symmetric across tiers.
96 114 add_action( 'xspeed_register_modules', array( $this, 'register_free_modules' ) );
97 115
@@ -116,8 +134,38 @@
116 134 }
117 135 }
118 136
119 137 /**
138 + * Add the 1.3.6 tracking params to a saved ignored-params list.
139 + *
140 + * Only those names, and only when missing: anything else the site
141 + * removed stays removed. A site with no saved list already reads the new
142 + * defaults.
143 + */
144 + public static function add_new_ignored_params(): void {
145 + $option = Settings_Manager::OPTION_PREFIX . 'cache';
146 + $stored = get_option( $option, array() );
147 + if ( ! is_array( $stored ) || ! is_array( $stored['ignored_query_params'] ?? null ) ) {
148 + return;
149 + }
150 + $list = $stored['ignored_query_params'];
151 + // An empty list is a choice: every query string bypasses the cache.
152 + if ( empty( $list ) ) {
153 + return;
154 + }
155 + $missing = array_values( array_diff( \XSpeed\Modules\Cache\CacheModule::TRACKING_PARAMS_1_3_6, $list ) );
156 + if ( empty( $missing ) ) {
157 + return;
158 + }
159 + $stored['ignored_query_params'] = array_merge( $list, $missing );
160 + update_option( $option, $stored );
161 + // The drop-in keeps its own copy of the list next to the cache.
162 + if ( defined( 'XSPEED_CACHE_DIR' ) ) {
163 + Cache::sync_query_allowlist();
164 + }
165 + }
166 +
167 + /**
120 168 * Run version-gated data migrations exactly once per upgrade.
121 169 *
122 170 * Keyed on `xspeed_data_version` rather than the plugin version header
123 171 * so a migration can be added without forcing a release bump.
@@ -127,17 +175,25 @@
127 175 if ( version_compare( $current, self::DATA_VERSION, '>=' ) ) {
128 176 return;
129 177 }
130 178
179 + // Each step runs once, for the version that needs it. They used to
180 + // run on every bump, which would purge every site's static tree again
181 + // for a migration that has nothing to do with it.
182 +
131 183 // 1.1.2 — strip credential values recorded by earlier versions'
132 184 // settings change annotations (they're served by the trend endpoints).
133 - Activity_Log::redact_legacy_secrets();
185 + if ( version_compare( $current, '1.1.2', '<' ) ) {
186 + Activity_Log::redact_legacy_secrets();
187 + }
134 188
135 189 // 1.1.4 — earlier versions cached a failed loopback as "gzip is not
136 190 // active" for an hour, which showed up as a bogus server-config
137 191 // warning. Drop the stale answer so the fixed probe re-runs instead
138 192 // of the wrong verdict living on past the update (issue #18).
139 - delete_transient( 'xspeed_gzip_active' );
193 + if ( version_compare( $current, '1.1.4', '<' ) ) {
194 + delete_transient( 'xspeed_gzip_active' );
195 + }
140 196
141 197 // 1.1.6 — a `/?s=<term>` request used to write its results page into
142 198 // the static tree under the *searched-from* path, which for the usual
143 199 // query-form search is `/`. The web server then served that results
@@ -145,10 +201,19 @@
145 201 // Cache::store_static(), but an entry poisoned before the update
146 202 // outlives it: nothing purges on upgrade, and the static serve path
147 203 // never revalidates. Clear the tree once. The flat cache is keyed
148 204 // correctly and is deliberately left alone. (issue #191)
149 - Cache::purge_static_tree();
205 + if ( version_compare( $current, '1.1.6', '<' ) ) {
206 + Cache::purge_static_tree();
207 + }
150 208
209 + // 1.3.6 — new click and campaign IDs in the default ignored list.
210 + // A site that ever saved the Cache panel has its own copy of the list,
211 + // which the new defaults never reach, so add them to it.
212 + if ( version_compare( $current, '1.3.6', '<' ) ) {
213 + self::add_new_ignored_params();
214 + }
215 +
151 216 update_option( 'xspeed_data_version', self::DATA_VERSION, false );
152 217 }
153 218
154 219 /**
@@ -256,8 +321,9 @@
256 321 // cache-coverage features (404 / search / feed / REST / rules /
257 322 // maintenance) into one sidebar sub-item (FBS-83633).
258 323 Module_Registry::register( new \XSpeed\Modules\CacheCoverage\CacheCoverageModule() );
259 324 Module_Registry::register( new \XSpeed\Modules\Fonts\FontsModule() );
325 + Module_Registry::register( new \XSpeed\Modules\TurboRender\TurboRenderModule() );
260 326 Module_Registry::register( new \XSpeed\Modules\ResourceHints\ResourceHintsModule() );
261 327 // AI Privacy (GDPR off-switch) ships in Free even though every AI
262 328 // *feature* is Pro — privacy is a right, not a paid tier. FEATURES.md
263 329 // §AI row 6 mandates it. Without this registration the module was dead
@@ -274,8 +340,12 @@
274 340 // snapshot is onboarding/diagnostics, not a paid value-add, so every
275 341 // user gets it. The snapshot degrades gracefully without Pro (Pro
276 342 // version/license fields fall back to defaults via defined()/get_option).
277 343 Module_Registry::register( new \XSpeed\Modules\Support\SupportModule() );
344 + // The dashboard control for usage-analytics consent. Consent used to
345 + // be collectable only in the wizard and withdrawable nowhere, while
346 + // the wizard and readme both promised a dashboard switch. (#437)
347 + Module_Registry::register( new \XSpeed\Modules\Privacy\PrivacyModule() );
278 348 // MCP remote control (AI assistants) — Free. The plugin serves the
279 349 // MCP protocol at the site's own /xspeed/mcp URL; the only gate is
280 350 // the per-site connection token an admin mints via Connect. No
281 351 // license, no hosted infra. See IMPLEMENTATION.md §17.
@@ -290,10 +360,11 @@
290 360 public function start_plugin_tracking(): void {
291 361 $this->usage_tracker = Usage_Tracker::get_instance(
292 362 XSPEED_FILE,
293 363 array(
294 - 'opt_in' => true,
295 - 'item_id' => defined( 'XSPEED_INSIGHTS_ITEM_ID' ) ? XSPEED_INSIGHTS_ITEM_ID : false,
364 + 'opt_in' => true,
365 + 'email_marketing' => true,
366 + 'item_id' => defined( 'XSPEED_INSIGHTS_ITEM_ID' ) ? XSPEED_INSIGHTS_ITEM_ID : false,
296 367 )
297 368 );
298 369 $this->usage_tracker->init();
299 370 }