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
← All changes | includes/class-wp-optimize-browser-cache.php +333 -87 3.2.20 → 4.7.0 View file →
@@ -1,23 +1,23 @@
1 1 <?php
2 2
3 -if (!defined('WPO_VERSION')) die('No direct access allowed');
3 +if (!defined('ABSPATH')) die('No direct access allowed');
4 4
5 -require_once 'class-wp-optimize-htaccess.php';
6 -
7 5 /**
8 6 * Class WP_Optimize_Browser_Cache
9 7 */
10 8 class WP_Optimize_Browser_Cache {
11 9
12 - private $_htaccess = null;
10 + use WP_Optimize_HTTP_Error_Codes_Trait;
13 11
14 - private $_options = null;
12 + private $_htaccess;
15 13
16 - private $_wp_optimize = null;
14 + private $_options;
17 15
16 + private $_wp_optimize;
17 +
18 18 /**
19 - * Browser cache section in htaccess will wrapped with this comment
19 + * Browser cache section in htaccess will be wrapped with this comment
20 20 *
21 21 * @var string
22 22 */
23 23 private $_htaccess_section_comment = 'WP-Optimize Browser Cache';
@@ -30,8 +30,10 @@
30 30
31 31 $this->_htaccess = new WP_Optimize_Htaccess();
32 32
33 33 $this->_options = $this->_wp_optimize->get_options();
34 +
35 + $this->maybe_restore_expire_values_from_htaccess();
34 36 }
35 37
36 38 /**
37 39 * Returns singleton instance object
@@ -51,17 +53,14 @@
51 53 *
52 54 * @return bool|WP_Error
53 55 **/
54 56 public function is_enabled() {
57 + $headers = $this->get_stylesheet_headers();
55 58
56 - static $is_enabled;
57 - if (isset($is_enabled)) return $is_enabled;
58 -
59 - $headers = WP_Optimize()->get_stylesheet_headers();
60 -
61 59 if (is_wp_error($headers)) return $headers;
62 60
63 - if (array_key_exists('cache-control', $headers) && array_key_exists('expires', $headers)) {
61 + $cache_control = isset($headers['cache-control']) ? (is_array($headers['cache-control']) ? implode(', ', $headers['cache-control']) : $headers['cache-control']) : '';
62 + if (array_key_exists('expires', $headers) && array_key_exists('cache-control', $headers) && preg_match('/\b(public|private)\b/i', $cache_control)) {
64 63 $is_enabled = true;
65 64 } else {
66 65 $is_enabled = false;
67 66 }
@@ -73,14 +72,62 @@
73 72 return $is_enabled;
74 73 }
75 74
76 75 /**
76 + * Get expire days and hours values from options.
77 + *
78 + * @return array // [0] - expire days, [1] - expire hours
79 + */
80 + private function get_expire_data() {
81 + return array(
82 + absint($this->_options->get_option('browser_cache_expire_days', 0)),
83 + absint($this->_options->get_option('browser_cache_expire_hours', 0)),
84 + );
85 + }
86 +
87 +
88 + /**
89 + * Check if .htaccess has already updated settings (manually by user) then reset error message output
90 + *
91 + * @return void
92 + */
93 + public function maybe_reset_error_message_output() {
94 + $error_message = $this->_options->get_option('browser_cache_error_message', '');
95 + if ('' === $error_message) return;
96 +
97 + list($expire_days, $expire_hours) = $this->get_expire_data();
98 +
99 + $expire = $this->prepare_interval($expire_days, $expire_hours);
100 + $search = 'ExpiresByType text/css "access '.$expire.'"';
101 +
102 + // if we already have setting in the file then we can reset error message output
103 + if (false !== strpos($this->_htaccess->get_content(), $search)) {
104 + $this->_options->update_option('browser_cache_error_message', '');
105 + $this->_options->update_option('browser_cache_output', '');
106 + }
107 + }
108 +
109 + /**
110 + * Set expire days and hours values.
111 + *
112 + * @param int $expire_days
113 + * @param int $expire_hours
114 + * @return void
115 + */
116 + private function set_expire_values($expire_days, $expire_hours) {
117 + $this->_options->update_option('browser_cache_expire_days', absint($expire_days));
118 + $this->_options->update_option('browser_cache_expire_hours', absint($expire_hours));
119 + }
120 +
121 + /**
77 122 * Enable browser cache - add settings into .htaccess.
78 123 *
79 - * @param string $expiry_time
124 + * @return void
80 125 */
81 - public function enable($expiry_time = '1 month') {
82 - $this->_htaccess->update_commented_section($this->prepare_browser_cache_section($expiry_time), $this->_htaccess_section_comment);
126 + public function enable() {
127 + list($expire_days, $expire_hours) = $this->get_expire_data();
128 +
129 + $this->_htaccess->update_commented_section($this->prepare_browser_cache_section($expire_days, $expire_hours), $this->_htaccess_section_comment);
83 130 $this->_htaccess->write_file();
84 131 $this->_options->update_option('enable_browser_cache', true);
85 132 }
86 133
@@ -85,8 +132,10 @@
85 132 }
86 133
87 134 /**
88 135 * Disable cache - remove settings from .htaccess added in enable() function.
136 + *
137 + * @return void
89 138 */
90 139 public function disable() {
91 140 $this->_htaccess->remove_commented_section($this->_htaccess_section_comment);
92 141 $this->_htaccess->write_file();
@@ -93,23 +142,62 @@
93 142 $this->_options->update_option('enable_browser_cache', false);
94 143 }
95 144
96 145 /**
97 - * Check if browser cache option is set to true then add section with gzip settings into .htaccess (used when plugin being activated).
146 + * Restore expiration settings from .htaccess if the options are empty.
147 + *
148 + * @return void
98 149 */
99 - public function restore() {
150 + public function maybe_restore_expire_values_from_htaccess() {
100 151 $expire_days = $this->_options->get_option('browser_cache_expire_days', '');
101 152 $expire_hours = $this->_options->get_option('browser_cache_expire_hours', '');
102 153
103 - $expiry_time = $this->prepare_interval($expire_days, $expire_hours);
154 + if ('' === $expire_days && '' === $expire_hours) {
155 + $this->restore_expire_values_from_htaccess();
156 + }
157 + }
104 158
105 - $enabled = ('' == $expiry_time) ? false : true;
159 + /**
160 + * Check if browser cache option is set to true then add section with browser cache settings into .htaccess.
161 + *
162 + * @return void
163 + */
164 + public function restore() {
165 + list($expire_days, $expire_hours) = $this->get_expire_data();
106 166
107 - if ($enabled && $this->_htaccess->is_writable()) $this->enable($expiry_time);
167 + $enabled = 0 !== $expire_days || 0 !== $expire_hours;
168 +
169 + // If we don't have values in the database try to read them from .htaccess file
170 + if (!$enabled) {
171 + $this->restore_expire_values_from_htaccess();
172 +
173 + list($expire_days, $expire_hours) = $this->get_expire_data();
174 + $enabled = 0 !== $expire_days || 0 !== $expire_hours;
175 + }
176 +
177 + if ($enabled && $this->_htaccess->is_writable()) $this->enable();
108 178 }
109 179
110 180 /**
181 + * Restore expire days and expire hours values form .htaccess if possible.
182 + *
183 + * @return void
184 + */
185 + private function restore_expire_values_from_htaccess() {
186 + $htaccess_values = $this->get_browser_cache_section_expire_values();
187 + list($expire_days, $expire_hours) = $htaccess_values;
188 +
189 + $enabled = 0 !== $expire_days || 0 !== $expire_hours;
190 +
191 + if ($enabled) {
192 + $this->set_expire_values($expire_days, $expire_hours);
193 + }
194 + }
195 +
196 + /**
111 197 * Check if section with browser cache settings already exists.
198 + *
199 + * @return bool
112 200 */
113 201 public function is_browser_cache_section_exists() {
114 202 return $this->_htaccess->is_commented_section_exists($this->_htaccess_section_comment);
115 203 }
@@ -114,31 +202,106 @@
114 202 return $this->_htaccess->is_commented_section_exists($this->_htaccess_section_comment);
115 203 }
116 204
117 205 /**
206 + * Get expire days and hours values from the .htaccess file
207 + *
208 + * @return array|bool
209 + */
210 + private function get_browser_cache_section_expire_values() {
211 + $section_content = $this->_htaccess->get_section_content();
212 +
213 + if (preg_match('/ExpiresByType text\/css "access ([0-9a-z\s]+)"/i', $section_content, $matches)) {
214 + return $this->expire_string_to_days_hours($matches[1]);
215 + }
216 +
217 + return array(0, 0);
218 + }
219 +
220 + /**
221 + * Returns days and hours values from expire string
222 + *
223 + * @param string $string
224 + * @return array
225 + */
226 + private function expire_string_to_days_hours($string) {
227 + $days = 0;
228 + $hours = 0;
229 +
230 + preg_match_all(
231 + '/(\d+)\s+(year|years|month|months|day|days|hour|hours)/i',
232 + $string,
233 + $matches,
234 + PREG_SET_ORDER
235 + );
236 +
237 + foreach ($matches as $match) {
238 + $value = (int) $match[1];
239 + $unit = strtolower($match[2]);
240 +
241 + switch ($unit) {
242 + case 'year':
243 + case 'years':
244 + $days += $value * 365;
245 + break;
246 +
247 + case 'month':
248 + case 'months':
249 + $days += $value * 30;
250 + break;
251 +
252 + case 'day':
253 + case 'days':
254 + $days += $value;
255 + break;
256 +
257 + case 'hour':
258 + case 'hours':
259 + $hours += $value;
260 + break;
261 + }
262 + }
263 +
264 + // Convert extra hours into days
265 + $days += intdiv($hours, 24);
266 + $hours %= 24;
267 +
268 + return array(absint($days), absint($hours));
269 + }
270 +
271 + /**
118 272 * Handle for enable_browser_cache command used in WP_Optimize_Commands.
119 273 *
120 274 * @param array $params - ['browser_cache_expire' => '1 month 15 days 2 hours' || '' - for disable cache]
121 275 * @return array
122 276 */
123 - public function enable_browser_cache_command_handler($params) {
124 - $expire_days = isset($params['browser_cache_expire_days']) ? $params['browser_cache_expire_days'] : '';
125 - $expire_hours = isset($params['browser_cache_expire_hours']) ? $params['browser_cache_expire_hours'] : '';
277 + public function enable_browser_cache_command_handler(array $params): array {
278 + $expire_days = absint($params['browser_cache_expire_days']);
279 + $expire_hours = absint($params['browser_cache_expire_hours']);
126 280
127 - $current_expire_days = $this->_options->get_option('browser_cache_expire_days', '');
128 - $current_expire_hours = $this->_options->get_option('browser_cache_expire_hours', '');
281 + list($current_expire_days, $current_expire_hours) = $this->get_browser_cache_section_expire_values();
129 282
130 - $section_updated = false;
283 + // clear error message output
284 + $this->_options->update_option('browser_cache_error_message', '');
285 + $this->_options->update_option('browser_cache_output', '');
131 286
132 - $expiry_time = $this->prepare_interval($expire_days, $expire_hours);
287 + $enable = 0 !== $expire_days || 0 !== $expire_hours;
133 288
134 - $enable = ('' == $expiry_time) ? false : true;
289 + // Store new expire values in the database
290 + $this->set_expire_values($expire_days, $expire_hours);
135 291
136 292 /**
137 293 * If we don't need to do anything in .htaccess then return message.
138 294 */
139 - if ($enable == $this->_htaccess->is_commented_section_exists() && $expire_days == $current_expire_days && $expire_hours == $current_expire_hours) {
140 - $message = __('Browser static caching settings already exists in the .htaccess file', 'wp-optimize');
295 + if ($enable === $this->_htaccess->is_commented_section_exists()
296 + && $expire_days === $current_expire_days
297 + && $expire_hours === $current_expire_hours
298 + ) {
299 + if ($enable) {
300 + $message = __('Browser static caching settings already exists in the .htaccess file', 'wp-optimize');
301 + } else {
302 + $message = '';
303 + }
141 304
142 305 return array(
143 306 'success' => true,
144 307 'enabled' => $enable,
@@ -145,63 +308,51 @@
145 308 'message' => $message,
146 309 );
147 310 }
148 311
149 - if ($this->_htaccess->is_writable()) {
150 - // update commented section
312 + $section_updated = $this->update_htaccess_section($enable);
151 313
152 - if ($enable) {
153 - $this->enable($expiry_time);
154 - } else {
155 - $this->disable();
156 - }
314 + $is_enabled = $this->is_enabled();
157 315
158 - // read updated file.
159 - $this->_htaccess->read_file();
160 - // check if section added or removed successfully.
161 - $section_exists = $this->_htaccess->is_commented_section_exists();
162 - // set correct $section-updated flag.
163 - $section_updated = $enable === $section_exists;
164 - }
165 -
166 316 if ($section_updated) {
167 - $enabled = $this->is_enabled();
168 317
169 - // save $expire value to options.
170 - $this->_options->update_option('browser_cache_expire_days', $expire_days);
171 - $this->_options->update_option('browser_cache_expire_hours', $expire_hours);
172 -
173 - if (is_wp_error($enabled)) {
318 + if (is_wp_error($is_enabled)) {
174 319 return array(
175 320 'success' => true,
176 - 'enabled' => $enabled,
177 - 'error_message' => $enabled->get_error_message(),
321 + 'enabled' => false,
322 + 'error_message' => $is_enabled->get_error_message(),
178 323 );
179 324 } else {
180 325 return array(
181 326 'success' => true,
182 - 'enabled' => $enabled,
327 + 'enabled' => $is_enabled,
183 328 'message' => __('We successfully updated your .htaccess file.', 'wp-optimize'),
184 329 );
185 330 }
186 331 } else {
187 - $cache_section = $this->prepare_browser_cache_section($expiry_time);
188 332
189 333 if ($enable) {
190 - $message = sprintf(__("We can\'t update your %s file.", 'wp-optimize'), $this->_htaccess->get_filename()) . ' ' . __('Please try to add following lines manually:', 'wp-optimize');
191 - $output = htmlentities($this->_htaccess->get_section_begin_comment() . PHP_EOL .
192 - join(PHP_EOL, $this->_htaccess->get_flat_array($cache_section)).
193 - PHP_EOL . $this->_htaccess->get_section_end_comment());
334 + // translators: %s is a file name
335 + $message = sprintf(__("We can't update your %s file.", 'wp-optimize'), $this->_htaccess->get_filename()) . ' ' . __('Please try to add following lines manually:', 'wp-optimize');
194 336 } else {
195 - $message = sprintf(__("We can\'t update your %s file.", 'wp-optimize'), $this->_htaccess->get_filename()) . ' ' . __('Please try to remove following lines manually:', 'wp-optimize');
196 - $output = htmlentities($this->_htaccess->get_section_begin_comment() . PHP_EOL .
197 - ' ... ... ... '.
198 - PHP_EOL . $this->_htaccess->get_section_end_comment());
337 + // translators: %s is a file name
338 + $message = sprintf(__("We can't update your %s file.", 'wp-optimize'), $this->_htaccess->get_filename()) . ' ' . __('Please try to remove following lines manually:', 'wp-optimize');
199 339 }
200 340
341 + $output = htmlentities($this->get_htaccess_section_output($enable));
342 +
343 + // store error message output
344 + $this->_options->update_option('browser_cache_error_message', $message);
345 + $this->_options->update_option('browser_cache_output', $output);
346 +
347 + if (is_wp_error($is_enabled)) {
348 + $message .= ' ' .$is_enabled->get_error_message();
349 + $is_enabled = false;
350 + }
351 +
201 352 return array(
202 353 'success' => false,
203 - 'enabled' => $this->is_enabled(),
354 + 'enabled' => $is_enabled,
204 355 'error_message' => $message,
205 356 'output' => $output,
206 357 );
207 358 }
@@ -207,20 +358,68 @@
207 358 }
208 359 }
209 360
210 361 /**
211 - * Use $days an $hours values to build correct time interval as a string like '2 days 3 hours' or empty string if date is empty.
362 + * Update the .htaccess section for browser caching.
212 363 *
364 + * @param boolean $enable
365 + * @return bool True if section updated successfully, false otherwise
366 + */
367 + private function update_htaccess_section(bool $enable) {
368 +
369 + if (!$this->_htaccess->is_writable()) {
370 + return false;
371 + }
372 +
373 + // update commented section
374 + if ($enable) {
375 + $this->enable();
376 + } else {
377 + $this->disable();
378 + }
379 +
380 + // read updated file.
381 + $this->_htaccess->read_file();
382 + // check if section added or removed successfully.
383 + $section_exists = $this->_htaccess->is_commented_section_exists();
384 + // return true if section updated successfully, false otherwise.
385 + return $enable === $section_exists;
386 + }
387 +
388 + /**
389 + * Get output content for enable/disable browser cache in .htaccess.
390 + *
391 + * @param bool $enable
392 + * @return string
393 + */
394 + private function get_htaccess_section_output($enable) {
395 + list($expire_days, $expire_hours) = $this->get_expire_data();
396 +
397 + if ($enable) {
398 + $cache_section = $this->prepare_browser_cache_section($expire_days, $expire_hours);
399 +
400 + $output = $this->_htaccess->get_section_begin_comment() . PHP_EOL .
401 + join(PHP_EOL, $this->_htaccess->get_flat_array($cache_section)).
402 + PHP_EOL . $this->_htaccess->get_section_end_comment();
403 + } else {
404 + $output = $this->_htaccess->get_section_begin_comment() . PHP_EOL .
405 + ' ... ... ... '.
406 + PHP_EOL . $this->_htaccess->get_section_end_comment();
407 + }
408 +
409 + return $output;
410 + }
411 +
412 + /**
413 + * Use $days and $hours values to build correct time interval as a string like '2 days 3 hours' or empty string if date is empty.
414 + *
213 415 * @param int $days
214 416 * @param int $hours
215 417 * @return string
216 418 */
217 - private function prepare_interval($days, $hours) {
419 + private function prepare_interval(int $days, int $hours): string {
218 420
219 - $days = is_numeric($days) ? floor($days) : 0;
220 - $hours = is_numeric($hours) ? floor($hours) : 0;
221 -
222 - if (0 == $days && 0 == $hours) {
421 + if (0 === $days && 0 === $hours) {
223 422 return '';
224 423 }
225 424
226 425 $parts = array();
@@ -253,20 +452,42 @@
253 452 return join(' ', $parts);
254 453 }
255 454
256 455 /**
456 + * Converts a human-readable time string into total seconds
457 + *
458 + * @param int $days
459 + * @param int $hours
460 + * @return string Total number of seconds
461 + */
462 + private function convert_to_seconds(int $days, int $hours): string {
463 + // Split the string into parts
464 + $total_seconds = $days * 24 * 60 * 60 + $hours * 60 * 60;
465 +
466 + return strval($total_seconds);
467 + }
468 +
469 + /**
257 470 * Build browser cache section array.
258 471 *
259 - * @param string $expire - value like - 1 day 12 hours 15 minutes
472 + * @param int $expire_days
473 + * @param int $expire_hours
260 474 * @return array
261 475 */
262 - public function prepare_browser_cache_section($expire) {
476 + public function prepare_browser_cache_section(int $expire_days, int $expire_hours): array {
477 + $expire = $this->prepare_interval($expire_days, $expire_hours);
478 + $max_age = $this->convert_to_seconds($expire_days, $expire_hours);
479 +
263 480 return array(
264 481 array(
482 + '<IfModule mod_setenvif.c>',
483 + 'SetEnvIf Request_URI "/wp-admin/" WPO_NO_CACHE',
484 + '</IfModule>',
485 + ),
486 + array(
265 487 '<IfModule mod_expires.c>',
266 488 'ExpiresActive On',
267 489 'ExpiresByType text/css "access '.$expire.'"',
268 - 'ExpiresByType text/html "access '.$expire.'"',
269 490 'ExpiresByType image/gif "access '.$expire.'"',
270 491 'ExpiresByType image/png "access '.$expire.'"',
271 492 'ExpiresByType image/jpg "access '.$expire.'"',
272 493 'ExpiresByType image/jpeg "access '.$expire.'"',
@@ -275,9 +496,19 @@
275 496 'ExpiresByType application/pdf "access '.$expire.'"',
276 497 'ExpiresByType application/javascript "access '.$expire.'"',
277 498 'ExpiresByType text/x-javascript "access '.$expire.'"',
278 499 'ExpiresByType application/x-shockwave-flash "access '.$expire.'"',
279 - 'ExpiresDefault "access '.$expire.'"',
500 + 'ExpiresByType image/svg+xml "access '.$expire.'"',
501 + 'ExpiresByType font/woff "access '.$expire.'"',
502 + 'ExpiresByType font/woff2 "access '.$expire.'"',
503 + 'ExpiresByType font/x-woff "access '.$expire.'"',
504 + 'ExpiresByType application/font-woff "access '.$expire.'"',
505 + 'ExpiresByType application/x-font-woff "access '.$expire.'"',
506 + 'ExpiresByType font/ttf "access '.$expire.'"',
507 + 'ExpiresByType font/otf "access '.$expire.'"',
508 + 'ExpiresByType application/vnd.ms-fontobject "access '.$expire.'"',
509 + 'ExpiresByType application/json "access 0 seconds"',
510 + 'ExpiresByType application/xml "access 0 seconds"',
280 511 '</IfModule>',
281 512 ),
282 513 '',
283 514 array(
@@ -282,27 +513,42 @@
282 513 '',
283 514 array(
284 515 '<IfModule mod_headers.c>',
285 516 array(
286 - '<filesMatch "\.(ico|jpe?g|png|gif|webp|swf)$">',
287 - 'Header set Cache-Control "public"',
288 - '</filesMatch>',
517 + '<FilesMatch "\.(ico|jpe?g|png|gif|webp|swf|svg)$">',
518 + 'Header set Cache-Control "public, max-age='.$max_age.'" env=!WPO_NO_CACHE',
519 + '</FilesMatch>',
289 520 ),
290 521 array(
291 - '<filesMatch "\.(css)$">',
292 - 'Header set Cache-Control "public"',
293 - '</filesMatch>',
522 + '<FilesMatch "\.(css)$">',
523 + 'Header set Cache-Control "public, max-age='.$max_age.'" env=!WPO_NO_CACHE',
524 + '</FilesMatch>',
294 525 ),
295 526 array(
296 - '<filesMatch "\.(js)$">',
297 - 'Header set Cache-Control "private"',
298 - '</filesMatch>',
527 + '<FilesMatch "\.(js)$">',
528 + 'Header set Cache-Control "private, max-age='.$max_age.'" env=!WPO_NO_CACHE',
529 + '</FilesMatch>',
299 530 ),
300 531 array(
301 - '<filesMatch "\.(x?html?|php)$">',
302 - 'Header set Cache-Control "private, must-revalidate"',
303 - '</filesMatch>',
532 + '<FilesMatch "\.(woff2?|ttf|otf|eot)$">',
533 + 'Header set Cache-Control "public, max-age='.$max_age.'" env=!WPO_NO_CACHE',
534 + '</FilesMatch>',
304 535 ),
536 + array(
537 + '<FilesMatch "\.(x?html?)$">',
538 + 'Header set Cache-Control "private, must-revalidate, max-age='.$max_age.'" env=!WPO_NO_CACHE',
539 + '</FilesMatch>',
540 + ),
541 + array(
542 + '<FilesMatch "\.php$">',
543 + 'Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0"',
544 + '</FilesMatch>',
545 + ),
546 + '</IfModule>',
547 + ),
548 + array(
549 + '<IfModule mod_headers.c>',
550 + 'Header unset Expires env=WPO_NO_CACHE',
305 551 '</IfModule>',
306 552 ),
307 553 '',
308 554 '#Disable ETag',