PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.6
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.6
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 1.1.5 All 32 releases
← All changes | includes/class-plugin.php +457 -8 1.0.0 → 1.3.6 View file →
@@ -10,10 +10,19 @@
10 10 defined( 'ABSPATH' ) || exit;
11 11
12 12 class Plugin {
13 13
14 + /**
15 + * Data-schema version for one-time migrations, independent of the
16 + * plugin version header. Bump when adding a step to maybe_upgrade().
17 + */
18 + public const DATA_VERSION = '1.1.6';
19 +
14 20 private static $instance = null;
15 21
22 + /** @var Usage_Tracker|null */
23 + private $usage_tracker = null;
24 +
16 25 public static function instance() {
17 26 if ( null === self::$instance ) {
18 27 self::$instance = new self();
19 28 }
@@ -20,32 +29,472 @@
20 29 return self::$instance;
21 30 }
22 31
23 32 public function init() {
33 + // v1 services that are NOT yet wrapped as Modules (Admin, Rest_Api,
34 + // Cache, Onboarding) are instantiated directly. Minifier is now
35 + // instantiated by MinifyModule::boot(); Gzip is purely static.
24 36 new Admin();
25 37 new Rest_Api();
26 38 new Cache();
27 - new Minifier();
39 + new Rest_Cache();
40 + new Onboarding();
41 +
42 + // Optional deactivation feedback survey on the Plugins screen. Admin
43 + // context only (its hooks are admin_enqueue_scripts / admin_footer /
44 + // wp_ajax). Sends nothing unless the user clicks "Submit & Deactivate".
45 + if ( is_admin() ) {
46 + new Deactivation_Feedback();
47 + }
48 +
49 + // Opt-in usage analytics. Instantiating + init() only registers the
50 + // cron callback; NOTHING is collected or sent until the admin opts in
51 + // from the setup wizard (Onboarding wires the consent toggle to
52 + // Usage_Tracker::opt_in()). See class-usage-tracker.php privacy contract.
53 + $this->start_plugin_tracking();
54 +
55 + // Auto-heal the cache drop-in + WP_CACHE constant when state
56 + // drifts. Scoped to admin_init only: filesystem writes belong in
57 + // an authenticated admin context, never on anonymous front-end
58 + // requests. The user already opted in (cache_enabled=true) —
59 + // this is a consistency check, not a new install path. First
60 + // admin page load after a plugin upgrade restores the state;
61 + // front-end then serves from cache on the next request.
62 + add_action( 'admin_init', array( Cache::class, 'auto_heal' ) );
63 + // Cheap: returns immediately unless the stored schema version is
64 + // behind. Covers updates and multisite, where activate() never runs.
65 + add_action( 'admin_init', array( Score_Store::class, 'maybe_install' ) );
66 +
67 + // Secondary net: restore as soon as an update completes, for the
68 + // cases where activate() does not re-run (bulk updates, auto-updates,
69 + // some host updaters). Best-effort by nature — this callback is only
70 + // registered when we were loaded in the request performing the
71 + // update, which is not guaranteed while WE are the plugin being
72 + // replaced. The restore in activate() is the primary guarantee;
73 + // auto_heal() on admin_init remains the backstop.
74 + add_action( 'upgrader_process_complete', array( $this, 'maybe_restore_after_update' ), 10, 2 );
75 +
76 + // Per-post cache rules (Phase 3.4) — registers postmeta with
77 + // REST + meta box on edit screens.
78 + Cache_Meta_Box::boot();
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 +
84 + // Phase 0 architecture — managers + Free modules. v1 services
85 + // (Cache/Minifier/Gzip) are NOT yet Modules; they'll be refactored
86 + // in a follow-up PR with parity tests.
87 + Conflict_Registry::boot();
88 +
89 + // Read-only page-cache ownership evidence, behind the same
90 + // activation/deactivation invalidation as the conflict matrix.
91 + Page_Cache_Detector::boot();
92 +
93 + // Integrations that clear OTHER plugins' caches of rendered output
94 + // (Elementor's element cache + generated CSS). Registers a listener
95 + // only; nothing runs until Cache::purge_render_caches() asks.
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();
106 +
107 + // Register Free modules via the same action xspeed-pro uses, so
108 + // the bootstrap path is symmetric across tiers.
109 + add_action( 'xspeed_register_modules', array( $this, 'register_free_modules' ) );
110 +
111 + // Fire the registration action + boot the registry at a LATER
112 + // plugins_loaded priority so add-on plugins (xspeed-pro, in
113 + // alphabetical order so it runs at default priority 10 AFTER
114 + // us, but any add-on loaded at plugins_loaded(< 20)) have a
115 + // chance to register their `xspeed_register_modules` callback
116 + // before we fire the action.
117 + //
118 + // Bug history: previously this fired inline from init() at
119 + // priority 10. xspeed-pro's plugins_loaded(15) hook then added
120 + // its register_pro_modules callback AFTER the action had
121 + // already fired — Pro modules never appeared in the registry.
122 + // Caught by the ProStatus sentinel module's integration test.
123 + add_action( 'plugins_loaded', array( $this, 'fire_module_lifecycle' ), 20 );
124 +
125 + // One-time data migrations keyed on the stored version. Runs in
126 + // admin only — nothing here needs to touch a front-end request.
127 + if ( is_admin() ) {
128 + add_action( 'plugins_loaded', array( $this, 'maybe_upgrade' ), 21 );
129 + }
28 130 }
29 131
132 + /**
133 + * Run version-gated data migrations exactly once per upgrade.
134 + *
135 + * Keyed on `xspeed_data_version` rather than the plugin version header
136 + * so a migration can be added without forcing a release bump.
137 + */
138 + public function maybe_upgrade(): void {
139 + $current = (string) get_option( 'xspeed_data_version', '0' );
140 + if ( version_compare( $current, self::DATA_VERSION, '>=' ) ) {
141 + return;
142 + }
143 +
144 + // 1.1.2 — strip credential values recorded by earlier versions'
145 + // settings change annotations (they're served by the trend endpoints).
146 + Activity_Log::redact_legacy_secrets();
147 +
148 + // 1.1.4 — earlier versions cached a failed loopback as "gzip is not
149 + // active" for an hour, which showed up as a bogus server-config
150 + // warning. Drop the stale answer so the fixed probe re-runs instead
151 + // of the wrong verdict living on past the update (issue #18).
152 + delete_transient( 'xspeed_gzip_active' );
153 +
154 + // 1.1.6 — a `/?s=<term>` request used to write its results page into
155 + // the static tree under the *searched-from* path, which for the usual
156 + // query-form search is `/`. The web server then served that results
157 + // page as the homepage to every visitor. The write is fixed in
158 + // Cache::store_static(), but an entry poisoned before the update
159 + // outlives it: nothing purges on upgrade, and the static serve path
160 + // never revalidates. Clear the tree once. The flat cache is keyed
161 + // correctly and is deliberately left alone. (issue #191)
162 + Cache::purge_static_tree();
163 +
164 + update_option( 'xspeed_data_version', self::DATA_VERSION, false );
165 + }
166 +
167 + /**
168 + * Phase 2 of plugin init: fire the registration action (collecting
169 + * Free + Pro + any third-party modules hooked into
170 + * `xspeed_register_modules`) and boot the registry.
171 + *
172 + * Runs at plugins_loaded(20) so every add-on that hooks at any
173 + * priority < 20 has time to register first.
174 + */
175 + public function fire_module_lifecycle(): void {
176 + $this->ensure_modules_registered();
177 +
178 + Module_Registry::boot_all();
179 + }
180 +
181 + /**
182 + * Fire `xspeed_register_modules` if this request has not yet, hooking
183 + * Free's own registration first when init() never got the chance.
184 + *
185 + * The activation request is the case that matters. activate_plugin()
186 + * includes the plugin file long after `plugins_loaded` has fired, so the
187 + * `plugins_loaded` callback init() would have added never runs, and
188 + * neither does the add_action() inside it that puts register_free_modules
189 + * on the action. Firing the action from activate() then registered
190 + * nothing: Settings::conflict_safe_profile() composed from an empty
191 + * registry, and a site with WP Super Cache came up with lazy-load,
192 + * resource hints, font swapping and preloading switched on — only the
193 + * four settings the registry-independent fallback names were held down
194 + * (PR #295 review). Module_Registry::activate_all() has been a no-op on
195 + * the same request for the same reason.
196 + *
197 + * On the activation request that means Free only: an add-on cannot have
198 + * hooked yet, because xspeed-pro bails when Free's classes are absent and
199 + * only hooks the action (at priority 20, from plugins_loaded(15)) once
200 + * Free is active. On an ordinary request fire_module_lifecycle() reaches
201 + * this at plugins_loaded(20) with every add-on already hooked. Do not
202 + * call this from anything that can run in between: the action fires
203 + * once, and an add-on that has not hooked yet stays unregistered for the
204 + * whole request.
205 + * did_action() keeps the action to one firing per request, so an
206 + * activation that ran first does not make plugins_loaded(20) register
207 + * every module a second time.
208 + */
209 + public function ensure_modules_registered(): void {
210 + if ( did_action( 'xspeed_register_modules' ) ) {
211 + return;
212 + }
213 +
214 + /*
215 + * register_free_modules() does an unconditional `new` on every module
216 + * class. On an ordinary request xspeed.php's integrity check refuses
217 + * to boot before that can fatal and explains itself in an admin
218 + * notice; the activation hook is registered outside that check, so
219 + * an install missing a module file (truncated zip, a security
220 + * plugin's quarantine, a half-applied update) would fatal here with
221 + * no notice and no active plugin. Same answer as boot: do nothing.
222 + */
223 + if ( function_exists( 'xspeed_missing_core_classes' ) && ! empty( xspeed_missing_core_classes() ) ) {
224 + return;
225 + }
226 +
227 + if ( ! has_action( 'xspeed_register_modules', array( $this, 'register_free_modules' ) ) ) {
228 + add_action( 'xspeed_register_modules', array( $this, 'register_free_modules' ) );
229 + }
230 +
231 + /**
232 + * Action: xspeed_register_modules
233 + *
234 + * Free modules register at priority 10; xspeed-pro at priority
235 + * 20; site code can hook in between to inject custom modules.
236 + * Fires exactly once per request.
237 + */
238 + do_action( 'xspeed_register_modules' );
239 + }
240 +
241 + /**
242 + * Register the Free Modules shipped in this plugin. Add new module
243 + * registrations here. Pro plugin hooks the same action separately.
244 + */
245 + public function register_free_modules(): void {
246 + Module_Registry::register( new \XSpeed\Modules\Cache\CacheModule() );
247 + Module_Registry::register( new \XSpeed\Modules\Health\HealthModule() );
248 + // Settings — owns no settings itself; it's the CLI/MCP surface over
249 + // Settings_Manager. Registering it is what makes `xspeed settings`
250 + // exist, which is what keeps the curated get_settings/update_settings
251 + // tools in the MCP catalog. (#149/#153)
252 + Module_Registry::register( new \XSpeed\Modules\Settings\SettingsModule() );
253 + // External performance scores (PSI / GTmetrix) — Free, off by
254 + // default. Rendered inside the Health host page's PageSpeed tab, so
255 + // it has no sidebar row of its own.
256 + Module_Registry::register( new \XSpeed\Modules\Score\ScoreModule() );
257 + Module_Registry::register( new \XSpeed\Modules\Preloader\PreloaderModule() );
258 + Module_Registry::register( new \XSpeed\Modules\Heartbeat\HeartbeatModule() );
259 + Module_Registry::register( new \XSpeed\Modules\Minify\MinifyModule() );
260 + Module_Registry::register( new \XSpeed\Modules\Gzip\GzipModule() );
261 + Module_Registry::register( new \XSpeed\Modules\Lazy\LazyModule() );
262 + Module_Registry::register( new \XSpeed\Modules\Bloat\BloatModule() );
263 + Module_Registry::register( new \XSpeed\Modules\Database\DatabaseModule() );
264 + Module_Registry::register( new \XSpeed\Modules\Cdn\CdnModule() );
265 + Module_Registry::register( new \XSpeed\Modules\Cloudflare\CloudflareModule() );
266 + Module_Registry::register( new \XSpeed\Modules\ObjectCache\ObjectCacheModule() );
267 + Module_Registry::register( new \XSpeed\Modules\BrowserCache\BrowserCacheModule() );
268 + // Advanced Cache — a Free container row that gathers the Pro
269 + // cache-coverage features (404 / search / feed / REST / rules /
270 + // maintenance) into one sidebar sub-item (FBS-83633).
271 + Module_Registry::register( new \XSpeed\Modules\CacheCoverage\CacheCoverageModule() );
272 + Module_Registry::register( new \XSpeed\Modules\Fonts\FontsModule() );
273 + Module_Registry::register( new \XSpeed\Modules\TurboRender\TurboRenderModule() );
274 + Module_Registry::register( new \XSpeed\Modules\ResourceHints\ResourceHintsModule() );
275 + // AI Privacy (GDPR off-switch) ships in Free even though every AI
276 + // *feature* is Pro — privacy is a right, not a paid tier. FEATURES.md
277 + // §AI row 6 mandates it. Without this registration the module was dead
278 + // code: no REST/settings surface, the promised off-switch unreachable
279 + // (FBS-83633 Bug 1). It carries its own cli_commands() so it satisfies
280 + // the CLI/MCP coverage guard once registered.
281 + Module_Registry::register( new \XSpeed\Modules\AIPrivacy\AIPrivacyModule() );
282 + // Migration moved Pro → Free: it's an acquisition/onboarding feature
283 + // (detect a competing caching plugin, import its settings, switch over),
284 + // so it must work without a Pro license. Agency-scale extras (profiles,
285 + // bulk multisite, host presets) remain Pro.
286 + Module_Registry::register( new \XSpeed\Modules\Migration\MigrationModule() );
287 + // Help & Support moved Pro → Free: a ticket link + read-only system
288 + // snapshot is onboarding/diagnostics, not a paid value-add, so every
289 + // user gets it. The snapshot degrades gracefully without Pro (Pro
290 + // version/license fields fall back to defaults via defined()/get_option).
291 + Module_Registry::register( new \XSpeed\Modules\Support\SupportModule() );
292 + // The dashboard control for usage-analytics consent. Consent used to
293 + // be collectable only in the wizard and withdrawable nowhere, while
294 + // the wizard and readme both promised a dashboard switch. (#437)
295 + Module_Registry::register( new \XSpeed\Modules\Privacy\PrivacyModule() );
296 + // MCP remote control (AI assistants) — Free. The plugin serves the
297 + // MCP protocol at the site's own /xspeed/mcp URL; the only gate is
298 + // the per-site connection token an admin mints via Connect. No
299 + // license, no hosted infra. See IMPLEMENTATION.md §17.
300 + Module_Registry::register( new \XSpeed\Modules\Mcp\McpModule() );
301 + }
302 +
303 + /**
304 + * Boot the opt-in usage tracker. Registers the cron sender only; the send
305 + * itself is consent-gated inside Usage_Tracker. The instance is held so the
306 + * onboarding REST handler can flip consent via usage_tracker()->opt_in().
307 + */
308 + public function start_plugin_tracking(): void {
309 + $this->usage_tracker = Usage_Tracker::get_instance(
310 + XSPEED_FILE,
311 + array(
312 + 'opt_in' => true,
313 + 'email_marketing' => true,
314 + 'item_id' => defined( 'XSPEED_INSIGHTS_ITEM_ID' ) ? XSPEED_INSIGHTS_ITEM_ID : false,
315 + )
316 + );
317 + $this->usage_tracker->init();
318 + }
319 +
320 + /**
321 + * The shared Usage_Tracker singleton (or null if tracking wasn't booted,
322 + * e.g. on the activation hook before init() runs).
323 + */
324 + public function usage_tracker(): ?Usage_Tracker {
325 + return $this->usage_tracker;
326 + }
327 +
30 328 public static function activate() {
31 - Settings::set_defaults();
329 + // Nothing below can see a module the registry does not hold — the
330 + // conflict-safe profile Settings::set_defaults() may pick is composed
331 + // from it, and activate_all() walks it. See ensure_modules_registered()
332 + // for why the registry is empty on this request without this call.
333 + self::instance()->ensure_modules_registered();
32 334
335 + $profile = Settings::set_defaults();
336 +
337 + // Score history table. Also called on admin_init (see init()) because
338 + // activation does not fire for a site added to a multisite network
339 + // later, nor after an update that ships a new schema version.
340 + Score_Store::maybe_install();
341 +
33 342 if ( ! file_exists( XSPEED_CACHE_DIR ) ) {
34 343 wp_mkdir_p( XSPEED_CACHE_DIR );
35 344 }
36 345 Cache::write_silence( XSPEED_CACHE_DIR );
37 346
38 - // Drop-in installation (advanced-cache.php) and the WP_CACHE constant
39 - // edit happen only when the user explicitly enables caching from the
40 - // admin UI — never on activation. See Cache::toggle() and
41 - // Rest_Api::toggle_cache(). This is a WordPress.org review requirement.
347 + /*
348 + * One exception to "caching is only ever enabled from the admin UI":
349 + * a fresh install another plugin performed on the user's behalf.
350 + *
351 + * That plugin asked the user for site performance and installed us to
352 + * provide it; making them go and find a second switch afterwards is
353 + * a step nobody wants. It is also what the copy-vendored Setup::finish()
354 + * did, so hosts migrating off it keep the behaviour they have.
355 + *
356 + * PROFILE_HOST_PAGE_CACHE is the whole condition, and it means three
357 + * things at once: the install was genuinely fresh, nothing else owns
358 + * the page cache, and another plugin claimed the install. Every other
359 + * fresh install — including a user's own on a clear site — waits for
360 + * the wizard. Every OTHER feature is off in this profile; the cache is
361 + * the one thing a host may assume, because it is what it installed us
362 + * for. And toggle() runs its own ownership transaction, so a
363 + * competitor appearing between the two checks loses the race rather
364 + * than being overwritten.
365 + */
366 + if ( Settings::PROFILE_HOST_PAGE_CACHE === $profile ) {
367 + $state = Cache::toggle( true );
368 + if ( ! empty( $state['blocked'] ) ) {
369 + /*
370 + * Refused after all — a competitor that appeared between the
371 + * profile decision and the write, or a drop-in we could not
372 + * install. The settings are identical either way (everything
373 + * off), so the honest record is the one that does not claim a
374 + * cache: conflict-safe is what the site actually got.
375 + */
376 + $profile = Settings::PROFILE_CONFLICT_SAFE;
377 + update_option( 'xspeed_install_profile', $profile, false );
378 + }
379 + }
380 +
381 + /*
382 + * Read the claim BEFORE spending it. consume_installed_by() records
383 + * the installer only for a FRESH install — on a re-activation over
384 + * settings that are already there it deletes the arming option and
385 + * keeps nothing — so asking again afterwards answered "the user did
386 + * it" about an install a host had just claimed, and the wizard opened
387 + * over the host's own flow. The claim decides who to tell and whether
388 + * to open the wizard; whether it is worth RECORDING is a separate
389 + * question, and only the recording depends on the install being fresh.
390 + */
391 + $installed_by = Settings::installed_by();
392 +
393 + // Spend the arming option now the profile is settled. It changes what
394 + // activation does, so it may not survive into the next one.
395 + Settings::consume_installed_by( $profile );
396 +
397 + // Caching is otherwise only ENABLED from the admin UI — see
398 + // Cache::toggle() and Rest_Api::toggle_cache(). A fresh install
399 + // therefore gets no drop-in and no wp-config.php edit here:
400 + // cache_enabled is unset, so the call below is a no-op.
401 + //
402 + // It is NOT a no-op during an upgrade. WordPress runs an update as
403 + // deactivate → wipe files → install → activate, which deletes
404 + // advanced-cache.php while cache_enabled stays true. Restoring it
405 + // here closes the window in which the site silently serves uncached
406 + // (auto_heal() alone only fires on the next wp-admin page load).
407 + Cache::restore_dropin_if_enabled();
408 +
409 + /*
410 + * First-run wizard: flag a one-time redirect for the activating user.
411 + * Suppressed for bulk activations / already-completed sites in
412 + * Onboarding::maybe_redirect() — and here for an install another plugin
413 + * performed, which has an onboarding flow of its own. The install runs
414 + * over AJAX, so our redirect would fire on that admin's NEXT page load
415 + * and pull them out of the middle of the host's wizard; finishing ours
416 + * would then overwrite the deliberately all-off profile they never
417 + * asked to change.
418 + */
419 + if ( '' === $installed_by ) {
420 + Onboarding::flag_redirect();
421 + }
422 +
423 + // Propagate activation to every registered Module — registered by
424 + // ensure_modules_registered() at the top, not by plugins_loaded,
425 + // which fired before this file was even included.
426 + Module_Registry::activate_all();
427 +
428 + /**
429 + * Fires at the end of activation, once the settings profile is decided.
430 + *
431 + * The other half of the host-plugin contract: a plugin that installed
432 + * xSpeed for the user writes `xspeed_installed_by` before activating
433 + * and listens here to find out how it came up. It carries no return
434 + * value and nothing branches on it — a host that ignores it changes
435 + * nothing about the install.
436 + *
437 + * @param string $installed_by Host slug, or '' when the user did it.
438 + * @param string $profile Settings::PROFILE_* — which profile a fresh
439 + * install came up with, '' if not fresh.
440 + */
441 + do_action( 'xspeed_activated', $installed_by, $profile );
42 442 }
43 443
444 + /**
445 + * Restore the cache drop-in right after THIS plugin is updated.
446 + *
447 + * Bound to upgrader_process_complete. Bulk updates, auto-updates and
448 + * host-level updaters finish without re-running activate(), so this is
449 + * the only hook that repairs the drop-in before the next wp-admin page
450 + * load. Narrow by design: bails unless the completed action was a
451 + * plugin update whose payload actually includes xspeed.
452 + *
453 + * @param \WP_Upgrader $upgrader Upgrader instance (unused).
454 + * @param array $hook_extra Contextual data about the update.
455 + * @return void
456 + */
457 + public function maybe_restore_after_update( $upgrader, $hook_extra ) {
458 + unset( $upgrader );
459 +
460 + if ( ! is_array( $hook_extra ) ) {
461 + return;
462 + }
463 + if ( ! isset( $hook_extra['type'], $hook_extra['action'] ) ) {
464 + return;
465 + }
466 + if ( 'plugin' !== $hook_extra['type'] || 'update' !== $hook_extra['action'] ) {
467 + return;
468 + }
469 +
470 + // Single update uses 'plugin'; bulk uses 'plugins'.
471 + $updated = array();
472 + if ( isset( $hook_extra['plugins'] ) && is_array( $hook_extra['plugins'] ) ) {
473 + $updated = $hook_extra['plugins'];
474 + } elseif ( isset( $hook_extra['plugin'] ) && is_string( $hook_extra['plugin'] ) ) {
475 + $updated = array( $hook_extra['plugin'] );
476 + }
477 +
478 + $ours = plugin_basename( XSPEED_FILE );
479 + if ( ! in_array( $ours, $updated, true ) ) {
480 + return;
481 + }
482 +
483 + Cache::restore_dropin_if_enabled();
484 + }
485 +
44 486 public static function deactivate() {
45 - Cache::remove_dropin();
46 - Cache::set_wp_cache_constant( false );
487 + // Drop-in + WP_CACHE constant are NOT touched here. WordPress
488 + // upgrades run as deactivate → wipe files → install → activate,
489 + // so removing those artifacts on every deactivate would silently
490 + // disable caching after each plugin update. uninstall.php
491 + // handles full teardown when the user actually removes the
492 + // plugin; auto_heal() restores state on the next admin_init if
493 + // the drop-in or WP_CACHE went missing for any other reason.
47 494 Cache::purge_all();
48 495 Minifier::purge_minified();
49 496 Gzip::apply( false );
497 +
498 + Module_Registry::deactivate_all();
50 499 }
51 500 }