PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.7
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.7
1.3.7 1.3.6 1.3.5 1.3.4 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 All 33 releases
← All changes | includes/modules/Gzip/GzipModule.php +132 -15 1.0.1 → 1.3.7 View file →
@@ -15,8 +15,11 @@
15 15 declare(strict_types=1);
16 16
17 17 namespace XSpeed\Modules\Gzip;
18 18
19 +defined( 'ABSPATH' ) || exit;
20 +
21 +use XSpeed\Deep_Link;
19 22 use XSpeed\Gzip as LegacyGzip;
20 23 use XSpeed\Module;
21 24 use XSpeed\Server;
22 25 use XSpeed\Settings_Manager;
@@ -28,11 +31,18 @@
28 31 public const VERSION = '1.1.0';
29 32
30 33 public function ui_metadata(): array {
31 34 return array(
32 - 'label' => 'GZIP',
33 - 'icon' => 'Layers',
34 - 'description' => 'Compress responses to reduce transfer size.',
35 + 'label' => __( 'Compression', 'xspeed' ),
36 + 'tab_label' => __( 'GZIP', 'xspeed' ), // its own tab on the Compression page
37 + 'icon' => 'Layers',
38 + 'description' => __( 'Compresses pages and files so they download faster.', 'xspeed' ),
39 + 'group' => 'performance',
40 + // Host panel merges GZIP (this module) + Brotli (Pro) into one
41 + // page — they are one decision with a fallback chain, not two
42 + // sidebar rows (FBS-83633). The panel renders this module's own
43 + // schema form plus a Brotli Pro section.
44 + 'custom_panel' => 'CompressionPanel',
35 45 );
36 46 }
37 47
38 48 public function settings_schema(): array {
@@ -39,15 +49,39 @@
39 49 return array(
40 50 'gzip_enabled' => array(
41 51 'type' => 'bool',
42 52 'default' => false,
43 - 'label' => 'Enable GZIP Compression',
44 - 'description' => 'On Apache / LiteSpeed we write the .htaccess rules automatically. On nginx the snippet below must be added to your server config.',
53 + 'label' => __( 'Enable Gzip compression', 'xspeed' ),
54 + // Server-conditional. The old copy said "On nginx the snippet
55 + // below must be added to your server config" — unconditionally,
56 + // and there is no snippet below: the Compression page is a tab
57 + // strip plus this toggle, and NginxServerBlock only mounts under
58 + // the Cache panel. So Apache/LiteSpeed users read dead text
59 + // about a server they aren't on, and nginx users got a promise
60 + // the page couldn't keep. The nginx half now lives in
61 + // ui_notices(), which can link to where the snippet actually is.
62 + 'description' => self::gzip_description(),
45 63 ),
46 64 );
47 65 }
48 66
49 67 /**
68 + * Copy for the GZIP toggle, matched to the server we're actually on.
69 + *
70 + * Schema descriptions have no server-conditional rendering, so the choice
71 + * has to happen here rather than in the panel.
72 + */
73 + private static function gzip_description(): string {
74 + if ( class_exists( '\\XSpeed\\Server' ) && Server::supports_htaccess() ) {
75 + return __( 'Compresses pages and files before sending them. xSpeed adds the .htaccess rules for you on this server.', 'xspeed' );
76 + }
77 + if ( class_exists( '\\XSpeed\\Server' ) && Server::NGINX === Server::type() ) {
78 + return __( 'Compresses pages and files before sending them. On nginx you add the rules to your server config yourself. See the notice below.', 'xspeed' );
79 + }
80 + return __( 'Compresses pages and files before sending them. On this server you add the rules to your server config yourself. See the notice below.', 'xspeed' );
81 + }
82 +
83 + /**
50 84 * 1.1.0: drain gzip_enabled from the legacy xspeed_options blob into
51 85 * this module's option. Idempotent — a re-run with the legacy key
52 86 * already gone is a no-op.
53 87 */
@@ -71,8 +105,9 @@
71 105 array(
72 106 'name' => 'xspeed gzip',
73 107 'callback' => array( $this, 'cli_handler' ),
74 108 'shortdesc' => 'Show GZIP status (server type, active, mode).',
109 + 'ai_hint' => 'Is text compression (GZIP/Brotli) actually working on this site? Answers "why are my HTML/CSS/JS transfers so large" and whether the server is compressing at all. Reports the detected server, whether compression is active, and how it is applied.',
75 110 'synopsis' => array(),
76 111 ),
77 112 );
78 113 }
@@ -87,24 +122,81 @@
87 122 $opts = Settings_Manager::get( self::SLUG );
88 123 if ( empty( $opts['gzip_enabled'] ) ) {
89 124 return array();
90 125 }
91 - if ( 'auto' === Server::gzip_mode() && LegacyGzip::probe_active() ) {
126 +
127 + // Probe the live response — works regardless of server type. If gzip
128 + // is actually being served, nothing is wrong. Mirrors BrowserCache's
129 + // probe_headers_present() pattern.
130 + //
131 + // Tri-state: true = proven serving, false = proven not serving,
132 + // null = the loopback never reached the origin, which is not evidence
133 + // of anything. Telling someone their server is misconfigured because
134 + // *we* couldn't call it is the bug behind issue #18. (#18)
135 + $serving = LegacyGzip::probe_active();
136 +
137 + // nginx gets a notice EITHER WAY, because on nginx this notice is the
138 + // only route from the Compression page to the snippet — the panel body
139 + // is a tab strip plus one toggle, and NginxServerBlock mounts under
140 + // the Cache panel alone. Suppressing it on a correctly-configured box
141 + // left that user with no way to reach the directives at all (say, to
142 + // re-paste them after an nginx upgrade). Working sites get a calm
143 + // "here's where it lives"; broken ones get the warning. (#87)
144 + //
145 + // An unreachable probe (null) takes the calm wording too: we cannot
146 + // prove gzip is missing, so the notice points at the snippet without
147 + // claiming the server is misconfigured. Only a proven-false probe
148 + // warns. (#18)
149 + if ( Server::NGINX === Server::type() ) {
150 + $proven_missing = ( false === $serving );
151 + return array(
152 + array(
153 + 'tone' => $proven_missing ? 'warn' : 'info',
154 + 'title' => $proven_missing
155 + ? __( 'GZIP requires server config on nginx', 'xspeed' )
156 + : __( 'GZIP is being served by nginx', 'xspeed' ),
157 + 'body' => $proven_missing
158 + ? __( 'xSpeed can only auto-configure GZIP on Apache and LiteSpeed (via .htaccess). The directives are included in the unified server-block snippet on the Cache panel — copy + paste it once into your nginx vhost (or container nginx config) and reload nginx.', 'xspeed' )
159 + : __( 'Responses are compressed. xSpeed cannot configure nginx from WordPress, so these directives live in the unified server-block snippet on the Cache panel — that is where to re-copy them if your server config is ever rebuilt.', 'xspeed' ),
160 + // Lands on the snippet itself and auto-expands it, rather
161 + // than on the Cache page where it is one collapsed section
162 + // among several. Same call BrowserCacheModule makes.
163 + 'action' => Deep_Link::action(
164 + __( 'Go to the snippet', 'xspeed' ),
165 + 'cache',
166 + 'nginx_snippet'
167 + ),
168 + ),
169 + );
170 + }
171 +
172 + // Non-nginx: stay quiet unless the probe positively proved gzip is
173 + // missing. `null` (unreachable) must not produce a notice. (#18)
174 + if ( false !== $serving ) {
92 175 return array();
93 176 }
177 + // Probe says NOT active and gzip is enabled — surface the right
178 + // notice per server topology.
94 179 if ( 'auto' === Server::gzip_mode() ) {
95 - return array();
180 + // Apache/LiteSpeed but probe failed — .htaccess write must
181 + // have been blocked, or another plugin is overriding. Tell
182 + // the user something is wrong, no snippet (we can't fix it
183 + // without their server access).
184 + return array(
185 + array(
186 + 'tone' => 'warn',
187 + 'title' => __( 'GZIP enabled but not active on the server', 'xspeed' ),
188 + 'body' => __( 'xSpeed wrote the .htaccess rules but the response still isn\'t gzipped. Your server may have AllowOverride disabled, another caching plugin overriding, or mod_deflate missing. Ask your host to enable GZIP on Apache.', 'xspeed' ),
189 + ),
190 + );
96 191 }
97 - // Manual server (nginx, IIS, unknown) → render the snippet.
98 - $type = Server::type();
192 + // IIS / unknown server fallback — keep the legacy "paste this"
193 + // notice until a non-nginx unified-snippet surface ships. (nginx
194 + // returned above, whether or not gzip is currently being served.)
99 195 return array(
100 196 array(
101 197 'tone' => 'warn',
102 - 'title' => sprintf(
103 - /* translators: %s: server software label (e.g. "nginx") */
104 - __( 'GZIP requires server config on %s', 'xspeed' ),
105 - 'nginx' === $type ? 'nginx' : __( 'this server', 'xspeed' )
106 - ),
198 + 'title' => __( 'GZIP requires server config on this server', 'xspeed' ),
107 199 'body' => __( 'xSpeed can only auto-configure GZIP on Apache and LiteSpeed (via .htaccess). Paste the snippet below into your server config and reload.', 'xspeed' ),
108 200 'snippet' => LegacyGzip::nginx_snippet(),
109 201 ),
110 202 );
@@ -131,8 +223,12 @@
131 223 $old_gzip = is_array( $old ) ? ! empty( $old['gzip_enabled'] ) : false;
132 224 $new_gzip = is_array( $new ) ? ! empty( $new['gzip_enabled'] ) : false;
133 225 if ( $old_gzip !== $new_gzip ) {
134 226 LegacyGzip::apply( $new_gzip );
227 + // Settings just changed — current "probe says active" answer
228 + // is stale. Drop the transient so the next dashboard load
229 + // re-checks the live response.
230 + delete_transient( 'xspeed_gzip_active' );
135 231 }
136 232 }
137 233
138 234 /**
@@ -173,8 +269,29 @@
173 269 $opts = Settings_Manager::get( self::SLUG );
174 270 \WP_CLI::log( 'enabled ' . ( $opts['gzip_enabled'] ? 'true' : 'false' ) );
175 271 \WP_CLI::log( 'server ' . Server::type() );
176 272 \WP_CLI::log( 'mode ' . Server::gzip_mode() );
177 - \WP_CLI::log( 'active ' . ( LegacyGzip::probe_active() ? 'true' : 'false' ) );
273 + $active = LegacyGzip::probe_active();
274 + \WP_CLI::log( 'active ' . ( null === $active ? 'unknown (loopback probe failed)' : ( $active ? 'true' : 'false' ) ) );
178 275 \WP_CLI::log( 'nginx_snippet ' . LegacyGzip::nginx_snippet() );
276 + }
277 +
278 + /**
279 + * GZIP directives for the unified nginx server-block snippet. Null
280 + * when the module is disabled.
281 + */
282 + public function nginx_directives(): ?string {
283 + $opts = Settings_Manager::get( self::SLUG );
284 + if ( empty( $opts['gzip_enabled'] ) ) {
285 + return null;
286 + }
287 + $snippet = LegacyGzip::nginx_snippet();
288 + return is_string( $snippet ) && '' !== $snippet ? $snippet : null;
289 + }
290 +
291 + /**
292 + * Gzip stores its switch as `gzip_enabled`, not `enabled`. (#363)
293 + */
294 + public function is_active(): ?bool {
295 + return $this->any_bool_flag_on();
179 296 }
180 297 }