| 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 |
private static $instance = null; |
| 15 |
|
| 16 |
/** @var Usage_Tracker|null */ |
| 17 |
private $usage_tracker = null; |
| 18 |
|
| 19 |
public static function instance() { |
| 20 |
if ( null === self::$instance ) { |
| 21 |
self::$instance = new self(); |
| 22 |
} |
| 23 |
return self::$instance; |
| 24 |
} |
| 25 |
|
| 26 |
public function init() { |
| 27 |
// v1 services that are NOT yet wrapped as Modules (Admin, Rest_Api, |
| 28 |
// Cache, Onboarding) are instantiated directly. Minifier is now |
| 29 |
// instantiated by MinifyModule::boot(); Gzip is purely static. |
| 30 |
new Admin(); |
| 31 |
new Rest_Api(); |
| 32 |
new Cache(); |
| 33 |
new Rest_Cache(); |
| 34 |
new Onboarding(); |
| 35 |
|
| 36 |
// Opt-in usage analytics. Instantiating + init() only registers the |
| 37 |
// cron callback; NOTHING is collected or sent until the admin opts in |
| 38 |
// from the setup wizard (Onboarding wires the consent toggle to |
| 39 |
// Usage_Tracker::opt_in()). See class-usage-tracker.php privacy contract. |
| 40 |
$this->start_plugin_tracking(); |
| 41 |
|
| 42 |
// Auto-heal the cache drop-in + WP_CACHE constant when state |
| 43 |
// drifts. Scoped to admin_init only: filesystem writes belong in |
| 44 |
// an authenticated admin context, never on anonymous front-end |
| 45 |
// requests. The user already opted in (cache_enabled=true) — |
| 46 |
// this is a consistency check, not a new install path. First |
| 47 |
// admin page load after a plugin upgrade restores the state; |
| 48 |
// front-end then serves from cache on the next request. |
| 49 |
add_action( 'admin_init', array( Cache::class, 'auto_heal' ) ); |
| 50 |
|
| 51 |
// Per-post cache rules (Phase 3.4) — registers postmeta with |
| 52 |
// REST + meta box on edit screens. |
| 53 |
Cache_Meta_Box::boot(); |
| 54 |
|
| 55 |
// Phase 0 architecture — managers + Free modules. v1 services |
| 56 |
// (Cache/Minifier/Gzip) are NOT yet Modules; they'll be refactored |
| 57 |
// in a follow-up PR with parity tests. |
| 58 |
Conflict_Registry::boot(); |
| 59 |
|
| 60 |
// Register Free modules via the same action xspeed-pro uses, so |
| 61 |
// the bootstrap path is symmetric across tiers. |
| 62 |
add_action( 'xspeed_register_modules', array( $this, 'register_free_modules' ) ); |
| 63 |
|
| 64 |
// Fire the registration action + boot the registry at a LATER |
| 65 |
// plugins_loaded priority so add-on plugins (xspeed-pro, in |
| 66 |
// alphabetical order so it runs at default priority 10 AFTER |
| 67 |
// us, but any add-on loaded at plugins_loaded(< 20)) have a |
| 68 |
// chance to register their `xspeed_register_modules` callback |
| 69 |
// before we fire the action. |
| 70 |
// |
| 71 |
// Bug history: previously this fired inline from init() at |
| 72 |
// priority 10. xspeed-pro's plugins_loaded(15) hook then added |
| 73 |
// its register_pro_modules callback AFTER the action had |
| 74 |
// already fired — Pro modules never appeared in the registry. |
| 75 |
// Caught by the ProStatus sentinel module's integration test. |
| 76 |
add_action( 'plugins_loaded', array( $this, 'fire_module_lifecycle' ), 20 ); |
| 77 |
} |
| 78 |
|
| 79 |
/** |
| 80 |
* Phase 2 of plugin init: fire the registration action (collecting |
| 81 |
* Free + Pro + any third-party modules hooked into |
| 82 |
* `xspeed_register_modules`) and boot the registry. |
| 83 |
* |
| 84 |
* Runs at plugins_loaded(20) so every add-on that hooks at any |
| 85 |
* priority < 20 has time to register first. |
| 86 |
*/ |
| 87 |
public function fire_module_lifecycle(): void { |
| 88 |
/** |
| 89 |
* Action: xspeed_register_modules |
| 90 |
* |
| 91 |
* Free modules register at priority 10; xspeed-pro at priority |
| 92 |
* 20; site code can hook in between to inject custom modules. |
| 93 |
* Fires exactly once per request. |
| 94 |
*/ |
| 95 |
do_action( 'xspeed_register_modules' ); |
| 96 |
|
| 97 |
Module_Registry::boot_all(); |
| 98 |
} |
| 99 |
|
| 100 |
/** |
| 101 |
* Register the Free Modules shipped in this plugin. Add new module |
| 102 |
* registrations here. Pro plugin hooks the same action separately. |
| 103 |
*/ |
| 104 |
public function register_free_modules(): void { |
| 105 |
Module_Registry::register( new \XSpeed\Modules\Cache\CacheModule() ); |
| 106 |
Module_Registry::register( new \XSpeed\Modules\Health\HealthModule() ); |
| 107 |
Module_Registry::register( new \XSpeed\Modules\Preloader\PreloaderModule() ); |
| 108 |
Module_Registry::register( new \XSpeed\Modules\Heartbeat\HeartbeatModule() ); |
| 109 |
Module_Registry::register( new \XSpeed\Modules\Minify\MinifyModule() ); |
| 110 |
Module_Registry::register( new \XSpeed\Modules\Gzip\GzipModule() ); |
| 111 |
Module_Registry::register( new \XSpeed\Modules\Lazy\LazyModule() ); |
| 112 |
Module_Registry::register( new \XSpeed\Modules\Bloat\BloatModule() ); |
| 113 |
Module_Registry::register( new \XSpeed\Modules\Database\DatabaseModule() ); |
| 114 |
Module_Registry::register( new \XSpeed\Modules\Cdn\CdnModule() ); |
| 115 |
Module_Registry::register( new \XSpeed\Modules\Cloudflare\CloudflareModule() ); |
| 116 |
Module_Registry::register( new \XSpeed\Modules\ObjectCache\ObjectCacheModule() ); |
| 117 |
Module_Registry::register( new \XSpeed\Modules\BrowserCache\BrowserCacheModule() ); |
| 118 |
Module_Registry::register( new \XSpeed\Modules\Fonts\FontsModule() ); |
| 119 |
// Migration moved Pro → Free: it's an acquisition/onboarding feature |
| 120 |
// (detect a competing caching plugin, import its settings, switch over), |
| 121 |
// so it must work without a Pro license. Agency-scale extras (profiles, |
| 122 |
// bulk multisite, host presets) remain Pro. |
| 123 |
Module_Registry::register( new \XSpeed\Modules\Migration\MigrationModule() ); |
| 124 |
// Help & Support moved Pro → Free: a ticket link + read-only system |
| 125 |
// snapshot is onboarding/diagnostics, not a paid value-add, so every |
| 126 |
// user gets it. The snapshot degrades gracefully without Pro (Pro |
| 127 |
// version/license fields fall back to defaults via defined()/get_option). |
| 128 |
Module_Registry::register( new \XSpeed\Modules\Support\SupportModule() ); |
| 129 |
} |
| 130 |
|
| 131 |
/** |
| 132 |
* Boot the opt-in usage tracker. Registers the cron sender only; the send |
| 133 |
* itself is consent-gated inside Usage_Tracker. The instance is held so the |
| 134 |
* onboarding REST handler can flip consent via usage_tracker()->opt_in(). |
| 135 |
*/ |
| 136 |
public function start_plugin_tracking(): void { |
| 137 |
$this->usage_tracker = Usage_Tracker::get_instance( |
| 138 |
XSPEED_FILE, |
| 139 |
array( |
| 140 |
'opt_in' => true, |
| 141 |
'item_id' => defined( 'XSPEED_INSIGHTS_ITEM_ID' ) ? XSPEED_INSIGHTS_ITEM_ID : false, |
| 142 |
) |
| 143 |
); |
| 144 |
$this->usage_tracker->init(); |
| 145 |
} |
| 146 |
|
| 147 |
/** |
| 148 |
* The shared Usage_Tracker singleton (or null if tracking wasn't booted, |
| 149 |
* e.g. on the activation hook before init() runs). |
| 150 |
*/ |
| 151 |
public function usage_tracker(): ?Usage_Tracker { |
| 152 |
return $this->usage_tracker; |
| 153 |
} |
| 154 |
|
| 155 |
public static function activate() { |
| 156 |
Settings::set_defaults(); |
| 157 |
|
| 158 |
if ( ! file_exists( XSPEED_CACHE_DIR ) ) { |
| 159 |
wp_mkdir_p( XSPEED_CACHE_DIR ); |
| 160 |
} |
| 161 |
Cache::write_silence( XSPEED_CACHE_DIR ); |
| 162 |
|
| 163 |
// Drop-in installation (advanced-cache.php) and the WP_CACHE constant |
| 164 |
// edit happen only when the user explicitly enables caching from the |
| 165 |
// admin UI — never on activation. See Cache::toggle() and |
| 166 |
// Rest_Api::toggle_cache(). This is a WordPress.org review requirement. |
| 167 |
|
| 168 |
// First-run wizard: flag a one-time redirect for the activating user. |
| 169 |
// Suppressed for bulk activations / already-completed sites in |
| 170 |
// Onboarding::maybe_redirect(). |
| 171 |
Onboarding::flag_redirect(); |
| 172 |
|
| 173 |
// Propagate activation to every registered Module. Activation |
| 174 |
// happens after plugins_loaded → modules are already registered. |
| 175 |
Module_Registry::activate_all(); |
| 176 |
} |
| 177 |
|
| 178 |
public static function deactivate() { |
| 179 |
// Drop-in + WP_CACHE constant are NOT touched here. WordPress |
| 180 |
// upgrades run as deactivate → wipe files → install → activate, |
| 181 |
// so removing those artifacts on every deactivate would silently |
| 182 |
// disable caching after each plugin update. uninstall.php |
| 183 |
// handles full teardown when the user actually removes the |
| 184 |
// plugin; auto_heal() restores state on the next admin_init if |
| 185 |
// the drop-in or WP_CACHE went missing for any other reason. |
| 186 |
Cache::purge_all(); |
| 187 |
Minifier::purge_minified(); |
| 188 |
Gzip::apply( false ); |
| 189 |
|
| 190 |
Module_Registry::deactivate_all(); |
| 191 |
} |
| 192 |
} |
| 193 |
|