PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.0
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.0
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/modules/Health/HealthModule.php +121 -3 1.3.2 → 1.4.0 View file →
@@ -53,10 +53,20 @@
53 53 // Out-of-band refresh of the Set-Cookie probe. Health::checks()
54 54 // only ever reads the cached verdict, so the HTTP round-trip
55 55 // happens here instead of inside a request the user waits on.
56 56 add_action( \XSpeed\Cookie_Inspector::CRON_HOOK, array( $this, 'refresh_cookie_probe' ) );
57 +
58 + // Same pattern for the edge-mode probe: the page it requests is
59 + // answered here, and the two round-trips run from cron.
60 + \XSpeed\Edge_Mode_Probe::boot();
61 + add_action( \XSpeed\Edge_Mode_Probe::CRON_HOOK, array( $this, 'refresh_edge_mode_probe' ) );
57 62 }
58 63
64 + /** Cron callback: ask the edge whether it obeys "do not store". */
65 + public function refresh_edge_mode_probe(): void {
66 + \XSpeed\Edge_Mode_Probe::run();
67 + }
68 +
59 69 /** Cron callback: perform the real (blocking) probe off-request. */
60 70 public function refresh_cookie_probe(): void {
61 71 \XSpeed\Cookie_Inspector::probe( true );
62 72 }
@@ -75,8 +85,52 @@
75 85 * embedded when nginx is detected. Skipped entirely when cache is
76 86 * disabled — no point telling the user to install a rewrite they
77 87 * haven't opted into.
78 88 */
89 + /**
90 + * Decide what the nginx static rewrite is actually doing.
91 + *
92 + * Extracted so the ordering is testable without a WordPress bootstrap,
93 + * and so Site Health and the dashboard Health panel cannot drift apart
94 + * again — the whole point of #480.
95 + *
96 + * Returns one of: 'active', 'mobile_separate', 'skipped_nonce',
97 + * 'unverified', 'required'.
98 + *
99 + * @param array<string, mixed> $probe probe_static_rewrite() result.
100 + * @param string $block_reason A known refusal, or ''.
101 + */
102 + public static function nginx_rewrite_verdict( array $probe, string $block_reason ): string {
103 + $is_active = (bool) ( $probe['active'] ?? false );
104 + $inconclusive = (bool) ( $probe['inconclusive'] ?? false );
105 +
106 + // A known refusal OUTRANKS the probe. probe_static_rewrite() writes
107 + // its own file under the static-cache dir and fetches that, which
108 + // succeeds whenever the server can serve a static file at all — even
109 + // when no real page is on the static path. It also outranks
110 + // "inconclusive", so a blocked rewrite whose probe merely failed to
111 + // complete is reported as the refusal it is. (FBS-83145)
112 + if ( '' !== $block_reason ) {
113 + if ( 'mobile_separate' === $block_reason ) {
114 + return 'mobile_separate';
115 + }
116 + if ( 'skipped_nonce' === $block_reason ) {
117 + return 'skipped_nonce';
118 + }
119 + return 'required';
120 + }
121 +
122 + if ( $is_active ) {
123 + return 'active';
124 + }
125 +
126 + // The probe never reached a verdict (blocked loopback, self-signed
127 + // cert, timeout, a CDN/WAF answering instead of the origin). That is
128 + // not evidence the config is wrong, and must not produce a
129 + // "paste this snippet" banner. (FBS-84012, #480)
130 + return $inconclusive ? 'unverified' : 'required';
131 + }
132 +
79 133 public function site_status_static_rewrite(): array {
80 134 $result = array(
81 135 'label' => __( 'xSpeed static-rewrite cache is active', 'xspeed' ),
82 136 'status' => 'good',
@@ -103,9 +157,16 @@
103 157 // "toggle Enable Cache off and on" cannot reinstall anything —
104 158 // the same condition suppresses the write and auto_heal() strips
105 159 // the block again on the next admin load. Explain the real cause.
106 160 $block_reason = \XSpeed\Cache::static_rewrite_block_reason();
107 - if ( 'no_mod_headers' === $block_reason ) {
161 + if ( 'litespeed_dropin' === $block_reason ) {
162 + // The intended LiteSpeed default (#509) — 'good', not a nag:
163 + // hits are visible and counted, and the faster path is a
164 + // deliberate opt-in, not a missing config.
165 + $result['label'] = __( 'xSpeed is serving cache hits through PHP (LiteSpeed)', 'xspeed' );
166 + $result['status'] = 'good';
167 + $result['description'] = '<p>' . esc_html__( 'Caching is working — hits are served by the xSpeed drop-in and tagged X-XSpeed-Cache: HIT (php), so every hit is visible and counted. LiteSpeed\'s .htaccess engine cannot tag or log statically served files, so this is the default. To serve hits straight from the web server with no PHP (at the cost of that tagging and counting), turn on LiteSpeed Static Fast Path in xSpeed\'s Cache settings.', 'xspeed' ) . '</p>';
168 + } elseif ( 'no_mod_headers' === $block_reason ) {
108 169 $result['label'] = __( 'xSpeed is serving cache hits through PHP', 'xspeed' );
109 170 $result['status'] = 'recommended';
110 171 $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 172 } elseif ( 'mobile_separate' === $block_reason ) {
@@ -111,9 +172,11 @@
111 172 } elseif ( 'mobile_separate' === $block_reason ) {
112 173 $result['label'] = __( 'xSpeed static rewrite is off (Separate Mobile Cache)', 'xspeed' );
113 174 $result['status'] = 'recommended';
114 175 $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() ) {
176 + } elseif ( '' === $block_reason && ! \XSpeed\Cache::rewrite_installed() ) {
177 + // Apache, or LiteSpeed with the Static Fast Path opt-in on
178 + // (#509) — either way the block SHOULD be there and is not.
116 179 $result['label'] = __( 'xSpeed .htaccess rewrite block is missing', 'xspeed' );
117 180 $result['status'] = 'recommended';
118 181 $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>';
119 182 }
@@ -120,8 +183,62 @@
120 183 return $result;
121 184 }
122 185
123 186 if ( \XSpeed\Server::NGINX === $server_type ) {
187 + // Ask the same question the dashboard Health panel asks, the same
188 + // way. This test used to return "config required" unconditionally,
189 + // so every correctly-configured nginx site — every xCloud site,
190 + // where the panel installs the block for you — was told to paste a
191 + // snippet it already had, and re-running the check never cleared
192 + // it. Worse, the dashboard said the opposite at the same moment.
193 + // Reuse probe_static_rewrite() + the refusal reasons so the two
194 + // surfaces cannot disagree. (#480)
195 + $probe = \XSpeed\Cache::probe_static_rewrite( true );
196 + $probe_reason = (string) ( $probe['reason'] ?? '' );
197 +
198 + $block_reason = \XSpeed\Cache::static_rewrite_block_reason();
199 + $skip = \XSpeed\Cache::last_static_skip();
200 + if ( '' === $block_reason && ! empty( $skip['reason'] ) ) {
201 + $block_reason = 'skipped_' . (string) $skip['reason'];
202 + }
203 +
204 + switch ( self::nginx_rewrite_verdict( $probe, $block_reason ) ) {
205 + case 'active':
206 + $result['label'] = __( 'xSpeed nginx static rewrite is active', 'xspeed' );
207 + $result['status'] = 'good';
208 + $result['description'] = '<p>' . esc_html__( 'nginx is serving cache hits directly — PHP is bypassed (~5-15ms TTFB). No action needed.', 'xspeed' ) . '</p>';
209 + return $result;
210 +
211 + case 'mobile_separate':
212 + $result['label'] = __( 'xSpeed static rewrite is off (Separate Mobile Cache)', 'xspeed' );
213 + $result['status'] = 'recommended';
214 + $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>';
215 + return $result;
216 +
217 + case 'skipped_nonce':
218 + $result['label'] = __( 'xSpeed is serving cache hits through PHP (pages contain nonces)', 'xspeed' );
219 + $result['status'] = 'recommended';
220 + $result['description'] = '<p>' . esc_html__( 'Your nginx config is correct, but pages are not reaching the static cache, so hits are served by PHP. They contain nonces, and a static file is served with no PHP — nothing could ever refresh them, so every anonymous form on the page would break once they expire. Keeping these pages on PHP is deliberate.', 'xspeed' ) . '</p>';
221 + return $result;
222 +
223 + case 'unverified':
224 + // The probe never reached a verdict (blocked loopback,
225 + // self-signed cert, timeout, a CDN/WAF answering instead of
226 + // the origin). Not evidence the config is wrong, so don't
227 + // say "required" and don't dump a snippet the user has
228 + // probably already pasted. (FBS-84012, and why #480 was filed.)
229 + $result['label'] = __( 'xSpeed could not verify the nginx static rewrite', 'xspeed' );
230 + $result['status'] = 'recommended';
231 + $result['description'] = '<p>' . esc_html(
232 + sprintf(
233 + /* translators: %s: the reason the probe could not complete. */
234 + __( 'The check itself did not complete, so this is not evidence that your config is wrong — if you have already pasted the snippet it may well be working. Reason: %s', 'xspeed' ),
235 + $probe_reason
236 + )
237 + ) . '</p>';
238 + return $result;
239 + }
240 +
124 241 $snippet = \XSpeed\Cache::nginx_snippet();
125 242 $result['label'] = __( 'xSpeed nginx server config required', 'xspeed' );
126 243 $result['status'] = 'recommended';
127 244 $result['description'] = '<p>' . esc_html__( 'xSpeed can\'t write nginx config from PHP. Paste this snippet into your site\'s server { } block, then reload nginx so cache hits serve without booting PHP:', 'xspeed' ) . '</p>'
@@ -142,9 +259,9 @@
142 259 public function ui_metadata(): array {
143 260 return array(
144 261 'label' => __( 'Health', 'xspeed' ),
145 262 'icon' => 'HeartPulse',
146 - 'description' => __( 'Diagnostics, hit ratio, and recent cache activity.', 'xspeed' ),
263 + 'description' => __( 'Checks for problems and shows how often visitors get cached pages.', 'xspeed' ),
147 264 // Health is the single host page for all Insights (FBS-83633):
148 265 // a Recommendations action card + Cache / Visitors / PageSpeed
149 266 // tabs. HealthPanel renders the Free cache diagnostics (the old
150 267 // HealthCard) as the Cache tab and hosts the Pro insight panels
@@ -149,8 +266,9 @@
149 266 // tabs. HealthPanel renders the Free cache diagnostics (the old
150 267 // HealthCard) as the Cache tab and hosts the Pro insight panels
151 268 // as the other tabs via ProSlot.
152 269 'custom_panel' => 'HealthPanel',
270 + 'group' => 'insights',
153 271 );
154 272 }
155 273
156 274 // No settings — explicit empty so Module::rest_routes() doesn't