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

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

290 lines 8.7 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, with secret fields masked. Uses the public
122 * view (not get_settings()) so a credential never leaves in a REST payload;
123 * the engine reads real values through get_settings()/get_setting(). (#115)
124 * Modules can override but rarely need to.
125 */
126 public function rest_get_settings( \WP_REST_Request $request ) {
127 return rest_ensure_response( Settings_Manager::get_public( static::SLUG ) );
128 }
129
130 /**
131 * Default POST handler — validates the JSON body against the
132 * schema, persists, returns the post-update settings. Unknown keys
133 * are stripped by Settings_Manager.
134 */
135 public function rest_update_settings( \WP_REST_Request $request ) {
136 $params = $request->get_json_params();
137 if ( ! is_array( $params ) ) {
138 $params = $request->get_params();
139 }
140 return rest_ensure_response( $this->update_settings( $params ) );
141 }
142
143 /**
144 * UI panel declarations consumed by the React dashboard via the
145 * bootstrap payload. Each entry: [
146 * 'section' => 'cache' | 'performance' | 'images' | ...,
147 * 'position' => int,
148 * 'component' => 'HealthCard' | 'TogglesList' | 'StatGrid' | 'Custom',
149 * 'props' => array,
150 * ]
151 *
152 * @return array[]
153 */
154 public function ui_panels(): array {
155 return array();
156 }
157
158 /**
159 * Sidebar / dashboard metadata. The React app uses these to render the
160 * module's nav entry. Override per module to set a friendly label and
161 * a lucide-react icon name (must be in the renderer's icon whitelist —
162 * see src/components/IconResolver.tsx).
163 *
164 * @return array{label:string,icon:string,description?:string,hidden?:bool}
165 */
166 public function ui_metadata(): array {
167 return array(
168 'label' => ucfirst( str_replace( '_', ' ', static::SLUG ) ),
169 'icon' => 'Square',
170 );
171 }
172
173 /**
174 * Dynamic in-panel notices (callouts) rendered above the schema form.
175 * Computed fresh on every dashboard load. Examples: nginx GZIP
176 * snippet when the server can't be auto-configured, "drop-in
177 * missing" warning when cache_enabled but no advanced-cache.php.
178 *
179 * Each entry: [
180 * 'tone' => 'info' | 'warn' | 'danger' | 'success',
181 * 'title' => 'Short heading.',
182 * 'body' => 'One- or two-sentence explanation.',
183 * 'snippet' => 'Optional verbatim code snippet rendered in a
184 * <pre> with a Copy button.',
185 * ]
186 *
187 * @return array[]
188 */
189 public function ui_notices(): array {
190 return array();
191 }
192
193 /**
194 * WP-CLI command definitions. Each entry: [
195 * 'name' => 'xspeed cache purge',
196 * 'callback' => callable,
197 * 'synopsis' => array, // wp-cli synopsis spec
198 * ]
199 *
200 * @return array[]
201 */
202 public function cli_commands(): array {
203 return array();
204 }
205
206 /**
207 * Nginx directives this module contributes to the unified server-block
208 * snippet rendered by Cache::full_nginx_server_block(). Returning a
209 * non-null string opts the module into the consolidated "paste this
210 * once into your nginx vhost" UX on the Cache panel.
211 *
212 * The returned string should be the bare directives only — no `server
213 * { }` wrapper, no comment header (the aggregator adds one). Empty
214 * string and null are both treated as "no contribution this render".
215 *
216 * Return null (default) when the module is disabled, its current
217 * settings make the directives a no-op, or the module doesn't have
218 * nginx-side directives at all.
219 */
220 public function nginx_directives(): ?string {
221 return null;
222 }
223
224 /**
225 * Conflict declarations for this module — which other plugins clash
226 * with which sub-feature. Each entry: [
227 * 'plugin' => 'wp-rocket/wp-rocket.php',
228 * 'feature' => 'page_cache',
229 * 'strategy' => 'refuse' | 'warn' | 'allow',
230 * 'reason' => 'human-readable why',
231 * ]
232 *
233 * @return array[]
234 */
235 public function conflicts(): array {
236 return array();
237 }
238
239 /**
240 * Register WP hooks. Called by Module_Registry::boot_all() after
241 * dependencies are resolved. Modules should NOT register hooks in
242 * their constructors — only in boot() — so the registry can control
243 * load order.
244 */
245 public function boot(): void {}
246
247 /**
248 * One-time setup at plugin activation. Idempotent. Examples: create
249 * a custom table, write a silence guard, register a cron schedule.
250 */
251 public function activate(): void {}
252
253 /**
254 * Tear down at plugin deactivation. Reversible counterpart to
255 * activate(). MUST leave the site in a clean state — no orphaned
256 * cron jobs, no leftover drop-ins.
257 */
258 public function deactivate(): void {}
259
260 /**
261 * Convenience accessors. Modules read/write their own settings
262 * through these so the storage detail (one option per module under
263 * `xspeed_module_<slug>`) stays encapsulated.
264 */
265 final public function get_setting( string $key, $default = null ) {
266 $opts = Settings_Manager::get( static::SLUG );
267 return array_key_exists( $key, $opts ) ? $opts[ $key ] : $default;
268 }
269
270 final public function get_settings(): array {
271 return Settings_Manager::get( static::SLUG );
272 }
273
274 final public function update_settings( array $input ): array {
275 return Settings_Manager::update( static::SLUG, $input );
276 }
277
278 final public function slug(): string {
279 return static::SLUG;
280 }
281
282 final public function tier(): string {
283 return static::TIER;
284 }
285
286 final public function version(): string {
287 return static::VERSION;
288 }
289 }
290