PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.7
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.7
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 1.2.0 All 28 releases
xspeed / includes / class-module.php

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

288 lines 8.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Module abstract base class.
4 *
5 * Every feature in xSpeed (Free or Pro) extends this class. The contract is
6 * documented in IMPLEMENTATION.md §1.1. A Module is a self-contained unit
7 * that declares its tier, settings schema, REST routes, UI panels, CLI
8 * commands, conflicts, and lifecycle hooks in one place — so moving a
9 * feature between Free and Pro is a `git mv` + flipping the TIER constant,
10 * with no call-site changes.
11 *
12 * Concrete modules MUST:
13 * - Set the SLUG class constant.
14 * - Set the TIER class constant (TIER_FREE or TIER_PRO).
15 * - Set the VERSION class constant.
16 *
17 * @package XSpeed
18 */
19
20 namespace XSpeed;
21
22 defined( 'ABSPATH' ) || exit;
23
24 abstract class Module {
25
26 public const TIER_FREE = 'free';
27 public const TIER_PRO = 'pro';
28
29 /**
30 * Concrete modules override these three constants.
31 */
32 public const SLUG = '';
33 public const TIER = self::TIER_FREE;
34 public const VERSION = '1.0.0';
35
36 /**
37 * Other module slugs this module needs at boot. Resolved by
38 * Module_Registry via topological sort; missing deps fail loudly.
39 *
40 * @return string[]
41 */
42 public function dependencies(): array {
43 return array();
44 }
45
46 /**
47 * Typed settings schema. See Settings_Manager::validate() for the
48 * supported `type` values (bool, int, string, enum, list, url). Each
49 * field declares `default` and optional `min` / `max` / `options` /
50 * `item_type`. Storage key is `xspeed_module_<slug>`.
51 *
52 * @return array<string,array>
53 */
54 public function settings_schema(): array {
55 return array();
56 }
57
58 /**
59 * Option keys a module stores OUTSIDE its settings_schema that must
60 * survive a schema-driven save. Settings_Manager rebuilds the option
61 * from the schema on get()/update(), which would otherwise drop these.
62 * Example: the REST-cache module keeps its route `rules` array here so a
63 * plain enabled/ttl save doesn't wipe the rules table. (FBS-82408)
64 *
65 * @return string[]
66 */
67 public function preserved_keys(): array {
68 return array();
69 }
70
71 /**
72 * Schema migrations keyed by target version. Each value is a callable
73 * that receives the stored options array and returns the migrated
74 * array. Migrations run in version order on first load after upgrade.
75 *
76 * @return array<string,callable>
77 */
78 public function migrations(): array {
79 return array();
80 }
81
82 /**
83 * REST routes the module owns. Paths are prefixed with
84 * `/xspeed/v1/<slug>/` by Rest_Manager; declare without the prefix.
85 * `permission_callback` is wrapped automatically with a final cap
86 * check + tier gate, so modules don't need to repeat that boilerplate
87 * — but they MUST still declare a sensible callback.
88 *
89 * Default impl returns the standard GET + POST pair for modules that
90 * declare a settings_schema. Modules that need extra endpoints can
91 * extend the array. Modules with truly custom REST should override
92 * entirely and skip parent::rest_routes().
93 *
94 * Per SETTINGS.md §5.1 every module's settings live at:
95 * GET /xspeed/v1/<slug>/ → current settings
96 * POST /xspeed/v1/<slug>/ → partial patch, returns updated settings
97 *
98 * @return array[]
99 */
100 public function rest_routes(): array {
101 if ( empty( $this->settings_schema() ) ) {
102 return array();
103 }
104 return array(
105 array(
106 'path' => '/',
107 'methods' => 'GET',
108 'callback' => array( $this, 'rest_get_settings' ),
109 ),
110 array(
111 'path' => '/',
112 'methods' => 'POST',
113 'callback' => array( $this, 'rest_update_settings' ),
114 'feature' => static::SLUG,
115 ),
116 );
117 }
118
119 /**
120 * Default GET handler — returns all settings (defaults + stored)
121 * coerced against the schema. Modules can override but rarely need
122 * to.
123 */
124 public function rest_get_settings( \WP_REST_Request $request ) {
125 return rest_ensure_response( $this->get_settings() );
126 }
127
128 /**
129 * Default POST handler — validates the JSON body against the
130 * schema, persists, returns the post-update settings. Unknown keys
131 * are stripped by Settings_Manager.
132 */
133 public function rest_update_settings( \WP_REST_Request $request ) {
134 $params = $request->get_json_params();
135 if ( ! is_array( $params ) ) {
136 $params = $request->get_params();
137 }
138 return rest_ensure_response( $this->update_settings( $params ) );
139 }
140
141 /**
142 * UI panel declarations consumed by the React dashboard via the
143 * bootstrap payload. Each entry: [
144 * 'section' => 'cache' | 'performance' | 'images' | ...,
145 * 'position' => int,
146 * 'component' => 'HealthCard' | 'TogglesList' | 'StatGrid' | 'Custom',
147 * 'props' => array,
148 * ]
149 *
150 * @return array[]
151 */
152 public function ui_panels(): array {
153 return array();
154 }
155
156 /**
157 * Sidebar / dashboard metadata. The React app uses these to render the
158 * module's nav entry. Override per module to set a friendly label and
159 * a lucide-react icon name (must be in the renderer's icon whitelist —
160 * see src/components/IconResolver.tsx).
161 *
162 * @return array{label:string,icon:string,description?:string,hidden?:bool}
163 */
164 public function ui_metadata(): array {
165 return array(
166 'label' => ucfirst( str_replace( '_', ' ', static::SLUG ) ),
167 'icon' => 'Square',
168 );
169 }
170
171 /**
172 * Dynamic in-panel notices (callouts) rendered above the schema form.
173 * Computed fresh on every dashboard load. Examples: nginx GZIP
174 * snippet when the server can't be auto-configured, "drop-in
175 * missing" warning when cache_enabled but no advanced-cache.php.
176 *
177 * Each entry: [
178 * 'tone' => 'info' | 'warn' | 'danger' | 'success',
179 * 'title' => 'Short heading.',
180 * 'body' => 'One- or two-sentence explanation.',
181 * 'snippet' => 'Optional verbatim code snippet rendered in a
182 * <pre> with a Copy button.',
183 * ]
184 *
185 * @return array[]
186 */
187 public function ui_notices(): array {
188 return array();
189 }
190
191 /**
192 * WP-CLI command definitions. Each entry: [
193 * 'name' => 'xspeed cache purge',
194 * 'callback' => callable,
195 * 'synopsis' => array, // wp-cli synopsis spec
196 * ]
197 *
198 * @return array[]
199 */
200 public function cli_commands(): array {
201 return array();
202 }
203
204 /**
205 * Nginx directives this module contributes to the unified server-block
206 * snippet rendered by Cache::full_nginx_server_block(). Returning a
207 * non-null string opts the module into the consolidated "paste this
208 * once into your nginx vhost" UX on the Cache panel.
209 *
210 * The returned string should be the bare directives only — no `server
211 * { }` wrapper, no comment header (the aggregator adds one). Empty
212 * string and null are both treated as "no contribution this render".
213 *
214 * Return null (default) when the module is disabled, its current
215 * settings make the directives a no-op, or the module doesn't have
216 * nginx-side directives at all.
217 */
218 public function nginx_directives(): ?string {
219 return null;
220 }
221
222 /**
223 * Conflict declarations for this module — which other plugins clash
224 * with which sub-feature. Each entry: [
225 * 'plugin' => 'wp-rocket/wp-rocket.php',
226 * 'feature' => 'page_cache',
227 * 'strategy' => 'refuse' | 'warn' | 'allow',
228 * 'reason' => 'human-readable why',
229 * ]
230 *
231 * @return array[]
232 */
233 public function conflicts(): array {
234 return array();
235 }
236
237 /**
238 * Register WP hooks. Called by Module_Registry::boot_all() after
239 * dependencies are resolved. Modules should NOT register hooks in
240 * their constructors — only in boot() — so the registry can control
241 * load order.
242 */
243 public function boot(): void {}
244
245 /**
246 * One-time setup at plugin activation. Idempotent. Examples: create
247 * a custom table, write a silence guard, register a cron schedule.
248 */
249 public function activate(): void {}
250
251 /**
252 * Tear down at plugin deactivation. Reversible counterpart to
253 * activate(). MUST leave the site in a clean state — no orphaned
254 * cron jobs, no leftover drop-ins.
255 */
256 public function deactivate(): void {}
257
258 /**
259 * Convenience accessors. Modules read/write their own settings
260 * through these so the storage detail (one option per module under
261 * `xspeed_module_<slug>`) stays encapsulated.
262 */
263 final public function get_setting( string $key, $default = null ) {
264 $opts = Settings_Manager::get( static::SLUG );
265 return array_key_exists( $key, $opts ) ? $opts[ $key ] : $default;
266 }
267
268 final public function get_settings(): array {
269 return Settings_Manager::get( static::SLUG );
270 }
271
272 final public function update_settings( array $input ): array {
273 return Settings_Manager::update( static::SLUG, $input );
274 }
275
276 final public function slug(): string {
277 return static::SLUG;
278 }
279
280 final public function tier(): string {
281 return static::TIER;
282 }
283
284 final public function version(): string {
285 return static::VERSION;
286 }
287 }
288