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-rest-api.php

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

254 lines 7.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
112 /**
113 * The registered-module descriptors — same payload baked into the
114 * admin bootstrap (Admin::modules_payload), re-evaluated live. The
115 * dashboard re-fetches this after a license activate/deactivate so a
116 * Pro module's custom_panel flips between its real surface and
117 * LicenseLockedPanel (decided server-side via the
118 * xspeed_module_descriptor filter) WITHOUT a full page reload.
119 */
120 public function get_modules() {
121 return rest_ensure_response( Admin::modules_payload() );
122 }
123
124 /**
125 * Run the Pro audit — scans current settings + cache stats,
126 * returns a personalized list of Pro features that would help
127 * THIS site. See Pro_Audit::run() for the rule set.
128 */
129 public function pro_audit( $request ) {
130 unset( $request );
131 return rest_ensure_response( array( 'suggestions' => Pro_Audit::run() ) );
132 }
133
134 /**
135 * Cache before/after benchmark — fetches home_url() twice (with +
136 * without the bypass header) and returns side-by-side timings for
137 * the dashboard widget.
138 */
139 public function benchmark( $request ) {
140 unset( $request );
141 return rest_ensure_response( Cache_Benchmark::run() );
142 }
143
144 public function permissions() {
145 return current_user_can( 'manage_options' );
146 }
147
148 public function get_status() {
149 $opts = Settings::get();
150 $stats = Cache::get_stats();
151
152 // rewrite_probe + nginx_server_block mirror the admin bootstrap
153 // payload (Admin::bootstrap_data). The dashboard re-fetches /status
154 // after every module save to refresh the consolidated nginx
155 // server-block snippet without a full page reload — if these were
156 // omitted here, the snippet would only ever update on reload (the
157 // QA bug: "Server config snippet requires full page reload to
158 // reflect toggle changes"). Keep this in sync with Admin.
159 $server_type = Server::type();
160 // LiteSpeed deliberately serves hits via the PHP drop-in (its
161 // .htaccess can't add the HIT header or log a static hit), so the
162 // static-rewrite probe is N/A there — surfacing it would pop the
163 // "PHP fallback" nag for a setup that's working as designed. Only
164 // nginx + Apache use a server-level rewrite worth probing.
165 $rewrite_capable = ( $server_type === Server::NGINX || $server_type === Server::APACHE );
166 $rewrite_probe = null;
167 if ( $opts['cache_enabled'] && $rewrite_capable ) {
168 $probe = Cache::probe_static_rewrite();
169 $rewrite_probe = array(
170 'active' => (bool) ( $probe['active'] ?? false ),
171 'server_type' => $server_type,
172 'snippet' => Cache::nginx_snippet(),
173 'topology' => Server::rewrite_topology(),
174 'behind_proxy' => Server::is_behind_proxy(),
175 );
176 }
177
178 return rest_ensure_response(
179 array(
180 'enabled' => (bool) $opts['cache_enabled'],
181 'stats' => $stats,
182 'server' => array(
183 'type' => $server_type,
184 'gzip_mode' => Server::gzip_mode(),
185 'gzip_active' => Gzip::probe_active(),
186 'nginx_snippet' => Gzip::nginx_snippet(),
187 ),
188 'rewrite_probe' => $rewrite_probe,
189 'nginx_server_block' => Cache::full_nginx_server_block(),
190 )
191 );
192 }
193
194 public function get_settings() {
195 return rest_ensure_response( Settings::get() );
196 }
197
198 public function update_settings( \WP_REST_Request $request ) {
199 $params = $request->get_json_params();
200 if ( ! is_array( $params ) ) {
201 $params = $request->get_params();
202 }
203 // `cache_enabled` is the trigger for drop-in install / wp-config.php
204 // edit and must only flow through the dedicated /cache/toggle
205 // endpoint. Strip it here so generic settings updates can never
206 // implicitly write a drop-in or modify wp-config.php.
207 unset( $params['cache_enabled'] );
208 $updated = Settings::update( $params );
209 return rest_ensure_response( $updated );
210 }
211
212 public function purge() {
213 Cache::purge_all();
214 return rest_ensure_response( array( 'stats' => Cache::get_stats() ) );
215 }
216
217 /**
218 * Resolved branding ({name, footer_credit, hide_help_links, logo_svg}).
219 * Runs the `xspeed_branding` filter so Pro's white-label override is
220 * reflected. Consumed by the dashboard's post-save branding refresh.
221 */
222 public function get_branding() {
223 return rest_ensure_response( Admin::branding() );
224 }
225
226 public function toggle_cache( \WP_REST_Request $request ) {
227 $params = $request->get_json_params();
228 $enabled = isset( $params['enabled'] ) ? (bool) $params['enabled'] : false;
229
230 // User-explicit drop-in install / wp-config.php edit happens here.
231 // permission_callback above already enforced current_user_can(
232 // 'manage_options' ); the REST nonce is verified by core via the
233 // X-WP-Nonce header.
234 $state = Cache::toggle( $enabled );
235 $updated = Settings::update( array( 'cache_enabled' => $state['enabled'] ) );
236
237 // Recompute the unified nginx block AFTER cache_enabled is persisted.
238 // Cache::toggle() computes it inline, but cache_enabled isn't written
239 // until the Settings::update() above — so the block inside $state
240 // reflects the PRE-toggle state (CacheModule::nginx_directives() gates
241 // on cache_enabled). Regenerate here so the dashboard's optimistic
242 // update shows the snippet for the state the user just selected.
243 $state['nginx_server_block'] = Cache::full_nginx_server_block();
244
245 return rest_ensure_response(
246 array(
247 'enabled' => $updated['cache_enabled'],
248 'stats' => Cache::get_stats(),
249 'install_state' => $state,
250 )
251 );
252 }
253 }
254