# xspeed/1.0.8/includes/class-conflict-registry.php

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

- Page: https://pluginprobe.com/plugins/xspeed/1.0.8/code/includes/class-conflict-registry.php
- Raw: https://pluginprobe.com/plugins/xspeed/1.0.8/raw/includes/class-conflict-registry.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.8/code/includes/class-conflict-registry.php#L10-L20`.

```php
<?php
/**
 * Conflict_Registry — the strategy matrix for coexisting (or refusing to
 * coexist) with other caching / optimization plugins.
 *
 * Source of truth: IMPLEMENTATION.md §6.2. Each entry says:
 *   - which plugin we're conflicting with (its main file path),
 *   - which "kind" of plugin it is (page-cache / minify / lazy-load / etc.),
 *   - per-feature strategy (refuse | warn | allow),
 *   - a human-readable reason rendered in UI + REST errors.
 *
 * The matrix is queried by:
 *   - Onboarding Step 1 (health rows).
 *   - Dashboard health card (Phase 2.1).
 *   - REST gates (refuse → 409, warn → response includes warnings[]).
 *   - CLI `wp xspeed conflicts`.
 *   - Cache toggle handler (refuses to enable when another page cache active).
 *
 * Detection results are cached for the request lifetime. The cache is
 * invalidated on `activated_plugin` / `deactivated_plugin`.
 *
 * Modules can declare additional conflicts via Module::conflicts(); those
 * are merged into the static matrix at runtime so Pro modules can extend
 * the registry without modifying Free code.
 *
 * @package XSpeed
 */

namespace XSpeed;

defined( 'ABSPATH' ) || exit;

final class Conflict_Registry {

	public const STRATEGY_REFUSE = 'refuse';
	public const STRATEGY_WARN   = 'warn';
	public const STRATEGY_ALLOW  = 'allow';

	/**
	 * @var array|null Memoized detection result for this request.
	 */
	private static $cache = null;

	/**
	 * Static strategy matrix. Keys are plugin file paths as they appear in
	 * the `active_plugins` option. Values declare:
	 *   - label    : human-readable plugin name
	 *   - kind     : page-cache | minify | lazy-load | image-opt | cdn | preload | mixed
	 *   - strategy : map of feature_key → strategy (refuse | warn | allow)
	 *
	 * Feature keys are namespaced `<module-slug>.<sub-feature>` or just
	 * `<module-slug>` for whole-module conflicts. Modules can extend this
	 * matrix via their conflicts() method.
	 */
	private static function matrix(): array {
		return array(
			'wp-rocket/wp-rocket.php'                      => array(
				'label'    => 'WP Rocket',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'cdn.rewrite'     => self::STRATEGY_REFUSE,
					'images.lazyload' => self::STRATEGY_REFUSE,
				),
			),
			'litespeed-cache/litespeed-cache.php'          => array(
				'label'    => 'LiteSpeed Cache',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'cdn.rewrite'     => self::STRATEGY_WARN,
					'images.lazyload' => self::STRATEGY_REFUSE,
				),
			),
			'w3-total-cache/w3-total-cache.php'            => array(
				'label'    => 'W3 Total Cache',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'cdn.rewrite'     => self::STRATEGY_WARN,
				),
			),
			'wp-super-cache/wp-cache.php'                  => array(
				'label'    => 'WP Super Cache',
				'kind'     => 'page-cache',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
				),
			),
			'wp-fastest-cache/wpFastestCache.php'          => array(
				'label'    => 'WP Fastest Cache',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'images.lazyload' => self::STRATEGY_REFUSE,
				),
			),
			'cache-enabler/cache-enabler.php'              => array(
				'label'    => 'Cache Enabler',
				'kind'     => 'page-cache',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
				),
			),
			'comet-cache/comet-cache.php'                  => array(
				'label'    => 'Comet Cache',
				'kind'     => 'page-cache',
				'strategy' => array(
					'cache.page' => self::STRATEGY_REFUSE,
				),
			),
			'hummingbird-performance/wp-hummingbird.php'   => array(
				'label'    => 'Hummingbird',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'images.lazyload' => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_WARN,
					'cdn.rewrite'     => self::STRATEGY_WARN,
				),
			),
			'sg-cachepress/sg-cachepress.php'              => array(
				'label'    => 'SG Optimizer',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'images.lazyload' => self::STRATEGY_WARN,
				),
			),
			'breeze/breeze.php'                            => array(
				'label'    => 'Breeze',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'cdn.rewrite'     => self::STRATEGY_WARN,
				),
			),
			'autoptimize/autoptimize.php'                  => array(
				'label'    => 'Autoptimize',
				'kind'     => 'minify',
				'strategy' => array(
					'minify.html' => self::STRATEGY_REFUSE,
					'minify.css'  => self::STRATEGY_REFUSE,
					'minify.js'   => self::STRATEGY_REFUSE,
				),
			),
			'flying-press/flying-press.php'                => array(
				'label'    => 'FlyingPress',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'cdn.rewrite'     => self::STRATEGY_REFUSE,
					'images.lazyload' => self::STRATEGY_REFUSE,
				),
			),
			'nitropack/main.php'                           => array(
				'label'    => 'NitroPack',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'      => self::STRATEGY_REFUSE,
					'minify.html'     => self::STRATEGY_REFUSE,
					'minify.css'      => self::STRATEGY_REFUSE,
					'minify.js'       => self::STRATEGY_REFUSE,
					'preload.crawler' => self::STRATEGY_REFUSE,
					'cdn.rewrite'     => self::STRATEGY_REFUSE,
					'images.lazyload' => self::STRATEGY_REFUSE,
				),
			),
			'wp-optimize/wp-optimize.php'                  => array(
				'label'    => 'WP-Optimize',
				'kind'     => 'mixed',
				'strategy' => array(
					'cache.page'  => self::STRATEGY_WARN,
					'minify.html' => self::STRATEGY_WARN,
					'minify.css'  => self::STRATEGY_WARN,
					'minify.js'   => self::STRATEGY_WARN,
				),
			),
			'jetpack-boost/jetpack-boost.php'              => array(
				'label'    => 'Jetpack Boost',
				'kind'     => 'mixed',
				'strategy' => array(
					'minify.css'      => self::STRATEGY_WARN,
					'minify.js'       => self::STRATEGY_WARN,
					'images.lazyload' => self::STRATEGY_WARN,
				),
			),
			'wp-asset-clean-up/wpacu.php'                  => array(
				'label'    => 'Asset CleanUp',
				'kind'     => 'minify',
				'strategy' => array(
					'minify.css' => self::STRATEGY_WARN,
					'minify.js'  => self::STRATEGY_WARN,
				),
			),
			'ewww-image-optimizer/ewww-image-optimizer.php' => array(
				'label'    => 'EWWW Image Optimizer',
				'kind'     => 'image-opt',
				'strategy' => array(
					'images.optimize' => self::STRATEGY_REFUSE,
				),
			),
			'shortpixel-image-optimiser/wp-shortpixel.php' => array(
				'label'    => 'ShortPixel',
				'kind'     => 'image-opt',
				'strategy' => array(
					'images.optimize' => self::STRATEGY_REFUSE,
				),
			),
			'wp-smushit/wp-smush.php'                      => array(
				'label'    => 'Smush',
				'kind'     => 'image-opt',
				'strategy' => array(
					'images.optimize' => self::STRATEGY_REFUSE,
					'images.lazyload' => self::STRATEGY_WARN,
				),
			),
		);
	}

	/**
	 * Active conflicts on this site. Returns one entry per active
	 * conflicting plugin: [
	 *   'plugin'   => 'wp-rocket/wp-rocket.php',
	 *   'label'    => 'WP Rocket',
	 *   'kind'     => 'mixed',
	 *   'strategy' => [ feature => strategy ],
	 * ]
	 *
	 * @return array[]
	 */
	public static function detected(): array {
		if ( null !== self::$cache ) {
			return self::$cache;
		}
		if ( ! function_exists( 'is_plugin_active' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$matrix = apply_filters( 'xspeed_conflict_matrix', self::matrix() );
		$out    = array();
		foreach ( $matrix as $plugin => $spec ) {
			if ( is_plugin_active( $plugin ) ) {
				$out[] = array(
					'plugin'   => $plugin,
					'label'    => $spec['label'],
					'kind'     => $spec['kind'],
					'strategy' => $spec['strategy'],
				);
			}
		}
		self::$cache = $out;
		return $out;
	}

	/**
	 * Strongest strategy across all detected conflicts for a given feature
	 * key. Returns 'refuse' > 'warn' > 'allow' (refuse wins if any
	 * conflict refuses; warn wins if any warns but none refuse).
	 */
	public static function strategy_for( string $feature_key ): string {
		$strongest = self::STRATEGY_ALLOW;
		foreach ( self::detected() as $conflict ) {
			$s = $conflict['strategy'][ $feature_key ] ?? self::STRATEGY_ALLOW;
			if ( self::STRATEGY_REFUSE === $s ) {
				return self::STRATEGY_REFUSE;
			}
			if ( self::STRATEGY_WARN === $s && self::STRATEGY_ALLOW === $strongest ) {
				$strongest = self::STRATEGY_WARN;
			}
		}
		return $strongest;
	}

	/**
	 * Human-readable reason a feature is blocked by a refuse-strategy
	 * conflict, or null if not blocked. Used by Rest_Manager (409) and UI
	 * tooltips.
	 *
	 * The optional $module_slug is currently informational only — the
	 * strategy lookup uses $feature_key alone. Reserved for future
	 * module-specific overrides.
	 */
	public static function why_blocked( string $module_slug, string $feature_key ): ?string {
		foreach ( self::detected() as $conflict ) {
			if ( ( $conflict['strategy'][ $feature_key ] ?? null ) === self::STRATEGY_REFUSE ) {
				return sprintf(
					/* translators: %s: conflicting plugin name */
					__( '%s is active and handles the same feature. Deactivate it before enabling this in xSpeed.', 'xspeed' ),
					$conflict['label']
				);
			}
		}
		return null;
	}

	/**
	 * All warnings for a feature (one string per warning-strategy conflict).
	 * Returned in the response body of warn-gated REST endpoints.
	 *
	 * @return string[]
	 */
	public static function warnings_for( string $feature_key ): array {
		$out = array();
		foreach ( self::detected() as $conflict ) {
			if ( ( $conflict['strategy'][ $feature_key ] ?? null ) === self::STRATEGY_WARN ) {
				$out[] = sprintf(
					/* translators: %s: conflicting plugin name */
					__( '%s is active and may interfere. Test carefully.', 'xspeed' ),
					$conflict['label']
				);
			}
		}
		return $out;
	}

	/**
	 * Invalidate the per-request cache. Called on activated_plugin /
	 * deactivated_plugin so the next read reflects the new state.
	 */
	public static function invalidate(): void {
		self::$cache = null;
	}

	/**
	 * Bootstrap hooks. Call once from Plugin::init().
	 */
	public static function boot(): void {
		add_action( 'activated_plugin', array( __CLASS__, 'invalidate' ) );
		add_action( 'deactivated_plugin', array( __CLASS__, 'invalidate' ) );
	}
}

```
