PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.1.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.1.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 / modules / Gzip / GzipModule.php

GzipModule.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.1.1, at includes/modules/Gzip/GzipModule.php

230 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * GZIP module.
4 *
5 * Owns the gzip_enabled setting. On flip → writes / removes Apache /
6 * LiteSpeed `.htaccess` rules via the static XSpeed\Gzip helper. On
7 * nginx (or any server xSpeed can't auto-configure), the panel shows
8 * the manual snippet via ui_notices().
9 *
10 * Tier: Free. See SETTINGS.md for the contract this module satisfies.
11 *
12 * @package XSpeed
13 */
14
15 declare(strict_types=1);
16
17 namespace XSpeed\Modules\Gzip;
18
19 defined( 'ABSPATH' ) || exit;
20
21 use XSpeed\Gzip as LegacyGzip;
22 use XSpeed\Module;
23 use XSpeed\Server;
24 use XSpeed\Settings_Manager;
25
26 final class GzipModule extends Module {
27
28 public const SLUG = 'gzip';
29 public const TIER = self::TIER_FREE;
30 public const VERSION = '1.1.0';
31
32 public function ui_metadata(): array {
33 return array(
34 'label' => 'Compression',
35 'tab_label' => 'GZIP', // its own tab on the Compression page
36 'icon' => 'Layers',
37 'description' => 'Compress responses to reduce transfer size.',
38 // Host panel merges GZIP (this module) + Brotli (Pro) into one
39 // page — they are one decision with a fallback chain, not two
40 // sidebar rows (FBS-83633). The panel renders this module's own
41 // schema form plus a Brotli Pro section.
42 'custom_panel' => 'CompressionPanel',
43 );
44 }
45
46 public function settings_schema(): array {
47 return array(
48 'gzip_enabled' => array(
49 'type' => 'bool',
50 'default' => false,
51 'label' => 'Enable GZIP Compression',
52 'description' => 'On Apache / LiteSpeed we write the .htaccess rules automatically. On nginx the snippet below must be added to your server config.',
53 ),
54 );
55 }
56
57 /**
58 * 1.1.0: drain gzip_enabled from the legacy xspeed_options blob into
59 * this module's option. Idempotent — a re-run with the legacy key
60 * already gone is a no-op.
61 */
62 public function migrations(): array {
63 return array(
64 '1.1.0' => static function ( array $opts ): array {
65 $legacy = get_option( 'xspeed_options', array() );
66 if ( ! is_array( $legacy ) || ! array_key_exists( 'gzip_enabled', $legacy ) ) {
67 return $opts;
68 }
69 $opts['gzip_enabled'] = (bool) $legacy['gzip_enabled'];
70 unset( $legacy['gzip_enabled'] );
71 update_option( 'xspeed_options', $legacy );
72 return $opts;
73 },
74 );
75 }
76
77 public function cli_commands(): array {
78 return array(
79 array(
80 'name' => 'xspeed gzip',
81 'callback' => array( $this, 'cli_handler' ),
82 'shortdesc' => 'Show GZIP status (server type, active, mode).',
83 'synopsis' => array(),
84 ),
85 );
86 }
87
88 /**
89 * Conditional notice published to the module's panel via
90 * Module::ui_notices(). When gzip is enabled and the server can't
91 * auto-configure (nginx / unknown / IIS), we surface a copy-able
92 * snippet so the user can wire it up.
93 */
94 public function ui_notices(): array {
95 $opts = Settings_Manager::get( self::SLUG );
96 if ( empty( $opts['gzip_enabled'] ) ) {
97 return array();
98 }
99 // Probe the live response — works regardless of server type
100 // (Apache, nginx, anything). If gzip is actually being served,
101 // nothing else matters: hide the notice. Mirror's BrowserCache's
102 // probe_headers_present() pattern.
103 if ( LegacyGzip::probe_active() ) {
104 return array();
105 }
106 // Probe says NOT active and gzip is enabled — surface the right
107 // notice per server topology.
108 if ( 'auto' === Server::gzip_mode() ) {
109 // Apache/LiteSpeed but probe failed — .htaccess write must
110 // have been blocked, or another plugin is overriding. Tell
111 // the user something is wrong, no snippet (we can't fix it
112 // without their server access).
113 return array(
114 array(
115 'tone' => 'warn',
116 'title' => __( 'GZIP enabled but not active on the server', 'xspeed' ),
117 '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' ),
118 ),
119 );
120 }
121 // Manual server (nginx, IIS, unknown) — probe failed because
122 // the user hasn't pasted the snippet (or hasn't reloaded nginx).
123 $type = Server::type();
124 if ( 'nginx' === $type ) {
125 return array(
126 array(
127 'tone' => 'warn',
128 'title' => __( 'GZIP requires server config on nginx', 'xspeed' ),
129 'body' => __( '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' ),
130 ),
131 );
132 }
133 // IIS / unknown server fallback — keep the legacy "paste this"
134 // notice until a non-nginx unified-snippet surface ships.
135 return array(
136 array(
137 'tone' => 'warn',
138 'title' => __( 'GZIP requires server config on this server', 'xspeed' ),
139 'body' => __( 'xSpeed can only auto-configure GZIP on Apache and LiteSpeed (via .htaccess). Paste the snippet below into your server config and reload.', 'xspeed' ),
140 'snippet' => LegacyGzip::nginx_snippet(),
141 ),
142 );
143 }
144
145 /**
146 * Module booting: seed per-module option from legacy if needed,
147 * then register the change hook that flips Apache / LiteSpeed
148 * .htaccess rules whenever gzip_enabled is toggled. This handler
149 * fires on writes to xspeed_module_gzip (not the legacy blob).
150 */
151 public function boot(): void {
152 $this->seed_from_legacy_if_needed();
153 add_action( 'update_option_xspeed_module_gzip', array( __CLASS__, 'on_settings_change' ), 10, 2 );
154 add_action( 'add_option_xspeed_module_gzip', array( __CLASS__, 'on_settings_added' ), 10, 2 );
155 }
156
157 /**
158 * Detect the gzip_enabled flip on write and apply / remove the
159 * .htaccess rules. Idempotent — re-running with the same state is a
160 * no-op inside LegacyGzip::apply().
161 */
162 public static function on_settings_change( $old, $new ): void {
163 $old_gzip = is_array( $old ) ? ! empty( $old['gzip_enabled'] ) : false;
164 $new_gzip = is_array( $new ) ? ! empty( $new['gzip_enabled'] ) : false;
165 if ( $old_gzip !== $new_gzip ) {
166 LegacyGzip::apply( $new_gzip );
167 // Settings just changed — current "probe says active" answer
168 // is stale. Drop the transient so the next dashboard load
169 // re-checks the live response.
170 delete_transient( 'xspeed_gzip_active' );
171 }
172 }
173
174 /**
175 * The very first write (option doesn't exist yet) hits add_option
176 * instead of update_option. Treat it as old=false → new=current.
177 */
178 public static function on_settings_added( $name, $value ): void {
179 $enabled = is_array( $value ) ? ! empty( $value['gzip_enabled'] ) : false;
180 if ( $enabled ) {
181 LegacyGzip::apply( true );
182 }
183 }
184
185 public function activate(): void {
186 $this->seed_from_legacy_if_needed();
187 }
188
189 private function seed_from_legacy_if_needed(): void {
190 if ( null !== get_option( 'xspeed_module_gzip', null ) ) {
191 return;
192 }
193 $legacy = get_option( 'xspeed_options', array() );
194 if ( ! is_array( $legacy ) || ! array_key_exists( 'gzip_enabled', $legacy ) ) {
195 return;
196 }
197 update_option(
198 'xspeed_module_gzip',
199 array(
200 '_version' => self::VERSION,
201 'gzip_enabled' => (bool) $legacy['gzip_enabled'],
202 )
203 );
204 unset( $legacy['gzip_enabled'] );
205 update_option( 'xspeed_options', $legacy );
206 }
207
208 public function cli_handler( array $args, array $assoc ): void {
209 $opts = Settings_Manager::get( self::SLUG );
210 \WP_CLI::log( 'enabled ' . ( $opts['gzip_enabled'] ? 'true' : 'false' ) );
211 \WP_CLI::log( 'server ' . Server::type() );
212 \WP_CLI::log( 'mode ' . Server::gzip_mode() );
213 \WP_CLI::log( 'active ' . ( LegacyGzip::probe_active() ? 'true' : 'false' ) );
214 \WP_CLI::log( 'nginx_snippet ' . LegacyGzip::nginx_snippet() );
215 }
216
217 /**
218 * GZIP directives for the unified nginx server-block snippet. Null
219 * when the module is disabled.
220 */
221 public function nginx_directives(): ?string {
222 $opts = Settings_Manager::get( self::SLUG );
223 if ( empty( $opts['gzip_enabled'] ) ) {
224 return null;
225 }
226 $snippet = LegacyGzip::nginx_snippet();
227 return is_string( $snippet ) && '' !== $snippet ? $snippet : null;
228 }
229 }
230