PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / includes / class-wp-optimize-gzip-compression.php

class-wp-optimize-gzip-compression.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.7.0, at includes/class-wp-optimize-gzip-compression.php

467 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) die('Access denied.');
3
4 /**
5 * Class WP_Optimize_Gzip_Compression
6 */
7 class WP_Optimize_Gzip_Compression {
8
9 use WP_Optimize_HTTP_Error_Codes_Trait;
10
11 /**
12 * WP_Optimize_Htaccess instance.
13 *
14 * @var WP_Optimize_Htaccess
15 */
16 private $_htaccess;
17
18 /**
19 * WP_Optimize instance.
20 *
21 * @var WP_Optimize
22 */
23 private $_wp_optimize;
24
25 /**
26 * Gzip section in htaccess will be wrapped with this comment
27 *
28 * @var string
29 */
30 private $_htaccess_section_comment = 'WP-Optimize Gzip compression';
31
32 /**
33 * WP_Optimize_Gzip_Compression constructor.
34 */
35 public function __construct() {
36 $this->_wp_optimize = WP_Optimize();
37 $this->_htaccess = $this->_wp_optimize->get_htaccess();
38 }
39
40 /**
41 * Returns singleton instance object
42 *
43 * @return WP_Optimize_Gzip_Compression Returns `WP_Optimize_Gzip_Compression` object
44 */
45 public static function instance() {
46 static $_instance = null;
47 if (null === $_instance) {
48 $_instance = new self();
49 }
50 return $_instance;
51 }
52
53 /**
54 * Make http request to $url, get 'server' line and check headers for gzip/brotli encoding option.
55 *
56 * @param string $url
57 *
58 * @return array|WP_Error
59 */
60 public function get_headers_information($url) {
61 static $cached_headers_information = array();
62 if (isset($cached_headers_information[$url])) return $cached_headers_information[$url];
63
64 $response = wp_remote_get($url, array('timeout' => 10));
65
66 if (is_wp_error($response)) {
67 return $response;
68 }
69
70 $response_code = wp_remote_retrieve_response_code($response);
71
72 if (200 !== $response_code) {
73 // translators: %1$s is a requested URL, %2$s is the HTTP response code
74 return new WP_Error($response_code, sprintf(__('Unexpected response code when trying to get response for %1$s: %2$s (expected 200)', 'wp-optimize'), $url, $response_code));
75 }
76
77 $headers = wp_remote_retrieve_headers($response);
78
79 if (empty($headers)) {
80 // translators: %s is a requested URL
81 return new WP_Error($response_code, sprintf(__('Unable to retrieve HTTP headers information for %s', 'wp-optimize'), $url));
82 }
83
84 if (is_object($headers) && method_exists($headers, 'getAll')) {
85 $headers = $headers->getAll();
86 }
87
88 $headers_information = array(
89 'server' => array_key_exists('server', $headers) ? $headers['server'] : '',
90 );
91
92 if (array_key_exists('content-encoding', $headers) && preg_match('/^(.*\W|)br(\W.*|)$/i', $headers['content-encoding'])) {
93 // check if there exists Content-encoding header with br(Brotli) value.
94 $headers_information['compression'] = 'brotli';
95 $this->disable();
96 } elseif (array_key_exists('content-encoding', $headers) && preg_match('/gzip/i', $headers['content-encoding'])) {
97 // check if there exists Content-encoding header with gzip value.
98 $headers_information['compression'] = 'gzip';
99 } elseif (array_key_exists('content-encoding', $headers) && preg_match('/zstd/i', $headers['content-encoding'])) {
100 // check if there exists Content-encoding header with zstd value.
101 $headers_information['compression'] = 'zstd';
102 } else {
103 $headers_information['compression'] = false;
104 }
105
106 $cached_headers_information[$url] = $headers_information;
107
108 return $headers_information;
109 }
110
111 /**
112 * Make request to checkgzipcompression.com api and check if gzip option enabled.
113 *
114 * @return bool|WP_Error
115 */
116 public function check_api_for_gzip() {
117 $url = get_template_directory_uri() . '/style.css';
118
119 $api_url = 'https://checkgzipcompression.com/js/checkgzip.json?url=' . urlencode($url);
120
121 $result = wp_remote_get($api_url, array('timeout' => 10));
122
123 if (is_wp_error($result)) return $result;
124
125 if (!isset($result['body'])) return new WP_Error('Gzip', __("We can't definitely determine Gzip status as API doesn't return correct answer.", 'wp-optimize'));
126
127 $body = json_decode($result['body']);
128
129 if (isset($body->error) && $body->error) return new WP_Error('Gzip', __("We can't definitely determine Gzip status as API doesn't return correct answer.", 'wp-optimize'));
130
131 if ($body->result->gzipenabled && !$body->error) {
132 return true;
133 }
134
135 return false;
136 }
137
138 /**
139 * Check if Gzip compression is enabled.
140 *
141 * @param boolean $use_cache - use cached data
142 * @return bool|WP_Error
143 */
144 public function is_gzip_compression_enabled($use_cache = true) {
145
146 $compression_types = $this->get_compression_types($use_cache);
147
148 $is_gzip_compression_enabled = true;
149
150 foreach ($compression_types as $compression) {
151 if (is_wp_error($compression)) return $compression;
152 if (!in_array($compression, array('gzip', 'brotli', 'zstd'))) $is_gzip_compression_enabled = false;
153 }
154
155 // if Gzip is not enabled, but we have added settings and Apache modules nt loaded then return error.
156 if (!$is_gzip_compression_enabled && $this->is_gzip_compression_section_exists()) {
157 if (false === $this->_wp_optimize->is_apache_module_loaded(array('mod_filter', 'mod_deflate'))) {
158 return new WP_Error('gzip_missing_module', __('We successfully added Gzip compression settings into .htaccess file.', 'wp-optimize').' '.__('However, the test file we fetched was not Gzip-compressed.', 'wp-optimize').' '.__('It seems one of Apache modules - mod_filter or mod_deflate - is not active.', 'wp-optimize'));
159 } elseif (WP_Optimize()->is_apache_server()) {
160 return new WP_Error('gzip_missing_module', __('We successfully added Gzip compression settings into .htaccess file.', 'wp-optimize').' '.__('However, the test file we fetched was not Gzip-compressed.', 'wp-optimize').' '.__('Possible causes include that Apache (your webserver) is not configured to allow .htaccess files to take effect, or one of Apache modules - mod_filter or mod_deflate - is not active, or the webserver is configured to disallow Gzip compression.', 'wp-optimize').' '.__('You should speak to your web hosting support to find how to enable it.', 'wp-optimize'));
161 } else {
162 return new WP_Error('gzip_unsuccessful', __('We successfully added Gzip compression settings into .htaccess file.', 'wp-optimize').' '.__('However, the test file we fetched was not Gzip-compressed.', 'wp-optimize').' '.__('You should speak to your web hosting support to find how to enable it.', 'wp-optimize'));
163 }
164 }
165
166 // Stored value used to restore gzip compression settings used when plugin being activated,
167 // i.e. we need to update it only when we have added settings to .htaccess or gzip is disabled
168 if (!$is_gzip_compression_enabled || $this->is_gzip_compression_section_exists()) {
169 WP_Optimize()->get_options()->update_option('is_gzip_compression_enabled', $is_gzip_compression_enabled);
170 }
171
172 return $is_gzip_compression_enabled;
173 }
174
175 /**
176 * Returns the compression type of the given URL.
177 *
178 * @param string $url
179 * @return string|WP_Error
180 */
181 private function get_compression_type($url) {
182
183 $headers_info = $this->get_headers_information($url);
184
185 if (is_wp_error($headers_info)) return $headers_info;
186
187 if (!isset($headers_info['compression'])) return '';
188
189 return $headers_info['compression'];
190 }
191
192 /**
193 * Check if section with Gzip options already exists in htaccess file.
194 *
195 * @return bool
196 */
197 public function is_gzip_compression_section_exists() {
198 return $this->_htaccess->is_commented_section_exists($this->_htaccess_section_comment);
199 }
200
201 /**
202 * Enable Gzip compression - add settings into .htaccess.
203 */
204 public function enable() {
205 $this->_htaccess->update_commented_section($this->prepare_gzip_section(), $this->_htaccess_section_comment);
206 $this->_htaccess->write_file();
207 }
208
209 /**
210 * Disable Gzip compression - remove settings from .htaccess.
211 */
212 public function disable() {
213 $this->_htaccess->remove_commented_section($this->_htaccess_section_comment);
214 $this->_htaccess->write_file();
215 }
216
217 /**
218 * Check if gzip compression option is set to true then add section with gzip settings into .htaccess (used when plugin being activated).
219 */
220 public function restore() {
221 $enabled = WP_Optimize()->get_options()->get_option('is_gzip_compression_enabled');
222
223 if ($enabled && $this->_htaccess->is_writable()) $this->enable();
224 }
225
226 /**
227 * Handler for Gzip compression enable command, called from WP_Optimize_Commands.
228 *
229 * @param array $params - ['enable' => true|false]
230 * @return array
231 */
232 public function enable_gzip_command_handler($params) {
233 $section_updated = false;
234
235 $enable = isset($params['enable']) && $params['enable'];
236
237 if ($this->_htaccess->is_writable()) {
238
239 // update commented section
240 if ($enable) {
241 $this->enable();
242 } else {
243 $this->disable();
244 }
245
246 // read updated file.
247 $this->_htaccess->read_file();
248 // check if section added or removed successfully.
249 $section_exists = $this->_htaccess->is_commented_section_exists($this->_htaccess_section_comment);
250 // set correct $section-updated flag.
251 $section_updated = $enable === $section_exists;
252 }
253
254 $is_gzip_compression_enabled = $this->is_gzip_compression_enabled(false);
255
256 if ($section_updated) {
257 return array(
258 'success' => true,
259 'enabled' => is_wp_error($is_gzip_compression_enabled) ? false : $is_gzip_compression_enabled,
260 // if we can't determine gzip status then return error message.
261 'message' => is_wp_error($is_gzip_compression_enabled) ? $is_gzip_compression_enabled->get_error_message() : '',
262 );
263 } else {
264 $gzip_section = $this->prepare_gzip_section();
265
266 if ($is_gzip_compression_enabled) {
267 // translators: %s is a file name
268 $message = sprintf(__("We can\'t update your %s file.", 'wp-optimize'), $this->_htaccess->get_filename()) . ' ' . __('Please try to remove following lines manually:', 'wp-optimize');
269 } else {
270 // translators: %s is a file name
271 $message = sprintf(__("We can\'t update your %s file.", 'wp-optimize'), $this->_htaccess->get_filename()) . ' ' . __('Please try to add following lines manually:', 'wp-optimize');
272 }
273
274 return array(
275 'success' => false,
276 'enabled' => is_wp_error($is_gzip_compression_enabled) ? false : $is_gzip_compression_enabled,
277 'message' => $message,
278 'output' =>
279 htmlentities($this->_htaccess->get_section_begin_comment($this->_htaccess_section_comment).PHP_EOL.
280 join(PHP_EOL, $this->_htaccess->get_flat_array($gzip_section)).
281 PHP_EOL.$this->_htaccess->get_section_end_comment($this->_htaccess_section_comment)),
282 );
283 }
284 }
285
286 /**
287 * Retrieve the compression statuses for HTML, CSS, and JS resources in HTML format.
288 *
289 * @param bool $use_cache if true, the cached value will be returned if possible
290 * @return string
291 */
292 public function get_compression_test_results_html($use_cache = false) {
293
294 $compression_types = $this->get_compression_types($use_cache);
295
296 $result_html = '';
297
298 foreach ($compression_types as $title => $compression) {
299 if (is_wp_error($compression)) {
300 $value = $compression->get_error_message();
301 } elseif (!empty($compression)) {
302 $value = '<b>'.$compression.'</b>';
303 } else {
304 $value = __('No compression', 'wp-optimize');
305 }
306
307 $result_html .= $title.': '. $value . '<br>';
308 }
309
310 return wp_kses_post($result_html);
311 }
312
313 /**
314 * Get list of content types for which enabled/disabled gzip compression (html, css, js).
315 *
316 * @param bool $use_cache if true, the cached value will be returned if possible
317 *
318 * @return array
319 */
320 public function get_enabled_disabled_compression_content_types($use_cache = false) {
321 $result = array(
322 'enabled' => array(),
323 'disabled' => array(),
324 );
325
326 $compression_types = $this->get_compression_types($use_cache);
327
328 foreach ($compression_types as $type => $status) {
329 if (false !== $status && '' !== $status && !is_wp_error($status)) {
330 $result['enabled'][] = $type;
331 } else {
332 $result['disabled'][] = $type;
333 }
334 }
335
336 return $result;
337 }
338
339 /**
340 * Get compression types for html, css, js files.
341 *
342 * @param bool $use_cache if true, the cached value will be returned if possible
343 *
344 * @return array
345 */
346 private function get_compression_types($use_cache = false) {
347 $transient_key = 'wpo_compression_test_results';
348 $compression_types = array('HTML' => '', 'CSS' => '', 'JS' => '');
349 $cached_compression_types = array();
350
351 if ($use_cache) {
352 $cached_compression_types = get_transient($transient_key);
353 }
354
355 $should_update_cache = false;
356
357 foreach ($compression_types as $content_type => $compression) {
358 // if we have cached value and it is not WP_Error then use it, otherwise get compression type and update cache later.
359 if ($use_cache && isset($cached_compression_types[$content_type]) && 'WP_Error' !== $cached_compression_types[$content_type]) {
360 $compression_types[$content_type] = $cached_compression_types[$content_type];
361 } else {
362 $compression_types[$content_type] = $this->get_compression_type_by_content_type($content_type);
363 $should_update_cache = true;
364 }
365 }
366
367 if ($should_update_cache) {
368 $compression_types_for_cache = $compression_types;
369 foreach ($compression_types_for_cache as $content_type => $compression) {
370 if (is_wp_error($compression)) {
371 $compression_types_for_cache[$content_type] = 'WP_Error';
372 }
373 }
374 set_transient($transient_key, $compression_types_for_cache, HOUR_IN_SECONDS);
375 }
376
377 return $compression_types;
378 }
379
380 /**
381 * Get compression type for the given content type (html, css, js).
382 *
383 * @param string $content_type - content type (html, css, js)
384 * @return string|false|WP_Error
385 */
386 private function get_compression_type_by_content_type($content_type) {
387 $content_type = strtoupper($content_type);
388 switch ($content_type) {
389 case 'HTML':
390 return $this->get_compression_type(get_home_url(null, '?cache=false'));
391 case 'CSS':
392 return $this->get_compression_type($this->css_resource_url());
393 case 'JS':
394 return $this->get_compression_type(site_url('wp-includes/js/admin-bar.min.js'));
395 default:
396 return '';
397 }
398 }
399
400 /**
401 * Get URL to the CSS resource for checking Gzip compression status of CSS files.
402 *
403 * @return string
404 */
405 public function css_resource_url() {
406 return WPO_PLUGIN_URL . 'css/wp-optimize-admin.css';
407 }
408
409 /**
410 * Prepare array with options to switch on gzip in htaccess.
411 *
412 * @return array
413 */
414 private function prepare_gzip_section() {
415 return array(
416 array(
417 '<IfModule mod_filter.c>',
418 array(
419 '<IfModule mod_deflate.c>',
420 '# Compress HTML, CSS, JavaScript, Text, XML and fonts',
421 'AddType application/vnd.ms-fontobject .eot',
422 'AddType font/ttf .ttf',
423 'AddType font/otf .otf',
424 'AddType font/x-woff .woff',
425 'AddType image/svg+xml .svg',
426 '',
427 'AddOutputFilterByType DEFLATE application/javascript',
428 'AddOutputFilterByType DEFLATE application/rss+xml',
429 'AddOutputFilterByType DEFLATE application/vnd.ms-fontobject',
430 'AddOutputFilterByType DEFLATE application/x-font',
431 'AddOutputFilterByType DEFLATE application/x-font-opentype',
432 'AddOutputFilterByType DEFLATE application/x-font-otf',
433 'AddOutputFilterByType DEFLATE application/x-font-truetype',
434 'AddOutputFilterByType DEFLATE application/x-font-ttf',
435 'AddOutputFilterByType DEFLATE application/x-font-woff',
436 'AddOutputFilterByType DEFLATE application/x-javascript',
437 'AddOutputFilterByType DEFLATE application/xhtml+xml',
438 'AddOutputFilterByType DEFLATE application/xml',
439 'AddOutputFilterByType DEFLATE font/opentype',
440 'AddOutputFilterByType DEFLATE font/otf',
441 'AddOutputFilterByType DEFLATE font/ttf',
442 'AddOutputFilterByType DEFLATE font/woff',
443 'AddOutputFilterByType DEFLATE image/svg+xml',
444 'AddOutputFilterByType DEFLATE image/x-icon',
445 'AddOutputFilterByType DEFLATE text/css',
446 'AddOutputFilterByType DEFLATE text/html',
447 'AddOutputFilterByType DEFLATE text/javascript',
448 'AddOutputFilterByType DEFLATE text/plain',
449 'AddOutputFilterByType DEFLATE text/xml',
450 '',
451 '# Remove browser bugs (only needed for really old browsers)',
452 'BrowserMatch ^Mozilla/4 gzip-only-text/html',
453 'BrowserMatch ^Mozilla/4\.0[678] no-gzip',
454 'BrowserMatch \bMSIE !no-gzip !gzip-only-text/html',
455 array(
456 '<IfModule mod_headers.c>',
457 'Header append Vary User-Agent',
458 '</IfModule>',
459 ),
460 '</IfModule>',
461 ),
462 '</IfModule>',
463 ),
464 );
465 }
466 }
467