PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.1
1.4.1 1.4.0 1.3.7 1.3.6 1.3.5 1.3.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 All 35 releases
← All changes | includes/class-rest-api.php +97 -8 1.2.4 → 1.4.1 View file →
@@ -90,10 +90,29 @@
90 90 'permission_callback' => array( $this, 'permissions' ),
91 91 )
92 92 );
93 93
94 + // The server-side mirror of "I pasted the block". Only meaningful on a
95 + // host where the probe cannot read the rules back — see
96 + // nginx_copied_hash().
94 97 register_rest_route(
95 98 self::NAMESPACE_V1,
99 + '/cache/nginx-copied-hash',
100 + array(
101 + 'methods' => 'POST',
102 + 'callback' => array( $this, 'nginx_copied_hash' ),
103 + 'permission_callback' => array( $this, 'permissions' ),
104 + 'args' => array(
105 + 'hash' => array(
106 + 'type' => 'string',
107 + 'required' => true,
108 + ),
109 + ),
110 + )
111 + );
112 +
113 + register_rest_route(
114 + self::NAMESPACE_V1,
96 115 '/cache/benchmark',
97 116 array(
98 117 'methods' => 'GET',
99 118 'callback' => array( $this, 'benchmark' ),
@@ -180,8 +199,17 @@
180 199 array(
181 200 'methods' => 'GET',
182 201 'callback' => array( $this, 'recommendations' ),
183 202 'permission_callback' => array( $this, 'permissions' ),
203 + 'args' => array(
204 + // `contributed` adds the entries other plugins hand in
205 + // through `xspeed_recommendations`. Only the Overview
206 + // card asks for them; see all_with_contributed().
207 + 'include' => array(
208 + 'type' => 'string',
209 + 'enum' => array( '', 'contributed' ),
210 + ),
211 + ),
184 212 )
185 213 );
186 214
187 215 register_rest_route(
@@ -340,10 +368,12 @@
340 368 }
341 369
342 370 /** Ranked "next best action" recommendations (issue #48). */
343 371 public function recommendations( $request ) {
344 - unset( $request );
345 - return rest_ensure_response( array( 'recommendations' => Recommendations::all() ) );
372 + $recs = 'contributed' === (string) $request->get_param( 'include' )
373 + ? Recommendations::all_with_contributed()
374 + : Recommendations::all();
375 + return rest_ensure_response( array( 'recommendations' => $recs ) );
346 376 }
347 377
348 378 /** One-click apply of a recommendation's settings fix. */
349 379 public function recommendations_apply( $request ) {
@@ -410,8 +440,14 @@
410 440 'server_type' => $server_type,
411 441 'snippet' => Cache::nginx_snippet(),
412 442 'topology' => Server::rewrite_topology(),
413 443 'behind_proxy' => Server::is_behind_proxy(),
444 + // Whether the rules the web server is running are the rules
445 + // these settings generate — `current`, `stale`, `absent` or
446 + // `unknown`, with the hashes both sides compared. Nothing
447 + // else can answer that: the nginx block lives in a server
448 + // config WordPress cannot read. See Cache::rules_state().
449 + 'rules' => Cache::rules_state( $probe ),
414 450 );
415 451 }
416 452
417 453 return rest_ensure_response(
@@ -425,8 +461,13 @@
425 461 'nginx_snippet' => Gzip::nginx_snippet(),
426 462 ),
427 463 'rewrite_probe' => $rewrite_probe,
428 464 'nginx_server_block' => Cache::full_nginx_server_block(),
465 + // What enabling the page cache would do to
466 + // wp-content/advanced-cache.php. The dashboard discloses the
467 + // replacement BEFORE the write when a leftover drop-in is
468 + // already there; see Page_Cache_Detector::dropin_disclosure().
469 + 'dropin' => Page_Cache_Detector::dropin_disclosure(),
429 470 // Separate Mobile Cache visibility (FBS-83145). `blocking` is
430 471 // true when mobile_separate is what's keeping the device-blind
431 472 // static fast path from installing on a rewrite-capable server;
432 473 // `needs_review` is true when a migration turned it on for us and
@@ -433,9 +474,16 @@
433 474 // the user hasn't confirmed they actually need it. The dashboard
434 475 // renders a callout (+ "Check now" equality probe) from these.
435 476 'mobile_separate' => array(
436 477 'enabled' => ! empty( Settings::get()['cache_enabled'] ) ? (bool) ( Settings_Manager::get( 'cache' )['mobile_separate'] ?? false ) : false,
437 - 'blocking' => $rewrite_capable && 'mobile_separate' === Cache::static_rewrite_block_reason(),
478 + // Gated to servers that HAVE a static fast path — see the
479 + // matching comment in Admin::bootstrap_payload(): on IIS /
480 + // unknown, block_reason still falls through to
481 + // mobile_separate and reporting it as "blocking" would nag
482 + // about a rewrite that does not exist there (#108).
483 + // LiteSpeed joined the capable set with the opt-in (#509).
484 + 'blocking' => ( $rewrite_capable || Server::LITESPEED === $server_type )
485 + && 'mobile_separate' === Cache::static_rewrite_block_reason(),
438 486 'needs_review' => Cache::mobile_separate_needs_review(),
439 487 ),
440 488 )
441 489 );
@@ -459,13 +507,19 @@
459 507 return rest_ensure_response( $updated );
460 508 }
461 509
462 510 public function purge() {
463 - // purge_type( 'all' ) rather than purge_all() so the dashboard button
464 - // behaves identically to the admin-bar "Purge All" — including
465 - // clearing third-party render caches (Render_Caches).
466 - Cache::purge_type( 'all', __( 'dashboard', 'xspeed' ) );
467 - return rest_ensure_response( array( 'stats' => Cache::get_stats() ) );
511 + // The same core function `wp xspeed purge` runs, so the dashboard
512 + // button and the CLI cannot clear different sets of stores — and the
513 + // per-store report is available here for the UI to surface a store
514 + // that was skipped or refused rather than flashing "cache cleared".
515 + $report = Purge_Runner::run( array( 'all' ), __( 'dashboard', 'xspeed' ) );
516 + return rest_ensure_response(
517 + array(
518 + 'stats' => Cache::get_stats(),
519 + 'report' => $report,
520 + )
521 + );
468 522 }
469 523
470 524 /**
471 525 * The "Cached Pages" drill-down: which pages are cached and how old they
@@ -530,10 +584,45 @@
530 584 'server_type' => $server_type,
531 585 'snippet' => Cache::nginx_snippet(),
532 586 'topology' => Server::rewrite_topology(),
533 587 'behind_proxy' => Server::is_behind_proxy(),
588 + // The whole reason to re-run the probe is usually that the
589 + // user just pasted the block, so this is where they most need
590 + // to be told whether the installed rules are the current ones.
591 + // It was only ever on /status before, which the CLI and MCP
592 + // recheck paths never call. See Cache::rules_state().
593 + 'rules' => $probe['rules'],
534 594 )
535 595 );
596 + }
597 +
598 + /**
599 + * Remember that this admin copied the current rules block.
600 + *
601 + * The mirror of the panel's own localStorage note, for the one case that
602 + * note cannot cover: a host where the probe returns `unknown` — blocked
603 + * loopback, or a CDN answering it — and a second admin, or the same admin
604 + * on another machine, is otherwise told to paste a block that is already
605 + * installed. Stored per user because it is a claim a person made.
606 + *
607 + * The hash is the rules marker, and a value that is not one is refused
608 + * rather than stored: the mirror is only useful while it holds something
609 + * rules_marker_expected() could also produce.
610 + */
611 + public function nginx_copied_hash( \WP_REST_Request $request ) {
612 + $params = (array) $request->get_json_params();
613 + $hash = isset( $params['hash'] ) ? (string) $params['hash'] : (string) $request->get_param( 'hash' );
614 +
615 + $stored = Cache::remember_rules_copied( $hash );
616 + if ( null === $stored ) {
617 + return new \WP_Error(
618 + 'xspeed_invalid_rules_hash',
619 + __( 'That is not a rules marker this site could have generated.', 'xspeed' ),
620 + array( 'status' => 400 )
621 + );
622 + }
623 +
624 + return rest_ensure_response( array( 'copied' => $stored ) );
536 625 }
537 626
538 627 public function toggle_cache( \WP_REST_Request $request ) {
539 628 $params = $request->get_json_params();