PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.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
← All changes | includes/class-server.php +95 -39 1.1.81.3.3 View file →
@@ -39,20 +39,36 @@
39 39 $cached = get_option( self::OPT_CACHED_TYPE, null );
40 40 if ( $cached !== $detected ) {
41 41 update_option( self::OPT_CACHED_TYPE, $detected, false );
42 42 }
43 - return $detected;
43 + $type = $detected;
44 + } else {
45 + // No definitive signal this request (typically WP-CLI, where
46 + // SERVER_SOFTWARE is empty). Read whatever was cached the last
47 + // time we ran from a real HTTP request.
48 + $cached = get_option( self::OPT_CACHED_TYPE, null );
49 + $type = ( is_string( $cached ) && '' !== $cached ) ? $cached : self::UNKNOWN;
44 50 }
45 51
46 - // No definitive signal this request (typically WP-CLI, where
47 - // SERVER_SOFTWARE is empty). Read whatever was cached the last
48 - // time we ran from a real HTTP request.
49 - $cached = get_option( self::OPT_CACHED_TYPE, null );
50 - if ( is_string( $cached ) && '' !== $cached ) {
51 - return $cached;
52 - }
53 -
54 - return self::UNKNOWN;
52 + /**
53 + * Filters the resolved server type.
54 + *
55 + * The override point for contexts that cannot detect. Detection
56 + * reads SERVER_SOFTWARE, which the web server supplies and WP-CLI
57 + * therefore never has; the cached option covers CLI runs on a site
58 + * some request has already reached, but a site provisioned entirely
59 + * over WP-CLI has nothing cached and resolves to `unknown` even on
60 + * nginx. `wp xspeed cache nginx-config --server=` hooks this so
61 + * every gate downstream — Cache::nginx_snippet(), each module's own
62 + * nginx_directives() — agrees on one answer, rather than each
63 + * re-deciding and emitting a half-built config.
64 + *
65 + * Filtering does NOT write the cached option: an assumption stated
66 + * for one command must not become this site's persisted answer.
67 + *
68 + * @param string $type One of apache|litespeed|nginx|iis|unknown.
69 + */
70 + return apply_filters( 'xspeed_server_type', $type );
55 71 }
56 72
57 73 /**
58 74 * Live detection — never reads the cache. Used by type() and by
@@ -656,18 +672,34 @@
656 672 : '';
657 673 }
658 674
659 675 /**
660 - * Detect active caching plugins that would conflict with xSpeed. Returns
661 - * a list of human-readable labels for any conflicting plugin currently
662 - * active; empty array means the field is clear. Used by the onboarding
663 - * wizard's Step 1 health check and (Phase 2.1) the main dashboard's
664 - * Health card.
676 + * Active PAGE-CACHING plugins that would fight xSpeed over the cache
677 + * drop-in. Returns human-readable labels; an empty array means the field
678 + * is clear. Used by the onboarding wizard's Step 1 health check and the
679 + * dashboard's Health card, both of which tell the user to deactivate what
680 + * is listed "to avoid double-caching".
665 681 *
666 - * The detection key is the plugin's main file path relative to the
667 - * plugins directory — the same value WordPress uses internally in
668 - * `active_plugins`. Folder-only checks (`is_plugin_active('foo/')`)
669 - * would false-positive on disabled plugins still on disk.
682 + * Which is why the list is filtered on the page-cache capability rather
683 + * than "is it a performance plugin": Autoptimize only minifies, so naming
684 + * it here made the health row give advice that was flatly wrong.
685 + * Minification overlap is still caught — by Conflict_Registry, per feature.
686 + *
687 + * The detection key is the plugin's main file path relative to the plugins
688 + * directory — the same value WordPress uses internally in `active_plugins`.
689 + * Folder-only checks (`is_plugin_active('foo/')`) would false-positive on
690 + * disabled plugins still on disk.
691 + *
692 + * Membership comes from Cache_Plugin_Catalog, so a plugin is added in one
693 + * place and shows up in both this list and the conflict matrix.
694 + *
695 + * Activation is not the whole test, though. What actually stops xSpeed
696 + * enabling its cache is who holds advanced-cache.php, and a drop-in left
697 + * behind by an uninstalled plugin holds it just as firmly as a running
698 + * one. Checking only active_plugins let the wizard say "No other caching
699 + * plugins detected" on the environment step and then refuse the enable on
700 + * the very next step, for a file it had just looked past. So a foreign
701 + * drop-in is listed too, named where we can name it.
670 702 */
671 703 public static function conflicts() {
672 704 if ( ! function_exists( 'is_plugin_active' ) ) {
673 705 require_once ABSPATH . 'wp-admin/includes/plugin.php';
@@ -672,29 +704,53 @@
672 704 if ( ! function_exists( 'is_plugin_active' ) ) {
673 705 require_once ABSPATH . 'wp-admin/includes/plugin.php';
674 706 }
675 707
676 - $known = array(
677 - 'wp-rocket/wp-rocket.php' => 'WP Rocket',
678 - 'w3-total-cache/w3-total-cache.php' => 'W3 Total Cache',
679 - 'wp-super-cache/wp-cache.php' => 'WP Super Cache',
680 - 'wp-fastest-cache/wpFastestCache.php' => 'WP Fastest Cache',
681 - 'litespeed-cache/litespeed-cache.php' => 'LiteSpeed Cache',
682 - 'cache-enabler/cache-enabler.php' => 'Cache Enabler',
683 - 'comet-cache/comet-cache.php' => 'Comet Cache',
684 - 'hummingbird-performance/wp-hummingbird.php' => 'Hummingbird',
685 - 'sg-cachepress/sg-cachepress.php' => 'SG Optimizer',
686 - 'breeze/breeze.php' => 'Breeze',
687 - 'autoptimize/autoptimize.php' => 'Autoptimize',
688 - 'flying-press/flying-press.php' => 'FlyingPress',
689 - 'nitropack/main.php' => 'NitroPack',
690 - );
691 -
692 - $active = array();
693 - foreach ( $known as $file => $label ) {
708 + $found = array();
709 + foreach ( Cache_Plugin_Catalog::with_capability( Cache_Plugin_Catalog::CAP_PAGE_CACHE ) as $file => $entry ) {
710 + // xSpeed is in the catalog — it is a page cache, and the detector
711 + // needs to be able to name our own drop-in. It is not a conflict
712 + // with itself, and listing it told every site running us to
713 + // deactivate us to avoid double-caching.
714 + if ( 'xspeed/xspeed.php' === $file ) {
715 + continue;
716 + }
694 717 if ( is_plugin_active( $file ) ) {
695 - $active[] = $label;
718 + $found[] = $entry['label'];
696 719 }
697 720 }
698 - return $active;
721 +
722 + $dropin = self::foreign_dropin_label();
723 + if ( null !== $dropin && ! in_array( $dropin, $found, true ) ) {
724 + $found[] = $dropin;
725 + }
726 +
727 + return array_values( array_unique( $found ) );
728 + }
729 +
730 + /**
731 + * The name of whoever owns advanced-cache.php, when it is not xSpeed.
732 + *
733 + * Null when the file is absent or ours. An owner we cannot identify still
734 + * blocks the enable, so it is reported under a generic name rather than
735 + * being silently dropped — "we could not tell" and "there is nothing
736 + * there" are different answers.
737 + */
738 + private static function foreign_dropin_label(): ?string {
739 + $owner = Cache::dropin_owner();
740 + if ( Cache::DROPIN_XSPEED === $owner || Cache::DROPIN_NONE === $owner ) {
741 + return null;
742 + }
743 + if ( Cache::DROPIN_UNREADABLE === $owner ) {
744 + return __( 'an unreadable advanced-cache.php', 'xspeed' );
745 + }
746 +
747 + $contents = @file_get_contents( WP_CONTENT_DIR . '/advanced-cache.php' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.PHP.NoSilencedErrors.Discouraged -- read-only inspection of a drop-in we may not own; failure is reported as an unidentified owner.
748 + $named = is_string( $contents ) ? Cache_Plugin_Catalog::identify_dropin( $contents ) : null;
749 + if ( null !== $named ) {
750 + $entry = Cache_Plugin_Catalog::get( $named );
751 + return (string) ( $entry['label'] ?? $named );
752 + }
753 +
754 + return __( 'an unidentified advanced-cache.php', 'xspeed' );
699 755 }
700 756 }