PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.1
1.4.1 1.4.0 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 All 35 releases
← All changes | includes/class-plugin.php +122 -6 1.3.6 → 1.4.1 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 */
@@ -103,8 +103,20 @@
103 103 // purge that runs after an import, which the nginx adapter's import
104 104 // gate stands down in favour of.
105 105 Server_Caches::boot();
106 106
107 + // Remembers the terms a post had before a save, so a narrow purge
108 + // can clear the category it left as well as the one it joined.
109 + Affected_Pages::boot();
110 + // Records which pages run a post list of their own (a page builder
111 + // grid, a related-posts block), so a narrow purge clears them too.
112 + Listing_Pages::boot();
113 +
114 + // Tell WP Statistics and Slimstat not to count the warmer, the
115 + // benchmark and the verifier as visitors. Record-time filters only;
116 + // see the class for why the tracking snippet itself is left alone.
117 + Self_Traffic::boot();
118 +
107 119 // Register Free modules via the same action xspeed-pro uses, so
108 120 // the bootstrap path is symmetric across tiers.
109 121 add_action( 'xspeed_register_modules', array( $this, 'register_free_modules' ) );
110 122
@@ -129,8 +141,38 @@
129 141 }
130 142 }
131 143
132 144 /**
145 + * Add the 1.3.6 tracking params to a saved ignored-params list.
146 + *
147 + * Only those names, and only when missing: anything else the site
148 + * removed stays removed. A site with no saved list already reads the new
149 + * defaults.
150 + */
151 + public static function add_new_ignored_params(): void {
152 + $option = Settings_Manager::OPTION_PREFIX . 'cache';
153 + $stored = get_option( $option, array() );
154 + if ( ! is_array( $stored ) || ! is_array( $stored['ignored_query_params'] ?? null ) ) {
155 + return;
156 + }
157 + $list = $stored['ignored_query_params'];
158 + // An empty list is a choice: every query string bypasses the cache.
159 + if ( empty( $list ) ) {
160 + return;
161 + }
162 + $missing = array_values( array_diff( \XSpeed\Modules\Cache\CacheModule::TRACKING_PARAMS_1_3_6, $list ) );
163 + if ( empty( $missing ) ) {
164 + return;
165 + }
166 + $stored['ignored_query_params'] = array_merge( $list, $missing );
167 + update_option( $option, $stored );
168 + // The drop-in keeps its own copy of the list next to the cache.
169 + if ( defined( 'XSPEED_CACHE_DIR' ) ) {
170 + Cache::sync_query_allowlist();
171 + }
172 + }
173 +
174 + /**
133 175 * Run version-gated data migrations exactly once per upgrade.
134 176 *
135 177 * Keyed on `xspeed_data_version` rather than the plugin version header
136 178 * so a migration can be added without forcing a release bump.
@@ -140,17 +182,25 @@
140 182 if ( version_compare( $current, self::DATA_VERSION, '>=' ) ) {
141 183 return;
142 184 }
143 185
186 + // Each step runs once, for the version that needs it. They used to
187 + // run on every bump, which would purge every site's static tree again
188 + // for a migration that has nothing to do with it.
189 +
144 190 // 1.1.2 — strip credential values recorded by earlier versions'
145 191 // settings change annotations (they're served by the trend endpoints).
146 - Activity_Log::redact_legacy_secrets();
192 + if ( version_compare( $current, '1.1.2', '<' ) ) {
193 + Activity_Log::redact_legacy_secrets();
194 + }
147 195
148 196 // 1.1.4 — earlier versions cached a failed loopback as "gzip is not
149 197 // active" for an hour, which showed up as a bogus server-config
150 198 // warning. Drop the stale answer so the fixed probe re-runs instead
151 199 // of the wrong verdict living on past the update (issue #18).
152 - delete_transient( 'xspeed_gzip_active' );
200 + if ( version_compare( $current, '1.1.4', '<' ) ) {
201 + delete_transient( 'xspeed_gzip_active' );
202 + }
153 203
154 204 // 1.1.6 — a `/?s=<term>` request used to write its results page into
155 205 // the static tree under the *searched-from* path, which for the usual
156 206 // query-form search is `/`. The web server then served that results
@@ -158,10 +208,50 @@
158 208 // Cache::store_static(), but an entry poisoned before the update
159 209 // outlives it: nothing purges on upgrade, and the static serve path
160 210 // never revalidates. Clear the tree once. The flat cache is keyed
161 211 // correctly and is deliberately left alone. (issue #191)
162 - Cache::purge_static_tree();
212 + if ( version_compare( $current, '1.1.6', '<' ) ) {
213 + $removed = Cache::purge_static_tree();
163 214
215 + /*
216 + * And tell anything caching in front of us, because the poisoned
217 + * entry is exactly the kind that reaches an edge: the web server
218 + * served it straight off disk as the homepage, so a CDN in front had
219 + * every opportunity to store it. Deleting our copy leaves that one
220 + * untouched, and the edge's lifetime is the longer of the two.
221 + *
222 + * Announced on `init` rather than here. This runs at
223 + * `plugins_loaded` 21, before an add-on has wired its purge
224 + * listeners, so firing it inline would announce to an empty room.
225 + * Priority 99 puts it after any reasonable `init` registration.
226 + *
227 + * Once per site, on the upgrade that clears the tree — never again,
228 + * since the data version is written immediately below.
229 + *
230 + * Through `Cache::announce_purge()` rather than a bare `do_action`,
231 + * because the purge-event contract (#348) is more than the payload:
232 + * it forwards to the server caches by direct call before the public
233 + * action runs, and it isolates listeners so one throwing add-on
234 + * cannot fatal the first admin request after an update. A raw
235 + * publish here would skip both and hand listeners the pre-contract
236 + * payload shape.
237 + */
238 + add_action(
239 + 'init',
240 + static function () use ( $removed ) {
241 + Cache::announce_purge( 'static cache repaired on upgrade', $removed );
242 + },
243 + 99
244 + );
245 + }
246 +
247 + // 1.3.6 — new click and campaign IDs in the default ignored list.
248 + // A site that ever saved the Cache panel has its own copy of the list,
249 + // which the new defaults never reach, so add them to it.
250 + if ( version_compare( $current, '1.3.6', '<' ) ) {
251 + self::add_new_ignored_params();
252 + }
253 +
164 254 update_option( 'xspeed_data_version', self::DATA_VERSION, false );
165 255 }
166 256
167 257 /**
@@ -438,8 +528,22 @@
438 528 * @param string $profile Settings::PROFILE_* — which profile a fresh
439 529 * install came up with, '' if not fresh.
440 530 */
441 531 do_action( 'xspeed_activated', $installed_by, $profile );
532 +
533 + /*
534 + * Announce the change to anything caching in front of us.
535 + *
536 + * Activation writes the default settings and activates modules, so
537 + * every URL on the site starts returning different HTML. A CDN
538 + * holding renders from before goes on serving them for their whole
539 + * lifetime, and nothing told it.
540 + *
541 + * Cheap on a fresh install — there is nothing to sweep — and the
542 + * case it exists for is reactivation on a site that has been running
543 + * for months behind an edge.
544 + */
545 + Cache::purge_all( 'plugin activated' );
442 546 }
443 547
444 548 /**
445 549 * Restore the cache drop-in right after THIS plugin is updated.
@@ -482,9 +586,14 @@
482 586
483 587 Cache::restore_dropin_if_enabled();
484 588 }
485 589
486 - public static function deactivate() {
590 + /**
591 + * @param bool $network_wide Whether a network admin deactivated the
592 + * plugin for every site (WordPress passes this
593 + * to deactivation hooks).
594 + */
595 + public static function deactivate( $network_wide = false ) {
487 596 // Drop-in + WP_CACHE constant are NOT touched here. WordPress
488 597 // upgrades run as deactivate → wipe files → install → activate,
489 598 // so removing those artifacts on every deactivate would silently
490 599 // disable caching after each plugin update. uninstall.php
@@ -491,9 +600,16 @@
491 600 // handles full teardown when the user actually removes the
492 601 // plugin; auto_heal() restores state on the next admin_init if
493 602 // the drop-in or WP_CACHE went missing for any other reason.
494 603 Cache::purge_all();
495 - Minifier::purge_minified();
604 + // purge_all() is site-scoped and no longer touches min/, so clear it
605 + // here. On a network where only this site is deactivating, the other
606 + // sites still link those files: drop this site's manifests only.
607 + if ( is_multisite() && ! $network_wide ) {
608 + Minifier::purge_manifests( Asset_Manifest::blog_id() );
609 + } else {
610 + Minifier::purge_minified();
611 + }
496 612 Gzip::apply( false );
497 613
498 614 Module_Registry::deactivate_all();
499 615 }