| @@ -1,5 +1,6 @@ | ||
| 1 | 1 | <?php |
| 2 | +if (!defined('ABSPATH')) die('Access denied.'); | |
| 2 | 3 | |
| 3 | 4 | /** |
| 4 | 5 | * Class WP_Optimize_Gzip_Compression |
| 5 | 6 | */ |
| @@ -4,14 +5,16 @@ | ||
| 4 | 5 | * Class WP_Optimize_Gzip_Compression |
| 5 | 6 | */ |
| 6 | 7 | class WP_Optimize_Gzip_Compression { |
| 7 | 8 | |
| 9 | + use WP_Optimize_HTTP_Error_Codes_Trait; | |
| 10 | + | |
| 8 | 11 | /** |
| 9 | 12 | * WP_Optimize_Htaccess instance. |
| 10 | 13 | * |
| 11 | 14 | * @var WP_Optimize_Htaccess |
| 12 | 15 | */ |
| 13 | - private $_htaccess = null; | |
| 16 | + private $_htaccess; | |
| 14 | 17 | |
| 15 | 18 | /** |
| 16 | 19 | * WP_Optimize instance. |
| 17 | 20 | * |
| @@ -16,12 +19,12 @@ | ||
| 16 | 19 | * WP_Optimize instance. |
| 17 | 20 | * |
| 18 | 21 | * @var WP_Optimize |
| 19 | 22 | */ |
| 20 | - private $_wp_optimize = null; | |
| 23 | + private $_wp_optimize; | |
| 21 | 24 | |
| 22 | 25 | /** |
| 23 | - * Gzip section in htaccess will wrapped with this comment | |
| 26 | + * Gzip section in htaccess will be wrapped with this comment | |
| 24 | 27 | * |
| 25 | 28 | * @var string |
| 26 | 29 | */ |
| 27 | 30 | private $_htaccess_section_comment = 'WP-Optimize Gzip compression'; |
| @@ -47,20 +50,42 @@ | ||
| 47 | 50 | return $_instance; |
| 48 | 51 | } |
| 49 | 52 | |
| 50 | 53 | /** |
| 51 | - * Make http request to theme style.css, get 'server' line and check headers for gzip/brotli encoding option. | |
| 54 | + * Make http request to $url, get 'server' line and check headers for gzip/brotli encoding option. | |
| 52 | 55 | * |
| 56 | + * @param string $url | |
| 57 | + * | |
| 53 | 58 | * @return array|WP_Error |
| 54 | 59 | */ |
| 55 | - public function get_headers_information() { | |
| 56 | - static $headers_information; | |
| 57 | - if (isset($headers_information)) return $headers_information; | |
| 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]; | |
| 58 | 63 | |
| 59 | - $headers = WP_Optimize()->get_stylesheet_headers(); | |
| 64 | + $response = wp_remote_get($url, array('timeout' => 10)); | |
| 60 | 65 | |
| 61 | - if (is_wp_error($headers)) return $headers; | |
| 66 | + if (is_wp_error($response)) { | |
| 67 | + return $response; | |
| 68 | + } | |
| 62 | 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 | + | |
| 63 | 88 | $headers_information = array( |
| 64 | 89 | 'server' => array_key_exists('server', $headers) ? $headers['server'] : '', |
| 65 | 90 | ); |
| 66 | 91 | |
| @@ -70,13 +95,17 @@ | ||
| 70 | 95 | $this->disable(); |
| 71 | 96 | } elseif (array_key_exists('content-encoding', $headers) && preg_match('/gzip/i', $headers['content-encoding'])) { |
| 72 | 97 | // check if there exists Content-encoding header with gzip value. |
| 73 | 98 | $headers_information['compression'] = 'gzip'; |
| 74 | - | |
| 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'; | |
| 75 | 102 | } else { |
| 76 | 103 | $headers_information['compression'] = false; |
| 77 | 104 | } |
| 78 | 105 | |
| 106 | + $cached_headers_information[$url] = $headers_information; | |
| 107 | + | |
| 79 | 108 | return $headers_information; |
| 80 | 109 | } |
| 81 | 110 | |
| 82 | 111 | /** |
| @@ -108,28 +137,24 @@ | ||
| 108 | 137 | |
| 109 | 138 | /** |
| 110 | 139 | * Check if Gzip compression is enabled. |
| 111 | 140 | * |
| 112 | - * @param boolean $force_check - force the check | |
| 141 | + * @param boolean $use_cache - use cached data | |
| 113 | 142 | * @return bool|WP_Error |
| 114 | 143 | */ |
| 115 | - public function is_gzip_compression_enabled($force_check = false) { | |
| 144 | + public function is_gzip_compression_enabled($use_cache = true) { | |
| 116 | 145 | |
| 117 | - if (!$force_check) return WP_Optimize()->get_options()->get_option('is_gzip_compression_enabled'); | |
| 146 | + $compression_types = $this->get_compression_types($use_cache); | |
| 118 | 147 | |
| 119 | - // trying to get info about gzip in headers. | |
| 120 | - $headers_info = $this->get_headers_information(); | |
| 148 | + $is_gzip_compression_enabled = true; | |
| 121 | 149 | |
| 122 | - // we can't determine then return WP_Error. | |
| 123 | - if (is_wp_error($headers_info)) return $headers_info; | |
| 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 | + } | |
| 124 | 154 | |
| 125 | - $is_gzip_compression_enabled = $headers_info['compression']; | |
| 126 | - | |
| 127 | - // if we got error then trying to get info from api otherwise get result from check_headers_for_gzip(). | |
| 128 | - // $is_gzip_compression_enabled = is_wp_error($is_gzip_compression_enabled) ? $this->check_api_for_gzip() : $is_gzip_compression_enabled; | |
| 129 | - | |
| 130 | - // if Gzip is not enabled but we have added settings and Apache modules nt loaded then return error. | |
| 131 | - if (false == $is_gzip_compression_enabled && $this->is_gzip_compression_section_exists()) { | |
| 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()) { | |
| 132 | 157 | if (false === $this->_wp_optimize->is_apache_module_loaded(array('mod_filter', 'mod_deflate'))) { |
| 133 | 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')); |
| 134 | 159 | } elseif (WP_Optimize()->is_apache_server()) { |
| 135 | 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')); |
| @@ -137,14 +162,35 @@ | ||
| 137 | 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')); |
| 138 | 163 | } |
| 139 | 164 | } |
| 140 | 165 | |
| 141 | - WP_Optimize()->get_options()->update_option('is_gzip_compression_enabled', $is_gzip_compression_enabled); | |
| 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 | + } | |
| 142 | 171 | |
| 143 | 172 | return $is_gzip_compression_enabled; |
| 144 | 173 | } |
| 145 | 174 | |
| 146 | 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 | + /** | |
| 147 | 193 | * Check if section with Gzip options already exists in htaccess file. |
| 148 | 194 | * |
| 149 | 195 | * @return bool |
| 150 | 196 | */ |
| @@ -185,9 +231,9 @@ | ||
| 185 | 231 | */ |
| 186 | 232 | public function enable_gzip_command_handler($params) { |
| 187 | 233 | $section_updated = false; |
| 188 | 234 | |
| 189 | - $enable = (isset($params['enable']) && $params['enable']) ? true : false; | |
| 235 | + $enable = isset($params['enable']) && $params['enable']; | |
| 190 | 236 | |
| 191 | 237 | if ($this->_htaccess->is_writable()) { |
| 192 | 238 | |
| 193 | 239 | // update commented section |
| @@ -204,9 +250,9 @@ | ||
| 204 | 250 | // set correct $section-updated flag. |
| 205 | 251 | $section_updated = $enable === $section_exists; |
| 206 | 252 | } |
| 207 | 253 | |
| 208 | - $is_gzip_compression_enabled = $this->is_gzip_compression_enabled(true); | |
| 254 | + $is_gzip_compression_enabled = $this->is_gzip_compression_enabled(false); | |
| 209 | 255 | |
| 210 | 256 | if ($section_updated) { |
| 211 | 257 | return array( |
| 212 | 258 | 'success' => true, |
| @@ -217,10 +263,12 @@ | ||
| 217 | 263 | } else { |
| 218 | 264 | $gzip_section = $this->prepare_gzip_section(); |
| 219 | 265 | |
| 220 | 266 | if ($is_gzip_compression_enabled) { |
| 267 | + // translators: %s is a file name | |
| 221 | 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'); |
| 222 | 269 | } else { |
| 270 | + // translators: %s is a file name | |
| 223 | 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'); |
| 224 | 272 | } |
| 225 | 273 | |
| 226 | 274 | return array( |
| @@ -232,8 +280,131 @@ | ||
| 232 | 280 | join(PHP_EOL, $this->_htaccess->get_flat_array($gzip_section)). |
| 233 | 281 | PHP_EOL.$this->_htaccess->get_section_end_comment($this->_htaccess_section_comment)), |
| 234 | 282 | ); |
| 235 | 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'; | |
| 236 | 407 | } |
| 237 | 408 | |
| 238 | 409 | /** |
| 239 | 410 | * Prepare array with options to switch on gzip in htaccess. |