PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.1.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.1.1
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 1.1.6 1.1.7 1.1.8 All 29 releases
xspeed / includes / class-plugin.php

class-plugin.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.1.1, at includes/class-plugin.php

246 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Main plugin bootstrap.
4 *
5 * @package XSpeed
6 */
7
8 namespace XSpeed;
9
10 defined( 'ABSPATH' ) || exit;
11
12 class Plugin {
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.2';
19
20 private static $instance = null;
21
22 /** @var Usage_Tracker|null */
23 private $usage_tracker = null;
24
25 public static function instance() {
26 if ( null === self::$instance ) {
27 self::$instance = new self();
28 }
29 return self::$instance;
30 }
31
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.
36 new Admin();
37 new Rest_Api();
38 new Cache();
39 new Rest_Cache();
40 new Onboarding();
41
42 // Opt-in usage analytics. Instantiating + init() only registers the
43 // cron callback; NOTHING is collected or sent until the admin opts in
44 // from the setup wizard (Onboarding wires the consent toggle to
45 // Usage_Tracker::opt_in()). See class-usage-tracker.php privacy contract.
46 $this->start_plugin_tracking();
47
48 // Auto-heal the cache drop-in + WP_CACHE constant when state
49 // drifts. Scoped to admin_init only: filesystem writes belong in
50 // an authenticated admin context, never on anonymous front-end
51 // requests. The user already opted in (cache_enabled=true) —
52 // this is a consistency check, not a new install path. First
53 // admin page load after a plugin upgrade restores the state;
54 // front-end then serves from cache on the next request.
55 add_action( 'admin_init', array( Cache::class, 'auto_heal' ) );
56
57 // Per-post cache rules (Phase 3.4) — registers postmeta with
58 // REST + meta box on edit screens.
59 Cache_Meta_Box::boot();
60
61 // Phase 0 architecture — managers + Free modules. v1 services
62 // (Cache/Minifier/Gzip) are NOT yet Modules; they'll be refactored
63 // in a follow-up PR with parity tests.
64 Conflict_Registry::boot();
65
66 // Register Free modules via the same action xspeed-pro uses, so
67 // the bootstrap path is symmetric across tiers.
68 add_action( 'xspeed_register_modules', array( $this, 'register_free_modules' ) );
69
70 // Fire the registration action + boot the registry at a LATER
71 // plugins_loaded priority so add-on plugins (xspeed-pro, in
72 // alphabetical order so it runs at default priority 10 AFTER
73 // us, but any add-on loaded at plugins_loaded(< 20)) have a
74 // chance to register their `xspeed_register_modules` callback
75 // before we fire the action.
76 //
77 // Bug history: previously this fired inline from init() at
78 // priority 10. xspeed-pro's plugins_loaded(15) hook then added
79 // its register_pro_modules callback AFTER the action had
80 // already fired — Pro modules never appeared in the registry.
81 // Caught by the ProStatus sentinel module's integration test.
82 add_action( 'plugins_loaded', array( $this, 'fire_module_lifecycle' ), 20 );
83
84 // One-time data migrations keyed on the stored version. Runs in
85 // admin only — nothing here needs to touch a front-end request.
86 if ( is_admin() ) {
87 add_action( 'plugins_loaded', array( $this, 'maybe_upgrade' ), 21 );
88 }
89 }
90
91 /**
92 * Run version-gated data migrations exactly once per upgrade.
93 *
94 * Keyed on `xspeed_data_version` rather than the plugin version header
95 * so a migration can be added without forcing a release bump.
96 */
97 public function maybe_upgrade(): void {
98 $current = (string) get_option( 'xspeed_data_version', '0' );
99 if ( version_compare( $current, self::DATA_VERSION, '>=' ) ) {
100 return;
101 }
102
103 // 1.1.2 — strip credential values recorded by earlier versions'
104 // settings change annotations (they're served by the trend endpoints).
105 Activity_Log::redact_legacy_secrets();
106
107 update_option( 'xspeed_data_version', self::DATA_VERSION, false );
108 }
109
110 /**
111 * Phase 2 of plugin init: fire the registration action (collecting
112 * Free + Pro + any third-party modules hooked into
113 * `xspeed_register_modules`) and boot the registry.
114 *
115 * Runs at plugins_loaded(20) so every add-on that hooks at any
116 * priority < 20 has time to register first.
117 */
118 public function fire_module_lifecycle(): void {
119 /**
120 * Action: xspeed_register_modules
121 *
122 * Free modules register at priority 10; xspeed-pro at priority
123 * 20; site code can hook in between to inject custom modules.
124 * Fires exactly once per request.
125 */
126 do_action( 'xspeed_register_modules' );
127
128 Module_Registry::boot_all();
129 }
130
131 /**
132 * Register the Free Modules shipped in this plugin. Add new module
133 * registrations here. Pro plugin hooks the same action separately.
134 */
135 public function register_free_modules(): void {
136 Module_Registry::register( new \XSpeed\Modules\Cache\CacheModule() );
137 Module_Registry::register( new \XSpeed\Modules\Health\HealthModule() );
138 Module_Registry::register( new \XSpeed\Modules\Preloader\PreloaderModule() );
139 Module_Registry::register( new \XSpeed\Modules\Heartbeat\HeartbeatModule() );
140 Module_Registry::register( new \XSpeed\Modules\Minify\MinifyModule() );
141 Module_Registry::register( new \XSpeed\Modules\Gzip\GzipModule() );
142 Module_Registry::register( new \XSpeed\Modules\Lazy\LazyModule() );
143 Module_Registry::register( new \XSpeed\Modules\Bloat\BloatModule() );
144 Module_Registry::register( new \XSpeed\Modules\Database\DatabaseModule() );
145 Module_Registry::register( new \XSpeed\Modules\Cdn\CdnModule() );
146 Module_Registry::register( new \XSpeed\Modules\Cloudflare\CloudflareModule() );
147 Module_Registry::register( new \XSpeed\Modules\ObjectCache\ObjectCacheModule() );
148 Module_Registry::register( new \XSpeed\Modules\BrowserCache\BrowserCacheModule() );
149 // Advanced Cache — a Free container row that gathers the Pro
150 // cache-coverage features (404 / search / feed / REST / rules /
151 // maintenance) into one sidebar sub-item (FBS-83633).
152 Module_Registry::register( new \XSpeed\Modules\CacheCoverage\CacheCoverageModule() );
153 Module_Registry::register( new \XSpeed\Modules\Fonts\FontsModule() );
154 Module_Registry::register( new \XSpeed\Modules\ResourceHints\ResourceHintsModule() );
155 // AI Privacy (GDPR off-switch) ships in Free even though every AI
156 // *feature* is Pro — privacy is a right, not a paid tier. FEATURES.md
157 // §AI row 6 mandates it. Without this registration the module was dead
158 // code: no REST/settings surface, the promised off-switch unreachable
159 // (FBS-83633 Bug 1). It carries its own cli_commands() so it satisfies
160 // the CLI/MCP coverage guard once registered.
161 Module_Registry::register( new \XSpeed\Modules\AIPrivacy\AIPrivacyModule() );
162 // Migration moved Pro → Free: it's an acquisition/onboarding feature
163 // (detect a competing caching plugin, import its settings, switch over),
164 // so it must work without a Pro license. Agency-scale extras (profiles,
165 // bulk multisite, host presets) remain Pro.
166 Module_Registry::register( new \XSpeed\Modules\Migration\MigrationModule() );
167 // Help & Support moved Pro → Free: a ticket link + read-only system
168 // snapshot is onboarding/diagnostics, not a paid value-add, so every
169 // user gets it. The snapshot degrades gracefully without Pro (Pro
170 // version/license fields fall back to defaults via defined()/get_option).
171 Module_Registry::register( new \XSpeed\Modules\Support\SupportModule() );
172 // MCP remote control (AI assistants) — Free. The plugin serves the
173 // MCP protocol at the site's own /xspeed/mcp URL; the only gate is
174 // the per-site connection token an admin mints via Connect. No
175 // license, no hosted infra. See IMPLEMENTATION.md §17.
176 Module_Registry::register( new \XSpeed\Modules\Mcp\McpModule() );
177 // Settings host page (FBS-83633): a Free container whose tabs embed
178 // AI Provider / AI Privacy / MCP / License / Branding / Multisite.
179 // Folds the old AI + Pro Add-ons sidebar groups into one Settings
180 // destination and gives the Free AI-Privacy off-switch a stable home.
181 Module_Registry::register( new \XSpeed\Modules\Settings\SettingsModule() );
182 }
183
184 /**
185 * Boot the opt-in usage tracker. Registers the cron sender only; the send
186 * itself is consent-gated inside Usage_Tracker. The instance is held so the
187 * onboarding REST handler can flip consent via usage_tracker()->opt_in().
188 */
189 public function start_plugin_tracking(): void {
190 $this->usage_tracker = Usage_Tracker::get_instance(
191 XSPEED_FILE,
192 array(
193 'opt_in' => true,
194 'item_id' => defined( 'XSPEED_INSIGHTS_ITEM_ID' ) ? XSPEED_INSIGHTS_ITEM_ID : false,
195 )
196 );
197 $this->usage_tracker->init();
198 }
199
200 /**
201 * The shared Usage_Tracker singleton (or null if tracking wasn't booted,
202 * e.g. on the activation hook before init() runs).
203 */
204 public function usage_tracker(): ?Usage_Tracker {
205 return $this->usage_tracker;
206 }
207
208 public static function activate() {
209 Settings::set_defaults();
210
211 if ( ! file_exists( XSPEED_CACHE_DIR ) ) {
212 wp_mkdir_p( XSPEED_CACHE_DIR );
213 }
214 Cache::write_silence( XSPEED_CACHE_DIR );
215
216 // Drop-in installation (advanced-cache.php) and the WP_CACHE constant
217 // edit happen only when the user explicitly enables caching from the
218 // admin UI — never on activation. See Cache::toggle() and
219 // Rest_Api::toggle_cache(). This is a WordPress.org review requirement.
220
221 // First-run wizard: flag a one-time redirect for the activating user.
222 // Suppressed for bulk activations / already-completed sites in
223 // Onboarding::maybe_redirect().
224 Onboarding::flag_redirect();
225
226 // Propagate activation to every registered Module. Activation
227 // happens after plugins_loaded → modules are already registered.
228 Module_Registry::activate_all();
229 }
230
231 public static function deactivate() {
232 // Drop-in + WP_CACHE constant are NOT touched here. WordPress
233 // upgrades run as deactivate → wipe files → install → activate,
234 // so removing those artifacts on every deactivate would silently
235 // disable caching after each plugin update. uninstall.php
236 // handles full teardown when the user actually removes the
237 // plugin; auto_heal() restores state on the next admin_init if
238 // the drop-in or WP_CACHE went missing for any other reason.
239 Cache::purge_all();
240 Minifier::purge_minified();
241 Gzip::apply( false );
242
243 Module_Registry::deactivate_all();
244 }
245 }
246