PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.9
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.9
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-rest-api.php

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

307 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API endpoints.
4 *
5 * @package XSpeed
6 */
7
8 namespace XSpeed;
9
10 defined( 'ABSPATH' ) || exit;
11
12 class Rest_Api {
13
14 const NAMESPACE_V1 = 'xspeed/v1';
15
16 public function __construct() {
17 add_action( 'rest_api_init', array( $this, 'register' ) );
18 }
19
20 public function register() {
21 register_rest_route(
22 self::NAMESPACE_V1,
23 '/status',
24 array(
25 'methods' => 'GET',
26 'callback' => array( $this, 'get_status' ),
27 'permission_callback' => array( $this, 'permissions' ),
28 )
29 );
30
31 register_rest_route(
32 self::NAMESPACE_V1,
33 '/settings',
34 array(
35 array(
36 'methods' => 'GET',
37 'callback' => array( $this, 'get_settings' ),
38 'permission_callback' => array( $this, 'permissions' ),
39 ),
40 array(
41 'methods' => 'POST',
42 'callback' => array( $this, 'update_settings' ),
43 'permission_callback' => array( $this, 'permissions' ),
44 ),
45 )
46 );
47
48 // Resolved white-label branding. The dashboard refetches this after
49 // a white-label save so the chrome (sidebar name/logo, footer)
50 // updates live without a reload. (FBS white-label-onboarding)
51 register_rest_route(
52 self::NAMESPACE_V1,
53 '/branding',
54 array(
55 'methods' => 'GET',
56 'callback' => array( $this, 'get_branding' ),
57 'permission_callback' => array( $this, 'permissions' ),
58 )
59 );
60
61 register_rest_route(
62 self::NAMESPACE_V1,
63 '/cache/purge',
64 array(
65 'methods' => 'POST',
66 'callback' => array( $this, 'purge' ),
67 'permission_callback' => array( $this, 'permissions' ),
68 )
69 );
70
71 register_rest_route(
72 self::NAMESPACE_V1,
73 '/cache/toggle',
74 array(
75 'methods' => 'POST',
76 'callback' => array( $this, 'toggle_cache' ),
77 'permission_callback' => array( $this, 'permissions' ),
78 )
79 );
80
81 register_rest_route(
82 self::NAMESPACE_V1,
83 '/cache/benchmark',
84 array(
85 'methods' => 'GET',
86 'callback' => array( $this, 'benchmark' ),
87 'permission_callback' => array( $this, 'permissions' ),
88 )
89 );
90
91 register_rest_route(
92 self::NAMESPACE_V1,
93 '/audit/pro',
94 array(
95 'methods' => 'GET',
96 'callback' => array( $this, 'pro_audit' ),
97 'permission_callback' => array( $this, 'permissions' ),
98 )
99 );
100
101 register_rest_route(
102 self::NAMESPACE_V1,
103 '/modules',
104 array(
105 'methods' => 'GET',
106 'callback' => array( $this, 'get_modules' ),
107 'permission_callback' => array( $this, 'permissions' ),
108 )
109 );
110
111 // On-demand desktop-vs-mobile HTML equality probe (FBS-83145). POST so
112 // it's never triggered by a prefetch/GET; runs only from the dashboard
113 // "Check now" button behind manage_options.
114 register_rest_route(
115 self::NAMESPACE_V1,
116 '/cache/mobile-probe',
117 array(
118 'methods' => 'POST',
119 'callback' => array( $this, 'mobile_probe' ),
120 'permission_callback' => array( $this, 'permissions' ),
121 )
122 );
123
124 // Dismiss the Separate-Mobile-Cache review prompt (FBS-83145).
125 register_rest_route(
126 self::NAMESPACE_V1,
127 '/cache/mobile-review-dismiss',
128 array(
129 'methods' => 'POST',
130 'callback' => array( $this, 'mobile_review_dismiss' ),
131 'permission_callback' => array( $this, 'permissions' ),
132 )
133 );
134 }
135
136 /**
137 * Run the on-demand mobile-equality probe and return the fresh /status
138 * mobile_separate block so the dashboard can update the callout in place.
139 */
140 public function mobile_probe( $request ) {
141 unset( $request );
142 return rest_ensure_response( Cache::probe_mobile_equality() );
143 }
144
145 /**
146 * Clear the migration review flag so the callout stops nagging.
147 */
148 public function mobile_review_dismiss( $request ) {
149 unset( $request );
150 Cache::clear_mobile_separate_review();
151 return rest_ensure_response( array( 'dismissed' => true ) );
152 }
153
154 /**
155 * The registered-module descriptors — same payload baked into the
156 * admin bootstrap (Admin::modules_payload), re-evaluated live. The
157 * dashboard re-fetches this after a license activate/deactivate so a
158 * Pro module's custom_panel flips between its real surface and
159 * LicenseLockedPanel (decided server-side via the
160 * xspeed_module_descriptor filter) WITHOUT a full page reload.
161 */
162 public function get_modules() {
163 return rest_ensure_response( Admin::modules_payload() );
164 }
165
166 /**
167 * Run the Pro audit — scans current settings + cache stats,
168 * returns a personalized list of Pro features that would help
169 * THIS site. See Pro_Audit::run() for the rule set.
170 */
171 public function pro_audit( $request ) {
172 unset( $request );
173 return rest_ensure_response( array( 'suggestions' => Pro_Audit::run() ) );
174 }
175
176 /**
177 * Cache before/after benchmark — fetches home_url() twice (with +
178 * without the bypass header) and returns side-by-side timings for
179 * the dashboard widget.
180 */
181 public function benchmark( $request ) {
182 unset( $request );
183 return rest_ensure_response( Cache_Benchmark::run() );
184 }
185
186 public function permissions() {
187 return current_user_can( 'manage_options' );
188 }
189
190 public function get_status() {
191 $opts = Settings::get();
192 $stats = Cache::get_stats();
193
194 // rewrite_probe + nginx_server_block mirror the admin bootstrap
195 // payload (Admin::bootstrap_data). The dashboard re-fetches /status
196 // after every module save to refresh the consolidated nginx
197 // server-block snippet without a full page reload — if these were
198 // omitted here, the snippet would only ever update on reload (the
199 // QA bug: "Server config snippet requires full page reload to
200 // reflect toggle changes"). Keep this in sync with Admin.
201 $server_type = Server::type();
202 // LiteSpeed deliberately serves hits via the PHP drop-in (its
203 // .htaccess can't add the HIT header or log a static hit), so the
204 // static-rewrite probe is N/A there — surfacing it would pop the
205 // "PHP fallback" nag for a setup that's working as designed. Only
206 // nginx + Apache use a server-level rewrite worth probing.
207 $rewrite_capable = ( $server_type === Server::NGINX || $server_type === Server::APACHE );
208 $rewrite_probe = null;
209 if ( $opts['cache_enabled'] && $rewrite_capable ) {
210 $probe = Cache::probe_static_rewrite();
211 $rewrite_probe = array(
212 'active' => (bool) ( $probe['active'] ?? false ),
213 'server_type' => $server_type,
214 'snippet' => Cache::nginx_snippet(),
215 'topology' => Server::rewrite_topology(),
216 'behind_proxy' => Server::is_behind_proxy(),
217 );
218 }
219
220 return rest_ensure_response(
221 array(
222 'enabled' => (bool) $opts['cache_enabled'],
223 'stats' => $stats,
224 'server' => array(
225 'type' => $server_type,
226 'gzip_mode' => Server::gzip_mode(),
227 'gzip_active' => Gzip::probe_active(),
228 'nginx_snippet' => Gzip::nginx_snippet(),
229 ),
230 'rewrite_probe' => $rewrite_probe,
231 'nginx_server_block' => Cache::full_nginx_server_block(),
232 // Separate Mobile Cache visibility (FBS-83145). `blocking` is
233 // true when mobile_separate is what's keeping the device-blind
234 // static fast path from installing on a rewrite-capable server;
235 // `needs_review` is true when a migration turned it on for us and
236 // the user hasn't confirmed they actually need it. The dashboard
237 // renders a callout (+ "Check now" equality probe) from these.
238 'mobile_separate' => array(
239 'enabled' => ! empty( Settings::get()['cache_enabled'] ) ? (bool) ( Settings_Manager::get( 'cache' )['mobile_separate'] ?? false ) : false,
240 'blocking' => $rewrite_capable && 'mobile_separate' === Cache::static_rewrite_block_reason(),
241 'needs_review' => Cache::mobile_separate_needs_review(),
242 ),
243 )
244 );
245 }
246
247 public function get_settings() {
248 return rest_ensure_response( Settings::get() );
249 }
250
251 public function update_settings( \WP_REST_Request $request ) {
252 $params = $request->get_json_params();
253 if ( ! is_array( $params ) ) {
254 $params = $request->get_params();
255 }
256 // `cache_enabled` is the trigger for drop-in install / wp-config.php
257 // edit and must only flow through the dedicated /cache/toggle
258 // endpoint. Strip it here so generic settings updates can never
259 // implicitly write a drop-in or modify wp-config.php.
260 unset( $params['cache_enabled'] );
261 $updated = Settings::update( $params );
262 return rest_ensure_response( $updated );
263 }
264
265 public function purge() {
266 Cache::purge_all();
267 return rest_ensure_response( array( 'stats' => Cache::get_stats() ) );
268 }
269
270 /**
271 * Resolved branding ({name, footer_credit, hide_help_links, logo_svg}).
272 * Runs the `xspeed_branding` filter so Pro's white-label override is
273 * reflected. Consumed by the dashboard's post-save branding refresh.
274 */
275 public function get_branding() {
276 return rest_ensure_response( Admin::branding() );
277 }
278
279 public function toggle_cache( \WP_REST_Request $request ) {
280 $params = $request->get_json_params();
281 $enabled = isset( $params['enabled'] ) ? (bool) $params['enabled'] : false;
282
283 // User-explicit drop-in install / wp-config.php edit happens here.
284 // permission_callback above already enforced current_user_can(
285 // 'manage_options' ); the REST nonce is verified by core via the
286 // X-WP-Nonce header.
287 $state = Cache::toggle( $enabled );
288 $updated = Settings::update( array( 'cache_enabled' => $state['enabled'] ) );
289
290 // Recompute the unified nginx block AFTER cache_enabled is persisted.
291 // Cache::toggle() computes it inline, but cache_enabled isn't written
292 // until the Settings::update() above — so the block inside $state
293 // reflects the PRE-toggle state (CacheModule::nginx_directives() gates
294 // on cache_enabled). Regenerate here so the dashboard's optimistic
295 // update shows the snippet for the state the user just selected.
296 $state['nginx_server_block'] = Cache::full_nginx_server_block();
297
298 return rest_ensure_response(
299 array(
300 'enabled' => $updated['cache_enabled'],
301 'stats' => Cache::get_stats(),
302 'install_state' => $state,
303 )
304 );
305 }
306 }
307