PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.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
xspeed / includes / class-health.php

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

243 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Health — shared diagnostic checks consumed by both Onboarding's
4 * environment surface and the Health module's dashboard panel.
5 *
6 * Each check returns:
7 * [
8 * 'id' => 'wp_version',
9 * 'tone' => 'ok' | 'warn' | 'fail' | 'info',
10 * 'label' => 'WordPress 6.6',
11 * 'detail' => 'Meets the 6.0+ minimum.',
12 * ]
13 *
14 * Pure reads — never writes to disk, never makes outbound HTTP calls.
15 * Safe to call from any request including the loading dashboard.
16 *
17 * @package XSpeed
18 */
19
20 declare(strict_types=1);
21
22 namespace XSpeed;
23
24 defined( 'ABSPATH' ) || exit;
25
26 final class Health {
27
28 public const OK = 'ok';
29 public const WARN = 'warn';
30 public const FAIL = 'fail';
31 public const INFO = 'info';
32
33 private const SERVER_LABELS = array(
34 'apache' => 'Apache',
35 'litespeed' => 'LiteSpeed',
36 'nginx' => 'nginx',
37 'iis' => 'IIS',
38 'unknown' => 'Unknown',
39 );
40
41 /**
42 * Full check list used by the Health module's dashboard panel.
43 *
44 * @return array<int,array{id:string,tone:string,label:string,detail:string}>
45 */
46 public static function checks(): array {
47 global $wp_version;
48
49 $server_type = Server::type();
50 $gzip_mode = Server::gzip_mode();
51 $conflicts = Server::conflicts();
52 $cache_dir = defined( 'XSPEED_CACHE_DIR' ) ? XSPEED_CACHE_DIR : ( WP_CONTENT_DIR . '/cache/xspeed' );
53
54 $out = array();
55
56 // WordPress version
57 $wp_ok = version_compare( (string) $wp_version, '6.0', '>=' );
58 $out[] = array(
59 'id' => 'wp_version',
60 'tone' => $wp_ok ? self::OK : self::FAIL,
61 'label' => sprintf( 'WordPress %s', (string) $wp_version ),
62 'detail' => $wp_ok ? 'Meets the 6.0+ minimum.' : 'Upgrade to WordPress 6.0 or higher.',
63 );
64
65 // PHP version
66 $php_ok = version_compare( PHP_VERSION, '7.4', '>=' );
67 $php_modern = version_compare( PHP_VERSION, '8.1', '>=' );
68 $out[] = array(
69 'id' => 'php_version',
70 'tone' => $php_modern ? self::OK : ( $php_ok ? self::WARN : self::FAIL ),
71 'label' => sprintf( 'PHP %s', PHP_VERSION ),
72 'detail' => $php_modern
73 ? 'Modern PHP — full speed.'
74 : ( $php_ok
75 ? 'Works, but 8.1+ is recommended for best performance.'
76 : 'Upgrade to PHP 7.4 or higher.' ),
77 );
78
79 // Server
80 $out[] = array(
81 'id' => 'server',
82 'tone' => self::INFO,
83 'label' => sprintf( 'Server: %s', self::SERVER_LABELS[ $server_type ] ?? 'Unknown' ),
84 'detail' => 'auto' === $gzip_mode
85 ? 'GZIP can be auto-configured via .htaccess.'
86 : 'GZIP requires a manual server-config snippet (shown in the GZIP module).',
87 );
88
89 // Cache directory writable
90 $dir_writable = wp_mkdir_p( $cache_dir ) && wp_is_writable( $cache_dir );
91 $out[] = array(
92 'id' => 'cache_dir',
93 'tone' => $dir_writable ? self::OK : self::FAIL,
94 'label' => 'Cache directory writable',
95 'detail' => $dir_writable
96 ? $cache_dir
97 : sprintf( 'Cannot write to %s. Adjust file permissions before enabling cache.', $cache_dir ),
98 );
99
100 // Drop-in installed (only when cache is enabled — otherwise N/A)
101 $cache_enabled = (bool) Settings::get()['cache_enabled'];
102 if ( $cache_enabled ) {
103 $dropin_path = WP_CONTENT_DIR . '/advanced-cache.php';
104 $dropin_match = file_exists( $dropin_path ) && false !== strpos( (string) file_get_contents( $dropin_path ), 'xspeed' );
105 $out[] = array(
106 'id' => 'dropin',
107 'tone' => $dropin_match ? self::OK : self::FAIL,
108 'label' => 'advanced-cache.php drop-in',
109 'detail' => $dropin_match
110 ? 'Installed and owned by xSpeed.'
111 : 'Drop-in missing or owned by another plugin. Toggle Enable Cache off and on to reinstall.',
112 );
113 }
114
115 // WP_CACHE constant
116 $wp_cache_const = defined( 'WP_CACHE' ) && WP_CACHE;
117 if ( $cache_enabled ) {
118 $out[] = array(
119 'id' => 'wp_cache_constant',
120 'tone' => $wp_cache_const ? self::OK : self::WARN,
121 'label' => 'WP_CACHE constant',
122 'detail' => $wp_cache_const
123 ? 'Defined and truthy in wp-config.php.'
124 : 'Not set. Cache is configured but WordPress will not load the drop-in until WP_CACHE = true is added to wp-config.php.',
125 );
126 }
127
128 // Static-rewrite probe. Active end-to-end check: writes a probe
129 // file under the static-cache dir, fetches it over HTTP, and
130 // confirms the web server (nginx OR Apache/LiteSpeed) served
131 // the raw file. Result is throttled to a 5-minute transient
132 // inside Cache::probe_static_rewrite so we never hit the
133 // network per-paint.
134 if ( $cache_enabled ) {
135 $server_type = Server::type();
136 $probe = Cache::probe_static_rewrite();
137 $is_active = (bool) ( $probe['active'] ?? false );
138
139 if ( Server::NGINX === $server_type ) {
140 $out[] = array(
141 'id' => 'static_rewrite_nginx',
142 'tone' => $is_active ? self::OK : self::WARN,
143 'label' => 'Static-file rewrite (nginx server config)',
144 'detail' => $is_active
145 ? 'nginx is serving cache hits directly — PHP bypassed (~5-15ms TTFB).'
146 : 'nginx detected but not yet routing to the cache. Paste the snippet below into your site\'s server { } block, then reload nginx.',
147 // Always ship the snippet — even when active, so the
148 // admin has it handy for re-pasting after a server
149 // rebuild without having to find it elsewhere. Mirror
150 // the SAME unified block the "Server config" panel
151 // renders (cache + gzip + browser-cache directives),
152 // not the cache-only snippet — otherwise Health and
153 // the Cache panel disagree on what to paste.
154 'snippet' => Cache::full_nginx_server_block(),
155 );
156 } elseif ( Server::APACHE === $server_type || Server::LITESPEED === $server_type ) {
157 $installed = Cache::rewrite_installed();
158 if ( $is_active ) {
159 $tone = self::OK;
160 $detail = 'Block installed and serving cache hits directly — PHP bypassed.';
161 } elseif ( ! $installed ) {
162 $tone = self::WARN;
163 $detail = 'Block missing from .htaccess. Toggle Enable Cache off and on to reinstall it.';
164 } else {
165 $tone = self::WARN;
166 $detail = sprintf( 'Block installed but probe failed (%s). Check that the .htaccess block is at the TOP of the file and that AllowOverride is enabled.', (string) ( $probe['reason'] ?? 'unknown' ) );
167 }
168 $out[] = array(
169 'id' => 'static_rewrite',
170 'tone' => $tone,
171 'label' => 'Static-file rewrite (.htaccess)',
172 'detail' => $detail,
173 );
174 }
175 }
176
177 // Permalinks
178 $permalinks_ok = (bool) get_option( 'permalink_structure' );
179 $out[] = array(
180 'id' => 'permalinks',
181 'tone' => $permalinks_ok ? self::OK : self::WARN,
182 'label' => 'Permalinks',
183 'detail' => $permalinks_ok
184 ? 'Pretty permalinks active.'
185 : 'Set permalinks to anything other than "Plain" — page caching needs URL paths to key on.',
186 );
187
188 // Conflicting plugins
189 $out[] = array(
190 'id' => 'conflicts',
191 'tone' => empty( $conflicts ) ? self::OK : self::WARN,
192 'label' => 'Caching plugin conflicts',
193 'detail' => empty( $conflicts )
194 ? 'No other caching plugins detected.'
195 : sprintf( 'Active: %s. Deactivate before enabling xSpeed cache to avoid double-caching.', implode( ', ', $conflicts ) ),
196 );
197
198 return $out;
199 }
200
201 /**
202 * Lightweight environment payload for the onboarding wizard. Keeps
203 * the legacy shape Onboarding::env_payload returned so the Welcome
204 * step's HealthRow rendering doesn't change.
205 */
206 public static function env_payload(): array {
207 global $wp_version;
208 $cache_dir = defined( 'XSPEED_CACHE_DIR' ) ? XSPEED_CACHE_DIR : ( WP_CONTENT_DIR . '/cache/xspeed' );
209 return array(
210 'wp' => array(
211 'version' => (string) $wp_version,
212 'ok' => version_compare( (string) $wp_version, '6.0', '>=' ),
213 ),
214 'php' => array(
215 'version' => PHP_VERSION,
216 'ok' => version_compare( PHP_VERSION, '7.4', '>=' ),
217 'modern' => version_compare( PHP_VERSION, '8.1', '>=' ),
218 ),
219 'server' => array(
220 'type' => Server::type(),
221 'gzip_mode' => Server::gzip_mode(),
222 ),
223 'cache_dir' => array(
224 'path' => $cache_dir,
225 'writable' => wp_mkdir_p( $cache_dir ) && wp_is_writable( $cache_dir ),
226 ),
227 'wp_config' => array(
228 'writable' => self::wp_config_writable(),
229 ),
230 'permalinks_ok' => (bool) get_option( 'permalink_structure' ),
231 'conflicts' => Server::conflicts(),
232 );
233 }
234
235 private static function wp_config_writable(): bool {
236 $path = ABSPATH . 'wp-config.php';
237 if ( ! file_exists( $path ) ) {
238 $path = dirname( ABSPATH ) . '/wp-config.php';
239 }
240 return file_exists( $path ) && wp_is_writable( $path );
241 }
242 }
243