# xspeed/1.0.2/includes/class-server.php

xSpeed Cache: AI-Powered Performance Hub with MCP, Caching &amp; CDN, version 1.0.2. 149 lines.

- Page: https://pluginprobe.com/plugins/xspeed/1.0.2/code/includes/class-server.php
- Raw: https://pluginprobe.com/plugins/xspeed/1.0.2/raw/includes/class-server.php
- Modified: 2026-06-01T17:33:22+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/xspeed/1.0.2/code/includes/class-server.php#L10-L20`.

```php
<?php
/**
 * Server / SAPI detection.
 *
 * Used by Gzip and the UI to decide which optimizations are server-applied
 * (Apache / LiteSpeed via .htaccess) vs. require manual config (nginx).
 *
 * @package XSpeed
 */

namespace XSpeed;

defined( 'ABSPATH' ) || exit;

class Server {

	const APACHE    = 'apache';
	const LITESPEED = 'litespeed';
	const NGINX     = 'nginx';
	const IIS       = 'iis';
	const UNKNOWN   = 'unknown';

	const OPT_CACHED_TYPE = 'xspeed_server_type';

	public static function type() {
		$detected = self::detect();
		if ( self::UNKNOWN !== $detected ) {
			// Persist whenever we have a real answer so future CLI /
			// cron / REST calls (where SERVER_SOFTWARE may be empty)
			// inherit it. Non-autoloaded — only read when needed.
			$cached = get_option( self::OPT_CACHED_TYPE, null );
			if ( $cached !== $detected ) {
				update_option( self::OPT_CACHED_TYPE, $detected, false );
			}
			return $detected;
		}

		// No definitive signal this request (typically WP-CLI, where
		// SERVER_SOFTWARE is empty). Read whatever was cached the last
		// time we ran from a real HTTP request.
		$cached = get_option( self::OPT_CACHED_TYPE, null );
		if ( is_string( $cached ) && '' !== $cached ) {
			return $cached;
		}

		return self::UNKNOWN;
	}

	/**
	 * Live detection — never reads the cache. Used by type() and by
	 * any caller that explicitly wants the current-request answer
	 * (e.g. diagnostic UI showing "detected this request").
	 *
	 * We DO NOT fall back to "if .htaccess exists assume Apache" here:
	 * Cache::install_rewrite() writes .htaccess itself, so on nginx
	 * hosts the file appears after first cache toggle and a presence
	 * check then flips us to APACHE forever. Cached HTTP detection
	 * is the cleaner backstop.
	 */
	public static function detect(): string {
		global $is_apache, $is_nginx, $is_IIS, $is_iis7;

		$signature = self::server_signature();

		if ( false !== stripos( $signature, 'litespeed' ) ) {
			return self::LITESPEED;
		}
		// apache_get_modules() exists only with mod_php (not FPM), so
		// gate it behind SERVER_SOFTWARE first. Otherwise an
		// "apache_get_modules exists" check would false-positive on a
		// few PHP-builtin-server / mod_php-on-localhost dev edge cases.
		if ( false !== stripos( $signature, 'apache' ) || ! empty( $is_apache ) ) {
			return self::APACHE;
		}
		if ( false !== stripos( $signature, 'nginx' ) || ! empty( $is_nginx ) ) {
			return self::NGINX;
		}
		if ( false !== stripos( $signature, 'microsoft-iis' ) || ! empty( $is_IIS ) || ! empty( $is_iis7 ) ) {
			return self::IIS;
		}
		return self::UNKNOWN;
	}

	/**
	 * Whether the server respects .htaccess / web.config-style file-based config.
	 */
	public static function supports_htaccess() {
		$t = self::type();
		return self::APACHE === $t || self::LITESPEED === $t;
	}

	/**
	 * GZIP support category for the UI:
	 *   'auto'   — toggling writes server config (Apache / LiteSpeed)
	 *   'manual' — must be configured outside the plugin (nginx, IIS, unknown)
	 */
	public static function gzip_mode() {
		return self::supports_htaccess() ? 'auto' : 'manual';
	}

	private static function server_signature() {
		return isset( $_SERVER['SERVER_SOFTWARE'] )
			? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) )
			: '';
	}

	/**
	 * Detect active caching plugins that would conflict with xSpeed. Returns
	 * a list of human-readable labels for any conflicting plugin currently
	 * active; empty array means the field is clear. Used by the onboarding
	 * wizard's Step 1 health check and (Phase 2.1) the main dashboard's
	 * Health card.
	 *
	 * The detection key is the plugin's main file path relative to the
	 * plugins directory — the same value WordPress uses internally in
	 * `active_plugins`. Folder-only checks (`is_plugin_active('foo/')`)
	 * would false-positive on disabled plugins still on disk.
	 */
	public static function conflicts() {
		if ( ! function_exists( 'is_plugin_active' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$known = array(
			'wp-rocket/wp-rocket.php'                                 => 'WP Rocket',
			'w3-total-cache/w3-total-cache.php'                       => 'W3 Total Cache',
			'wp-super-cache/wp-cache.php'                             => 'WP Super Cache',
			'wp-fastest-cache/wpFastestCache.php'                     => 'WP Fastest Cache',
			'litespeed-cache/litespeed-cache.php'                     => 'LiteSpeed Cache',
			'cache-enabler/cache-enabler.php'                         => 'Cache Enabler',
			'comet-cache/comet-cache.php'                             => 'Comet Cache',
			'hummingbird-performance/wp-hummingbird.php'              => 'Hummingbird',
			'sg-cachepress/sg-cachepress.php'                         => 'SG Optimizer',
			'breeze/breeze.php'                                       => 'Breeze',
			'autoptimize/autoptimize.php'                             => 'Autoptimize',
			'flying-press/flying-press.php'                           => 'FlyingPress',
			'nitropack/main.php'                                      => 'NitroPack',
		);

		$active = array();
		foreach ( $known as $file => $label ) {
			if ( is_plugin_active( $file ) ) {
				$active[] = $label;
			}
		}
		return $active;
	}
}

```
