PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.3
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
← All changes | includes/class-rest-api.php +180 -6 1.1.11.3.3 View file →
@@ -77,10 +77,23 @@
77 77 'permission_callback' => array( $this, 'permissions' ),
78 78 )
79 79 );
80 80
81 + // Force a fresh static-rewrite probe. The result is otherwise cached
82 + // for five minutes with nothing to invalidate it, so a user who just
83 + // fixed their nginx config had no way to confirm it. (FBS-84012)
81 84 register_rest_route(
82 85 self::NAMESPACE_V1,
86 + '/cache/recheck-rewrite',
87 + array(
88 + 'methods' => 'POST',
89 + 'callback' => array( $this, 'recheck_rewrite' ),
90 + 'permission_callback' => array( $this, 'permissions' ),
91 + )
92 + );
93 +
94 + register_rest_route(
95 + self::NAMESPACE_V1,
83 96 '/cache/benchmark',
84 97 array(
85 98 'methods' => 'GET',
86 99 'callback' => array( $this, 'benchmark' ),
@@ -97,10 +110,63 @@
97 110 'permission_callback' => array( $this, 'permissions' ),
98 111 )
99 112 );
100 113
114 + // Drill-downs behind the four dashboard stat cards. Each is a plain
115 + // GET so the same data reaches the CLI and MCP through
116 + // `wp xspeed cache inventory|size|purge-log`.
101 117 register_rest_route(
102 118 self::NAMESPACE_V1,
119 + '/cache/inventory',
120 + array(
121 + 'methods' => 'GET',
122 + 'callback' => array( $this, 'cache_inventory' ),
123 + 'permission_callback' => array( $this, 'permissions' ),
124 + 'args' => array(
125 + 'limit' => array(
126 + 'type' => 'integer',
127 + 'default' => 50,
128 + ),
129 + 'offset' => array(
130 + 'type' => 'integer',
131 + 'default' => 0,
132 + ),
133 + 'fresh' => array(
134 + 'type' => 'boolean',
135 + 'default' => false,
136 + ),
137 + ),
138 + )
139 + );
140 +
141 + register_rest_route(
142 + self::NAMESPACE_V1,
143 + '/cache/size',
144 + array(
145 + 'methods' => 'GET',
146 + 'callback' => array( $this, 'cache_size' ),
147 + 'permission_callback' => array( $this, 'permissions' ),
148 + )
149 + );
150 +
151 + register_rest_route(
152 + self::NAMESPACE_V1,
153 + '/cache/purge-log',
154 + array(
155 + 'methods' => 'GET',
156 + 'callback' => array( $this, 'cache_purge_log' ),
157 + 'permission_callback' => array( $this, 'permissions' ),
158 + 'args' => array(
159 + 'limit' => array(
160 + 'type' => 'integer',
161 + 'default' => 25,
162 + ),
163 + ),
164 + )
165 + );
166 +
167 + register_rest_route(
168 + self::NAMESPACE_V1,
103 169 '/stats/hit-daily',
104 170 array(
105 171 'methods' => 'GET',
106 172 'callback' => array( $this, 'hit_daily' ),
@@ -139,8 +205,18 @@
139 205 );
140 206
141 207 register_rest_route(
142 208 self::NAMESPACE_V1,
209 + '/activity',
210 + array(
211 + 'methods' => 'GET',
212 + 'callback' => array( $this, 'activity' ),
213 + 'permission_callback' => array( $this, 'permissions' ),
214 + )
215 + );
216 +
217 + register_rest_route(
218 + self::NAMESPACE_V1,
143 219 '/modules',
144 220 array(
145 221 'methods' => 'GET',
146 222 'callback' => array( $this, 'get_modules' ),
@@ -213,8 +289,20 @@
213 289 return rest_ensure_response( array( 'suggestions' => Pro_Audit::run() ) );
214 290 }
215 291
216 292 /**
293 + * Recent activity-log entries for the Overview activity strip —
294 + * newest-first (settings changes, purges, cache toggles, …).
295 + *
296 + * @param \WP_REST_Request $request Unused.
297 + * @return \WP_REST_Response
298 + */
299 + public function activity( $request ) {
300 + unset( $request );
301 + return rest_ensure_response( array( 'activity' => Activity_Log::entries() ) );
302 + }
303 +
304 + /**
217 305 * Cache before/after benchmark — fetches home_url() twice (with +
218 306 * without the bypass header) and returns side-by-side timings for
219 307 * the dashboard widget.
220 308 */
@@ -311,8 +399,15 @@
311 399 if ( $opts['cache_enabled'] && $rewrite_capable ) {
312 400 $probe = Cache::probe_static_rewrite();
313 401 $rewrite_probe = array(
314 402 'active' => (bool) ( $probe['active'] ?? false ),
403 + // Both flags were previously dropped here, so the dashboard
404 + // could not tell "proven inactive" from "no result yet" or
405 + // "probe failed" — and rendered the configure-your-server
406 + // banner for all three. (FBS-84012)
407 + 'pending' => (bool) ( $probe['pending'] ?? false ),
408 + 'inconclusive' => (bool) ( $probe['inconclusive'] ?? false ),
409 + 'reason' => (string) ( $probe['reason'] ?? '' ),
315 410 'server_type' => $server_type,
316 411 'snippet' => Cache::nginx_snippet(),
317 412 'topology' => Server::rewrite_topology(),
318 413 'behind_proxy' => Server::is_behind_proxy(),
@@ -330,8 +425,13 @@
330 425 'nginx_snippet' => Gzip::nginx_snippet(),
331 426 ),
332 427 'rewrite_probe' => $rewrite_probe,
333 428 'nginx_server_block' => Cache::full_nginx_server_block(),
429 + // What enabling the page cache would do to
430 + // wp-content/advanced-cache.php. The dashboard discloses the
431 + // replacement BEFORE the write when a leftover drop-in is
432 + // already there; see Page_Cache_Detector::dropin_disclosure().
433 + 'dropin' => Page_Cache_Detector::dropin_disclosure(),
334 434 // Separate Mobile Cache visibility (FBS-83145). `blocking` is
335 435 // true when mobile_separate is what's keeping the device-blind
336 436 // static fast path from installing on a rewrite-capable server;
337 437 // `needs_review` is true when a migration turned it on for us and
@@ -364,13 +464,47 @@
364 464 return rest_ensure_response( $updated );
365 465 }
366 466
367 467 public function purge() {
368 - Cache::purge_all();
369 - return rest_ensure_response( array( 'stats' => Cache::get_stats() ) );
468 + // The same core function `wp xspeed purge` runs, so the dashboard
469 + // button and the CLI cannot clear different sets of stores — and the
470 + // per-store report is available here for the UI to surface a store
471 + // that was skipped or refused rather than flashing "cache cleared".
472 + $report = Purge_Runner::run( array( 'all' ), __( 'dashboard', 'xspeed' ) );
473 + return rest_ensure_response(
474 + array(
475 + 'stats' => Cache::get_stats(),
476 + 'report' => $report,
477 + )
478 + );
370 479 }
371 480
372 481 /**
482 + * The "Cached Pages" drill-down: which pages are cached and how old they
483 + * are. Paginated because a busy site's cache is thousands of entries and
484 + * the answer to "is my cache working" doesn't need all of them at once.
485 + */
486 + public function cache_inventory( \WP_REST_Request $request ) {
487 + return rest_ensure_response(
488 + Cache_Inventory::entries(
489 + (int) $request->get_param( 'limit' ),
490 + (int) $request->get_param( 'offset' ),
491 + (bool) $request->get_param( 'fresh' )
492 + )
493 + );
494 + }
495 +
496 + /** The "Cache Size" drill-down: where the bytes actually go. */
497 + public function cache_size() {
498 + return rest_ensure_response( Cache_Inventory::size_breakdown() );
499 + }
500 +
501 + /** The "Last Purge" drill-down: what cleared the cache, when, and why. */
502 + public function cache_purge_log( \WP_REST_Request $request ) {
503 + return rest_ensure_response( Cache_Inventory::purge_log( (int) $request->get_param( 'limit' ) ) );
504 + }
505 +
506 + /**
373 507 * Resolved branding ({name, footer_credit, hide_help_links, logo_svg}).
374 508 * Runs the `xspeed_branding` filter so Pro's white-label override is
375 509 * reflected. Consumed by the dashboard's post-save branding refresh.
376 510 */
@@ -377,8 +511,42 @@
377 511 public function get_branding() {
378 512 return rest_ensure_response( Admin::branding() );
379 513 }
380 514
515 + /**
516 + * Re-run the static-rewrite probe, bypassing the cached result.
517 + *
518 + * Returns the same shape the dashboard bootstrap uses, so the caller can
519 + * swap it straight into state without a second round trip. (FBS-84012)
520 + */
521 + public function recheck_rewrite() {
522 + $raw = Cache::recheck_static_rewrite();
523 + $server_type = Server::detect();
524 +
525 + // Qualify the raw probe against known config refusals. The probe
526 + // fetches its own file from the static tree, which succeeds even when
527 + // no real page is served that way — so an unqualified `active` told
528 + // clients the static path was engaged on sites where it demonstrably
529 + // wasn't. `block_reason` is exposed so a client can act on the
530 + // specific cause rather than re-deriving it. See
531 + // Cache::qualify_rewrite_probe().
532 + $probe = Cache::qualify_rewrite_probe( $raw );
533 +
534 + return rest_ensure_response(
535 + array(
536 + 'active' => $probe['active'],
537 + 'pending' => (bool) ( $raw['pending'] ?? false ),
538 + 'inconclusive' => $probe['inconclusive'],
539 + 'reason' => $probe['reason'],
540 + 'block_reason' => $probe['block_reason'],
541 + 'server_type' => $server_type,
542 + 'snippet' => Cache::nginx_snippet(),
543 + 'topology' => Server::rewrite_topology(),
544 + 'behind_proxy' => Server::is_behind_proxy(),
545 + )
546 + );
547 + }
548 +
381 549 public function toggle_cache( \WP_REST_Request $request ) {
382 550 $params = $request->get_json_params();
383 551 $enabled = isset( $params['enabled'] ) ? (bool) $params['enabled'] : false;
384 552
@@ -386,9 +554,9 @@
386 554 // permission_callback above already enforced current_user_can(
387 555 // 'manage_options' ); the REST nonce is verified by core via the
388 556 // X-WP-Nonce header.
389 557 $state = Cache::toggle( $enabled );
390 - $updated = Settings::update( array( 'cache_enabled' => $state['enabled'] ) );
558 + $updated = Settings::get();
391 559
392 560 // Recompute the unified nginx block AFTER cache_enabled is persisted.
393 561 // Cache::toggle() computes it inline, but cache_enabled isn't written
394 562 // until the Settings::update() above — so the block inside $state
@@ -398,11 +566,17 @@
398 566 $state['nginx_server_block'] = Cache::full_nginx_server_block();
399 567
400 568 return rest_ensure_response(
401 569 array(
402 - 'enabled' => $updated['cache_enabled'],
403 - 'stats' => Cache::get_stats(),
404 - 'install_state' => $state,
570 + 'enabled' => $updated['cache_enabled'],
571 + // Surfaced at the top level so the dashboard can explain a
572 + // refusal rather than silently snapping the toggle back:
573 + // Cache::toggle() writes nothing when another plugin owns the
574 + // drop-in or WP_CACHE is in a shape we must not rewrite.
575 + 'blocked' => ! empty( $state['blocked'] ),
576 + 'blocked_reason' => $state['blocked_reason'] ?? null,
577 + 'stats' => Cache::get_stats(),
578 + 'install_state' => $state,
405 579 )
406 580 );
407 581 }
408 582 }