PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.1
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-server.php

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

149 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server / SAPI detection.
4 *
5 * Used by Gzip and the UI to decide which optimizations are server-applied
6 * (Apache / LiteSpeed via .htaccess) vs. require manual config (nginx).
7 *
8 * @package XSpeed
9 */
10
11 namespace XSpeed;
12
13 defined( 'ABSPATH' ) || exit;
14
15 class Server {
16
17 const APACHE = 'apache';
18 const LITESPEED = 'litespeed';
19 const NGINX = 'nginx';
20 const IIS = 'iis';
21 const UNKNOWN = 'unknown';
22
23 const OPT_CACHED_TYPE = 'xspeed_server_type';
24
25 public static function type() {
26 $detected = self::detect();
27 if ( self::UNKNOWN !== $detected ) {
28 // Persist whenever we have a real answer so future CLI /
29 // cron / REST calls (where SERVER_SOFTWARE may be empty)
30 // inherit it. Non-autoloaded — only read when needed.
31 $cached = get_option( self::OPT_CACHED_TYPE, null );
32 if ( $cached !== $detected ) {
33 update_option( self::OPT_CACHED_TYPE, $detected, false );
34 }
35 return $detected;
36 }
37
38 // No definitive signal this request (typically WP-CLI, where
39 // SERVER_SOFTWARE is empty). Read whatever was cached the last
40 // time we ran from a real HTTP request.
41 $cached = get_option( self::OPT_CACHED_TYPE, null );
42 if ( is_string( $cached ) && '' !== $cached ) {
43 return $cached;
44 }
45
46 return self::UNKNOWN;
47 }
48
49 /**
50 * Live detection — never reads the cache. Used by type() and by
51 * any caller that explicitly wants the current-request answer
52 * (e.g. diagnostic UI showing "detected this request").
53 *
54 * We DO NOT fall back to "if .htaccess exists assume Apache" here:
55 * Cache::install_rewrite() writes .htaccess itself, so on nginx
56 * hosts the file appears after first cache toggle and a presence
57 * check then flips us to APACHE forever. Cached HTTP detection
58 * is the cleaner backstop.
59 */
60 public static function detect(): string {
61 global $is_apache, $is_nginx, $is_IIS, $is_iis7;
62
63 $signature = self::server_signature();
64
65 if ( false !== stripos( $signature, 'litespeed' ) ) {
66 return self::LITESPEED;
67 }
68 // apache_get_modules() exists only with mod_php (not FPM), so
69 // gate it behind SERVER_SOFTWARE first. Otherwise an
70 // "apache_get_modules exists" check would false-positive on a
71 // few PHP-builtin-server / mod_php-on-localhost dev edge cases.
72 if ( false !== stripos( $signature, 'apache' ) || ! empty( $is_apache ) ) {
73 return self::APACHE;
74 }
75 if ( false !== stripos( $signature, 'nginx' ) || ! empty( $is_nginx ) ) {
76 return self::NGINX;
77 }
78 if ( false !== stripos( $signature, 'microsoft-iis' ) || ! empty( $is_IIS ) || ! empty( $is_iis7 ) ) {
79 return self::IIS;
80 }
81 return self::UNKNOWN;
82 }
83
84 /**
85 * Whether the server respects .htaccess / web.config-style file-based config.
86 */
87 public static function supports_htaccess() {
88 $t = self::type();
89 return self::APACHE === $t || self::LITESPEED === $t;
90 }
91
92 /**
93 * GZIP support category for the UI:
94 * 'auto' — toggling writes server config (Apache / LiteSpeed)
95 * 'manual' — must be configured outside the plugin (nginx, IIS, unknown)
96 */
97 public static function gzip_mode() {
98 return self::supports_htaccess() ? 'auto' : 'manual';
99 }
100
101 private static function server_signature() {
102 return isset( $_SERVER['SERVER_SOFTWARE'] )
103 ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) )
104 : '';
105 }
106
107 /**
108 * Detect active caching plugins that would conflict with xSpeed. Returns
109 * a list of human-readable labels for any conflicting plugin currently
110 * active; empty array means the field is clear. Used by the onboarding
111 * wizard's Step 1 health check and (Phase 2.1) the main dashboard's
112 * Health card.
113 *
114 * The detection key is the plugin's main file path relative to the
115 * plugins directory — the same value WordPress uses internally in
116 * `active_plugins`. Folder-only checks (`is_plugin_active('foo/')`)
117 * would false-positive on disabled plugins still on disk.
118 */
119 public static function conflicts() {
120 if ( ! function_exists( 'is_plugin_active' ) ) {
121 require_once ABSPATH . 'wp-admin/includes/plugin.php';
122 }
123
124 $known = array(
125 'wp-rocket/wp-rocket.php' => 'WP Rocket',
126 'w3-total-cache/w3-total-cache.php' => 'W3 Total Cache',
127 'wp-super-cache/wp-cache.php' => 'WP Super Cache',
128 'wp-fastest-cache/wpFastestCache.php' => 'WP Fastest Cache',
129 'litespeed-cache/litespeed-cache.php' => 'LiteSpeed Cache',
130 'cache-enabler/cache-enabler.php' => 'Cache Enabler',
131 'comet-cache/comet-cache.php' => 'Comet Cache',
132 'hummingbird-performance/wp-hummingbird.php' => 'Hummingbird',
133 'sg-cachepress/sg-cachepress.php' => 'SG Optimizer',
134 'breeze/breeze.php' => 'Breeze',
135 'autoptimize/autoptimize.php' => 'Autoptimize',
136 'flying-press/flying-press.php' => 'FlyingPress',
137 'nitropack/main.php' => 'NitroPack',
138 );
139
140 $active = array();
141 foreach ( $known as $file => $label ) {
142 if ( is_plugin_active( $file ) ) {
143 $active[] = $label;
144 }
145 }
146 return $active;
147 }
148 }
149