PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.2
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
← All changes | includes/modules/Health/HealthModule.php +85 -7 1.0.31.3.2 View file →
@@ -24,8 +24,10 @@
24 24 declare(strict_types=1);
25 25
26 26 namespace XSpeed\Modules\Health;
27 27
28 +defined( 'ABSPATH' ) || exit;
29 +
28 30 use XSpeed\Activity_Log;
29 31 use XSpeed\Cache;
30 32 use XSpeed\Health;
31 33 use XSpeed\Hit_Counter;
@@ -46,10 +48,20 @@
46 48 * vs the optimal cache hit path.
47 49 */
48 50 public function boot(): void {
49 51 add_filter( 'site_status_tests', array( $this, 'register_site_status_tests' ) );
52 +
53 + // Out-of-band refresh of the Set-Cookie probe. Health::checks()
54 + // only ever reads the cached verdict, so the HTTP round-trip
55 + // happens here instead of inside a request the user waits on.
56 + add_action( \XSpeed\Cookie_Inspector::CRON_HOOK, array( $this, 'refresh_cookie_probe' ) );
50 57 }
51 58
59 + /** Cron callback: perform the real (blocking) probe off-request. */
60 + public function refresh_cookie_probe(): void {
61 + \XSpeed\Cookie_Inspector::probe( true );
62 + }
63 +
52 64 public function register_site_status_tests( array $tests ): array {
53 65 $tests['direct']['xspeed_static_rewrite'] = array(
54 66 'label' => __( 'xSpeed static-rewrite cache', 'xspeed' ),
55 67 'test' => array( $this, 'site_status_static_rewrite' ),
@@ -85,9 +97,23 @@
85 97 }
86 98
87 99 $server_type = \XSpeed\Server::type();
88 100 if ( \XSpeed\Server::APACHE === $server_type || \XSpeed\Server::LITESPEED === $server_type ) {
89 - if ( ! \XSpeed\Cache::rewrite_installed() ) {
101 + // Only call the block "missing" when it is genuinely absent by
102 + // accident. When static_rewrite_allowed() deliberately refused it,
103 + // "toggle Enable Cache off and on" cannot reinstall anything —
104 + // the same condition suppresses the write and auto_heal() strips
105 + // the block again on the next admin load. Explain the real cause.
106 + $block_reason = \XSpeed\Cache::static_rewrite_block_reason();
107 + if ( 'no_mod_headers' === $block_reason ) {
108 + $result['label'] = __( 'xSpeed is serving cache hits through PHP', 'xspeed' );
109 + $result['status'] = 'recommended';
110 + $result['description'] = '<p>' . esc_html__( 'Caching is working — hits are served by the xSpeed drop-in and tagged X-XSpeed-Cache: HIT (php). The faster .htaccess fast path is off because Apache\'s mod_headers module is not loaded, without which a static hit could not be tagged or counted. Enable mod_headers (a2enmod headers on Debian/Ubuntu, then restart Apache) to shave roughly 20-30ms off each cache hit.', 'xspeed' ) . '</p>';
111 + } elseif ( 'mobile_separate' === $block_reason ) {
112 + $result['label'] = __( 'xSpeed static rewrite is off (Separate Mobile Cache)', 'xspeed' );
113 + $result['status'] = 'recommended';
114 + $result['description'] = '<p>' . esc_html__( 'Separate Mobile Cache is on, so cache hits are served by the PHP drop-in to keep per-device HTML correct. Turn Separate Mobile Cache off if your site serves the same HTML to every device to regain the faster static path.', 'xspeed' ) . '</p>';
115 + } elseif ( \XSpeed\Server::APACHE === $server_type && ! \XSpeed\Cache::rewrite_installed() ) {
90 116 $result['label'] = __( 'xSpeed .htaccess rewrite block is missing', 'xspeed' );
91 117 $result['status'] = 'recommended';
92 118 $result['description'] = '<p>' . esc_html__( 'Without the static-rewrite block, cache hits go through the PHP drop-in (~85ms TTFB) instead of the web server (~5-15ms). Toggle Enable Cache off and on in xSpeed to reinstall the block.', 'xspeed' ) . '</p>';
93 119 }
@@ -114,15 +140,17 @@
114 140 }
115 141
116 142 public function ui_metadata(): array {
117 143 return array(
118 - 'label' => 'Health',
144 + 'label' => __( 'Health', 'xspeed' ),
119 145 'icon' => 'HeartPulse',
120 - 'description' => 'Diagnostics, hit ratio, and recent cache activity.',
121 - // Tells the React side to render HealthCard instead of
122 - // schema-driven settings (SETTINGS.md §6.2 allows custom
123 - // panels for non-settings surfaces).
124 - 'custom_panel' => 'HealthCard',
146 + 'description' => __( 'Diagnostics, hit ratio, and recent cache activity.', 'xspeed' ),
147 + // Health is the single host page for all Insights (FBS-83633):
148 + // a Recommendations action card + Cache / Visitors / PageSpeed
149 + // tabs. HealthPanel renders the Free cache diagnostics (the old
150 + // HealthCard) as the Cache tab and hosts the Pro insight panels
151 + // as the other tabs via ProSlot.
152 + 'custom_panel' => 'HealthPanel',
125 153 );
126 154 }
127 155
128 156 // No settings — explicit empty so Module::rest_routes() doesn't
@@ -146,11 +174,61 @@
146 174 array(
147 175 'name' => 'xspeed health',
148 176 'callback' => array( $this, 'cli_handler' ),
149 177 'shortdesc' => 'Print diagnostic checks + cache stats + recent activity.',
178 + 'ai_hint' => 'Full diagnostic sweep: what is misconfigured or degraded on this site right now, plus cache stats and recent activity. The best FIRST call for open-ended "why is my site slow" or "is anything wrong" questions.',
150 179 'synopsis' => array(),
151 180 ),
181 + array(
182 + 'name' => 'xspeed recommend',
183 + 'callback' => array( $this, 'cli_recommend' ),
184 + 'shortdesc' => 'List ranked next-best-action recommendations, or apply one by id.',
185 + 'ai_hint' => 'The ranked list of what to do next to make this site faster, and the way to apply one. Use when asked "what should I improve" or "what\'s the biggest win" — each item is actionable and ordered by impact.',
186 + 'synopsis' => array(
187 + array(
188 + 'type' => 'positional',
189 + 'name' => 'action',
190 + 'options' => array( 'list', 'apply' ),
191 + 'optional' => true,
192 + ),
193 + array(
194 + 'type' => 'positional',
195 + 'name' => 'id',
196 + 'optional' => true,
197 + ),
198 + ),
199 + ),
152 200 );
201 + }
202 +
203 + /** CLI: `wp xspeed recommend [list|apply <id>]` — MCP-reachable via run_command. */
204 + public function cli_recommend( array $args, array $assoc ): void {
205 + $action = isset( $args[0] ) ? (string) $args[0] : 'list';
206 +
207 + if ( 'apply' === $action ) {
208 + $id = isset( $args[1] ) ? (string) $args[1] : '';
209 + if ( '' === $id ) {
210 + \WP_CLI::error( 'Usage: wp xspeed recommend apply <id>' );
211 + return;
212 + }
213 + $result = \XSpeed\Recommendations::apply( $id );
214 + if ( is_wp_error( $result ) ) {
215 + \WP_CLI::error( $result->get_error_message() );
216 + return;
217 + }
218 + \WP_CLI::success( sprintf( 'Applied "%s". %d recommendation(s) remain.', $id, count( $result['recommendations'] ) ) );
219 + return;
220 + }
221 +
222 + $recs = \XSpeed\Recommendations::all();
223 + if ( empty( $recs ) ) {
224 + \WP_CLI::success( 'No recommendations — configuration looks healthy.' );
225 + return;
226 + }
227 + foreach ( $recs as $i => $rec ) {
228 + $fixable = 'apply' === ( $rec['action']['type'] ?? '' ) ? ' (one-click: wp xspeed recommend apply ' . $rec['id'] . ')' : '';
229 + \WP_CLI::log( sprintf( '%d. [%s] %s — %s%s', $i + 1, $rec['id'], $rec['title'], $rec['detail'], $fixable ) );
230 + }
153 231 }
154 232
155 233 /**
156 234 * Single endpoint that backs the dashboard panel. Refreshed lazily by