PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.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 / cache / class-wpo-page-cache.php

class-wpo-page-cache.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.7.0, at cache/class-wpo-page-cache.php

1,574 lines 48.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Page caching functionality
4 *
5 * Acknowledgement: The page cache functionality was loosely based on the simple cache plugin - https://github.com/tlovett1/simple-cache
6 */
7
8 if (!defined('ABSPATH')) die('No direct access allowed');
9
10 /**
11 * Base cache directory, everything else goes under here
12 */
13 if (!defined('WPO_CACHE_DIR')) define('WPO_CACHE_DIR', untrailingslashit(WP_CONTENT_DIR).'/wpo-cache');
14
15 /**
16 * Extensions directory.
17 */
18 if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', dirname(__FILE__).'/extensions');
19
20 /**
21 * Directory that stores config and related files
22 */
23 if (!defined('WPO_CACHE_CONFIG_DIR')) define('WPO_CACHE_CONFIG_DIR', WPO_CACHE_DIR.'/config');
24
25 /**
26 * Directory that stores the cache, including gzipped files and mobile specific cache
27 */
28 if (!defined('WPO_CACHE_FILES_DIR')) define('WPO_CACHE_FILES_DIR', untrailingslashit(WP_CONTENT_DIR).'/cache/wpo-cache');
29
30 require_once dirname(__FILE__) . '/file-based-page-cache-functions.php';
31
32 if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
33 require_once dirname(__FILE__) . '/php-5.3-functions.php';
34 }
35
36 wpo_cache_load_extensions();
37
38 if (!class_exists('WPO_Page_Cache')) :
39
40 class WPO_Page_Cache {
41
42 /**
43 * Cache config object
44 *
45 * @var mixed
46 */
47 public $config;
48
49 /**
50 * Logger for this class
51 *
52 * @var mixed
53 */
54 public $logger;
55
56 /**
57 * Instance of this class
58 *
59 * @var mixed
60 */
61 public static $instance;
62
63 /**
64 * Store last advanced cache file writing status
65 * If true then last writing finished with error
66 *
67 * @var bool
68 */
69 public $advanced_cache_file_writing_error;
70
71 /**
72 * Store errors
73 *
74 * @var array
75 */
76 private $_errors = array();
77
78 /**
79 * Last advanced cache file content
80 *
81 * @var string
82 */
83 public $advanced_cache_file_content;
84
85 /**
86 * Store the latest advanced-cache.php version required
87 *
88 * @var string
89 */
90 private $_minimum_advanced_cache_file_version = '3.0.17';
91
92 public $should_purge;
93
94 /**
95 * Set everything up here
96 */
97 public function __construct() {
98 $this->config = WPO_Cache_Config::instance();
99 WPO_Cache_Rules::instance();
100 WP_Optimize_Page_Cache_Preloader::instance();
101 $this->logger = new Updraft_PHP_Logger();
102
103 add_action('activated_plugin', array($this, 'activate_deactivate_plugin'));
104 add_action('deactivate_plugin', array($this, 'activate_deactivate_plugin'));
105 add_action('wpo_purge_old_cache', array($this, 'purge_old'));
106
107 /**
108 * Regenerate config file on cache flush.
109 */
110 add_action('wpo_cache_flush', array($this, 'update_cache_config'));
111 add_action('wpo_cache_flush', array($this, 'delete_cache_size_information'));
112 add_action('update_option_permalink_structure', array($this, 'update_option_permalink_structure'), 10, 3);
113
114 // Add purge cache link to admin bar.
115 add_filter('wpo_cache_admin_bar_menu_items', array($this, 'admin_bar_purge_cache'), 20, 1);
116
117 // Handle single page purge.
118 add_action('wp_loaded', array($this, 'handle_purge_single_page_cache'));
119
120 add_action('admin_init', array($this, 'admin_init'));
121
122 add_action('update_option_gmt_offset', array($this, 'update_gmt_offset_timezone_string_config'), 10, 3);
123 add_action('update_option_timezone_string', array($this, 'update_gmt_offset_timezone_string_config'), 10, 3);
124 add_action('update_option_date_format', array($this, 'update_option_date_format'), 10, 2);
125 add_action('update_option_time_format', array($this, 'update_option_time_format'), 10, 2);
126
127 $this->check_compatibility_issues();
128
129 add_filter('cron_schedules', array($this, 'cron_schedules'));
130 add_action('wpo_save_images_settings', array($this, 'update_webp_images_option'));
131
132 add_action('wpo_preload_url', array($this, 'maybe_preload_url'));
133 }
134
135 /**
136 * Activate cron job when the cache is enabled for deleting expired cache files
137 */
138 public function cron_activate() {
139 $page_cache_length = $this->config->get_option('page_cache_length');
140 if ($this->is_enabled()) {
141 if (!wp_next_scheduled('wpo_purge_old_cache')) {
142 wp_schedule_event(time() + (false === $page_cache_length ? '86400' : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
143 }
144 } else {
145 wp_clear_scheduled_hook('wpo_purge_old_cache');
146 }
147 }
148
149 /**
150 * Do required actions on activate/deactivate any plugin.
151 */
152 public function activate_deactivate_plugin() {
153
154 wp_clear_scheduled_hook('wpo_purge_old_cache');
155
156 $this->update_cache_config();
157
158 $this->purge();
159 }
160
161 /**
162 * Check if current user can purge cache.
163 *
164 * @return bool
165 */
166 public function can_purge_cache() {
167 if (!$this->is_enabled()) return false;
168 $required_capability = is_multisite() ? 'manage_network_options' : 'manage_options';
169
170 if (WP_Optimize::is_premium()) {
171 return current_user_can($required_capability) || WP_Optimize_Premium()->can_purge_the_cache();
172 } else {
173 return current_user_can($required_capability);
174 }
175 }
176
177 /**
178 * Add Purge from cache in admin bar.
179 *
180 * @param array $menu_items
181 * @return array
182 */
183 public function admin_bar_purge_cache($menu_items) {
184 global $pagenow;
185 if (!$this->can_purge_cache()) return $menu_items;
186
187 $act_url = remove_query_arg(array('wpo_single_page_cache_purged', 'wpo_all_pages_cache_purged'));
188
189 $cache_size = $this->get_cache_size();
190 $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
191 $cache_size_info .= '<span>'.__('Cache size:', 'wp-optimize').' '. WP_Optimize()->format_size($cache_size['size']).' '.sprintf(__('(%d files)', 'wp-optimize'), $cache_size['file_count']).'</span>';
192
193 $menu_items[] = array(
194 'id' => 'wpo_cache_stats',
195 'title' => $cache_size_info,
196 'meta' => array(
197 'class' => 'wpo-cache-stats',
198 ),
199 'parent' => 'wpo_purge_cache',
200 );
201
202 $menu_items[] = array(
203 'id' => 'wpo_purge_all_pages_cache',
204 'title' => __('Purge cache for all pages', 'wp-optimize'),
205 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_all_pages_cache'), $act_url),
206 'meta' => array(
207 'title' => __('Purge cache for all pages', 'wp-optimize'),
208 ),
209 'parent' => 'wpo_purge_cache',
210 );
211
212 if (!is_admin() || 'post.php' == $pagenow) {
213 $menu_items[] = array(
214 'id' => 'wpo_purge_this_page_cache',
215 'title' => __('Purge cache for this page', 'wp-optimize'),
216 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
217 'meta' => array(
218 'title' => __('Purge cache for this page', 'wp-optimize'),
219 ),
220 'parent' => 'wpo_purge_cache',
221 );
222 }
223
224 return $menu_items;
225
226 }
227
228 /**
229 * Check if purge single page action sent and purge cache.
230 */
231 public function handle_purge_single_page_cache() {
232
233 if (!$this->can_purge_cache()) return;
234
235 if (isset($_GET['wpo_single_page_cache_purged']) || isset($_GET['wpo_all_pages_cache_purged'])) {
236 if (isset($_GET['wpo_single_page_cache_purged'])) {
237 $notice_function = $_GET['wpo_single_page_cache_purged'] ? 'notice_purge_single_page_cache_success' : 'notice_purge_single_page_cache_error';
238 } else {
239 $notice_function = $_GET['wpo_all_pages_cache_purged'] ? 'notice_purge_all_pages_cache_success' : 'notice_purge_all_pages_cache_error';
240 }
241
242 add_action('admin_notices', array($this, $notice_function));
243
244 return;
245 }
246
247 if (!isset($_GET['_wpo_purge'])) return;
248
249 if (wp_verify_nonce($_GET['_wpo_purge'], 'wpo_purge_single_page_cache')) {
250 $success = false;
251
252 if (is_admin()) {
253 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
254 if ($post > 0) {
255 $success = self::delete_single_post_cache($post);
256 }
257 } else {
258 $success = self::delete_cache_by_url(wpo_current_url());
259 }
260
261 // remove nonce from url and reload page.
262 wp_redirect(add_query_arg('wpo_single_page_cache_purged', $success, remove_query_arg('_wpo_purge')));
263 exit;
264
265 } elseif (wp_verify_nonce($_GET['_wpo_purge'], 'wpo_purge_all_pages_cache')) {
266 $success = self::purge();
267 $this->maybe_set_preload_cron_job();
268
269 // remove nonce from url and reload page.
270 wp_redirect(add_query_arg('wpo_all_pages_cache_purged', $success, remove_query_arg('_wpo_purge')));
271 exit;
272 }
273 }
274
275 /**
276 * Sets a preload cron job to run after 30 seconds
277 *
278 * As preloading may take time for larger sites, we are opting for an immediate cron job instead
279 * of instant preloading to prevent the current request from slowing down the page load
280 *
281 * @return void
282 */
283 private function maybe_set_preload_cron_job() {
284 $wpo_page_cache_preloader = WP_Optimize_Page_Cache_Preloader::instance();
285
286 if ($wpo_page_cache_preloader->is_scheduled_preload_enabled() || $this->should_auto_preload_purged_contents()) {
287 if (!wp_next_scheduled('wpo_page_cache_run_preload')) {
288 wp_schedule_single_event(time() + 30, 'wpo_page_cache_run_preload');
289 }
290 }
291 }
292
293 /**
294 * Show notification when page cache purged successfully.
295 */
296 public function notice_purge_single_page_cache_success() {
297 $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
298 }
299
300 /**
301 * Show notification when page cache wasn't purged.
302 */
303 public function notice_purge_single_page_cache_error() {
304 $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
305 }
306
307 /**
308 * Show notification when all pages cache purged successfully.
309 */
310 public function notice_purge_all_pages_cache_success() {
311 $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
312 }
313
314 /**
315 * Show notification when all pages cache wasn't purged.
316 */
317 public function notice_purge_all_pages_cache_error() {
318 $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
319 }
320
321 /**
322 * Show notification in WordPress admin.
323 *
324 * @param string $message HTML (no further escaping is performed)
325 * @param string $type error, warning, success, or info
326 */
327 public function show_notice($message, $type) {
328 global $current_screen;
329
330 if ($current_screen && is_callable(array($current_screen, 'is_block_editor')) && $current_screen->is_block_editor()) : ?>
331 <script>
332 window.addEventListener('load', function() {
333 (function(wp) {
334 if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
335 wp.data.dispatch('core/notices').createNotice(
336 '<?php echo $type; ?>',
337 '<?php echo $message; ?>',
338 {
339 isDismissible: true,
340 }
341 );
342 }
343 })(window.wp);
344 });
345 </script>
346 <?php else : ?>
347 <div class="notice wpo-notice notice-<?php echo $type; ?> is-dismissible">
348 <p><?php echo $message; ?></p>
349 </div>
350 <?php
351 endif;
352 }
353
354 /**
355 * Enables page cache
356 *
357 * @param bool $force_enable - Force regenerating everything. E.g. we want to do that when saving the settings
358 *
359 * @return WP_Error|bool - true on success, error otherwise
360 */
361 public function enable($force_enable = false) {
362 static $already_ran_enable = false;
363
364 if ($already_ran_enable) return $already_ran_enable;
365
366 $folders_created = $this->create_folders();
367 if (is_wp_error($folders_created)) {
368 $already_ran_enable = $folders_created;
369 return $already_ran_enable;
370 }
371
372 if (!file_exists($this->config->get_config_file_path())) {
373 $result = $this->update_cache_config();
374 if (is_wp_error($result)) return $result;
375 }
376
377 // if WPO_ADVANCED_CACHE isn't set, or environment doesn't contain the right constant, force regeneration
378 if (!defined('WPO_ADVANCED_CACHE') || !defined('WP_CACHE')) {
379 $force_enable = true;
380 }
381
382 $this->maybe_regenerate_cache_config_file();
383
384 if (!$force_enable) {
385 $already_ran_enable = true;
386 return true;
387 }
388
389 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
390 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
391 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
392
393 if (!defined('WP_CLI') || !WP_CLI) {
394 $message .= "\n\n1. ".sprintf(__('Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
395 $message .= "\n2. ".__('Edit or create a file with the name advanced-cache.php', 'wp-optimize');
396 $message .= "\n3. ".__('Copy and paste the following lines into the file:', 'wp-optimize');
397 }
398
399 $already_ran_enable = new WP_Error("write_advanced_cache", $message);
400 return $already_ran_enable;
401 }
402
403 if (!$this->write_wp_config(true)) {
404 $already_ran_enable = new WP_Error("write_wp_config", "Could not turn on the WP_CACHE constant in wp-config.php. Check your permissions.");
405 return $already_ran_enable;
406 }
407
408 if (!$this->verify_cache()) {
409 $errors = $this->get_errors();
410 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
411 return $already_ran_enable;
412 }
413
414 $already_ran_enable = true;
415
416 return true;
417 }
418
419 /**
420 * Disables page cache
421 *
422 * @return bool - true on success, false otherwise
423 */
424 public function disable() {
425 $ret = true;
426
427 $advanced_cache_file = $this->get_advanced_cache_filename();
428
429 // N.B. The only use of WP_CACHE in WP core is to include('advanced-cache.php') (and run a function if it's then defined); so, if the decision to leave it enable is, for some unexpected reason, technically incorrect, it still can't cause a problem.
430 $disabled_wp_config = $this->write_wp_config(false);
431 if (!$disabled_wp_config) {
432 $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
433 $action = "deactivate_".$plugin_basename."/wp-optimize.php";
434 if (current_action() === $action) {
435 $cache_config = WPO_Cache_Config::instance()->config;
436 $cache_config['enable_page_caching'] = false;
437 WPO_Cache_Config::instance()->update($cache_config, true);
438 }
439 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
440 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
441 }
442
443 $disabled_advanced_cache = true;
444 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
445 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
446 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
447 if (file_exists($advanced_cache_file) && (!unlink($advanced_cache_file) && false === file_put_contents($advanced_cache_file, "<?php\n// WP-Optimize: page cache disabled"))) {
448 $disabled_advanced_cache = false;
449 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
450 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
451 }
452 }
453
454 // If both actions failed, the cache wasn't disabled. So we send an error. If only one succeeds, it will still be disabled.
455 if (!$disabled_wp_config && !$disabled_advanced_cache) {
456 $ret = new WP_Error('error_disabling_cache', __('The page caching could not be disabled: the WP_CACHE constant could not be removed from wp-config.php and the request to the filesystem to remove or empty advanced-cache.php failed.', 'wp-optimize'));
457 }
458
459 // Delete cache to avoid stale cache on next activation
460 $this->purge();
461
462 // Unschedule cron job to purge old cache
463 wp_clear_scheduled_hook('wpo_purge_old_cache');
464
465 return $ret;
466 }
467
468
469 /**
470 * Purges the cache
471 *
472 * @return bool - true on success, false otherwise
473 */
474 public function purge() {
475 /**
476 * Filters whether activating / deactivating a plugin will purge the cache.
477 */
478 $this->should_purge = apply_filters('wpo_purge_page_cache_on_activate_deactivate_plugin', true);
479 $purge_actions = array(
480 'deactivate_' . plugin_basename(WPO_PLUGIN_MAIN_PATH . 'wp-optimize.php'),
481 'deactivate_plugin',
482 'activated_plugin',
483 );
484 $is_deactivation_action = in_array(current_action(), $purge_actions, true);
485 if ($is_deactivation_action && !$this->should_purge) return false;
486
487 if (!self::delete(WPO_CACHE_FILES_DIR)) {
488 $this->log("The request to the filesystem to delete the cache failed");
489 return false;
490 }
491
492 /**
493 * Fires after purging the cache
494 */
495 do_action('wpo_cache_flush');
496
497 return true;
498 }
499
500 /**
501 * Purge cache files older than cache life span
502 *
503 * @return array Log messages.
504 */
505 public function purge_old() {
506 $log = array();
507 $page_cache_length = $this->config->get_option('page_cache_length');
508 if (0 === $page_cache_length) return $log;
509 $expires = time() - $page_cache_length;
510 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
511 // get all directories that are a direct child of current directory
512 if (is_dir($cache_folder) && is_writable($cache_folder)) {
513 if ($handle = opendir($cache_folder)) {
514 while (false !== ($d = readdir($handle))) {
515 if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
516 continue;
517 }
518
519 if ($this->is_front_page_cache($d)) {
520 $modified_time = (int) filemtime("$cache_folder/$d");
521 if ($modified_time <= $expires) {
522 unlink("$cache_folder/$d");
523 }
524 continue;
525 }
526
527 $dir = $cache_folder.'/'.$d;
528 if (!is_dir($dir)) continue;
529
530 $stat = stat($dir);
531 $modified_time = $stat['mtime'];
532 $log[] = "checking if cache has expired - $d";
533 if ($modified_time <= $expires) {
534 $log[] = "deleting cache in $dir";
535 wpo_delete_files($dir, true);
536 if (file_exists($dir)) rmdir($dir);
537 }
538 }
539 closedir($handle);
540 }
541 }
542 return $log;
543 }
544
545 /**
546 * Finds out given cache file is of front page or not
547 *
548 * @return bool
549 */
550 private function is_front_page_cache($d) {
551 return in_array($d, array('index.html', 'index.html.gz'));
552 }
553
554 /**
555 * Purges the cache
556 *
557 * @return bool - true on success, false otherwise
558 */
559 public function clean_up() {
560
561 $this->disable();
562
563 if (!self::delete(WPO_CACHE_DIR, true)) {
564 $this->log("The request to the filesystem to clean up the cache failed");
565 return false;
566 }
567
568 return true;
569 }
570
571 /**
572 * Check if cache is enabled and working
573 *
574 * @return bool - true on success, false otherwise
575 */
576 public function is_enabled() {
577
578 if (!$this->config->get_option('enable_page_caching')) return false;
579
580 if (!defined('WP_CACHE') || !WP_CACHE) {
581 if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
582 $this->log("WP_CACHE constant is not present in wp-config.php");
583 return false;
584 }
585 }
586
587 if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
588 if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
589 $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
590 return false;
591 }
592 }
593
594 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
595 if (!file_exists($config_file)) {
596 if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
597 $this->log("Cache config file $config_file is not present");
598 return false;
599 }
600 }
601
602 return true;
603 }
604
605 /**
606 * Create the folder structure needed for cache to work
607 *
608 * @return bool - true on success, false otherwise
609 */
610 public function create_folders() {
611 $permissions_str = __('Please check your file permissions.', 'wp-optimize');
612
613 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
614 return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s.', 'wp-optimize'), str_ireplace(ABSPATH, '', WPO_CACHE_DIR)) .' '. $permissions_str);
615 }
616
617 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
618 return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s.', 'wp-optimize'), str_ireplace(ABSPATH, '', WPO_CACHE_CONFIG_DIR)) .' '. $permissions_str);
619 }
620
621 if (!is_dir(WPO_CACHE_FILES_DIR)) {
622 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
623 return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s.', 'wp-optimize'), str_ireplace(ABSPATH, '', WPO_CACHE_FILES_DIR)) .' '. $permissions_str);
624 } else {
625 wpo_disable_cache_directories_viewing();
626 }
627 }
628
629 return true;
630 }
631
632 /**
633 * Get advanced-cache.php file name with full path.
634 *
635 * @return string
636 */
637 public function get_advanced_cache_filename() {
638 return untrailingslashit(WP_CONTENT_DIR) . '/advanced-cache.php';
639 }
640
641 /**
642 * Writes advanced-cache.php
643 *
644 * @param boolean $update_required - Whether the update is required or not.
645 * @return bool
646 */
647 private function write_advanced_cache($update_required = false) {
648 $config_file_basename = $this->config->get_cache_config_filename();
649 $cache_file_basename = untrailingslashit(plugin_dir_path(__FILE__));
650 $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
651 $cache_path = '/wpo-cache';
652 $cache_files_path = '/cache/wpo-cache';
653 $cache_extensions_path = WPO_CACHE_EXT_DIR;
654 $wpo_version = WPO_VERSION;
655 $wpo_home_url = trailingslashit(home_url());
656 $abspath = ABSPATH;
657
658 // CS does not like heredoc
659 // phpcs:disable
660 $this->advanced_cache_file_content = <<<EOF
661 <?php
662
663 if (!defined('ABSPATH')) die('No direct access allowed');
664
665 // WP-Optimize advanced-cache.php (written by version: $wpo_version) (homeurl: $wpo_home_url) (abspath: $abspath) (do not change this line, it is used for correctness checks)
666
667 if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);
668
669 \$possible_plugin_locations = array(
670 defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR.'/$plugin_basename/cache' : false,
671 defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR.'/plugins/$plugin_basename/cache' : false,
672 dirname(__FILE__).'/plugins/$plugin_basename/cache',
673 '$cache_file_basename',
674 );
675
676 \$plugin_location = false;
677
678 foreach (\$possible_plugin_locations as \$possible_location) {
679 if (false !== \$possible_location && @file_exists(\$possible_location.'/file-based-page-cache.php')) {
680 \$plugin_location = \$possible_location;
681 break;
682 }
683 }
684
685 if (false === \$plugin_location) {
686 if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND')) define('WPO_PLUGIN_LOCATION_NOT_FOUND', true);
687 \$protocol = \$_SERVER['REQUEST_SCHEME'];
688 \$host = \$_SERVER['HTTP_HOST'];
689 \$request_uri = \$_SERVER['REQUEST_URI'];
690 if (strcasecmp('$wpo_home_url', \$protocol . '://' . \$host . \$request_uri) === 0) {
691 error_log('WP-Optimize: No caching took place, because the plugin location could not be found');
692 }
693 } else {
694 if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND')) define('WPO_PLUGIN_LOCATION_NOT_FOUND', false);
695 }
696
697 if (is_admin()) { return; }
698
699 if (!defined('WPO_CACHE_DIR')) define('WPO_CACHE_DIR', WP_CONTENT_DIR.'$cache_path');
700 if (!defined('WPO_CACHE_CONFIG_DIR')) define('WPO_CACHE_CONFIG_DIR', WPO_CACHE_DIR.'/config');
701 if (!defined('WPO_CACHE_FILES_DIR')) define('WPO_CACHE_FILES_DIR', WP_CONTENT_DIR.'$cache_files_path');
702 if (false !== \$plugin_location) {
703 if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', \$plugin_location.'/extensions');
704 } else {
705 if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', '$cache_extensions_path');
706 }
707
708 if (!@file_exists(WPO_CACHE_CONFIG_DIR . '/$config_file_basename')) { return; }
709
710 \$GLOBALS['wpo_cache_config'] = @json_decode(file_get_contents(WPO_CACHE_CONFIG_DIR . '/$config_file_basename'), true);
711
712 if (empty(\$GLOBALS['wpo_cache_config'])) {
713 include_once(WPO_CACHE_CONFIG_DIR . '/$config_file_basename');
714 }
715
716 if (empty(\$GLOBALS['wpo_cache_config'])) {
717 error_log('WP-Optimize: Caching failed because the configuration data could not be loaded from the config file.');
718 return;
719 }
720
721 if (empty(\$GLOBALS['wpo_cache_config']['enable_page_caching'])) { return; }
722
723 if (false !== \$plugin_location) { include_once(\$plugin_location.'/file-based-page-cache.php'); }
724
725 EOF;
726
727 // phpcs:enable
728 $advanced_cache_filename = $this->get_advanced_cache_filename();
729
730 // If the file content is already up to date, success
731 if (is_file($advanced_cache_filename) && file_get_contents($advanced_cache_filename) === $this->advanced_cache_file_content) {
732 $this->advanced_cache_file_writing_error = false;
733 return true;
734 }
735
736 // check if we can't write the advanced cache file
737 // case 1: the directory is read-only and the file doesn't exist
738 if (!is_file($advanced_cache_filename) && !is_writable(dirname($advanced_cache_filename))) {
739 $this->advanced_cache_file_writing_error = true;
740 return false;
741 }
742
743 // case 2: the file already exists but it's read-only
744 if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
745 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
746 $this->advanced_cache_file_writing_error = true;
747 return false;
748 } else {
749 $this->advanced_cache_file_writing_error = false;
750 return true;
751 }
752 }
753
754 if (!file_put_contents($this->get_advanced_cache_filename(), $this->advanced_cache_file_content)) {
755 $this->advanced_cache_file_writing_error = true;
756 return false;
757 }
758
759 $this->advanced_cache_file_writing_error = false;
760 return true;
761 }
762
763 /**
764 * Update advanced cache version if needed.
765 */
766 public function maybe_update_advanced_cache() {
767
768 if (!$this->is_enabled()) return;
769
770 if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND') || (defined('WPO_PLUGIN_LOCATION_NOT_FOUND') && true === WPO_PLUGIN_LOCATION_NOT_FOUND)) {
771 if (!$this->write_advanced_cache(true)) {
772 add_action('admin_notices', array($this, 'show_admin_notice_advanced_cache'));
773 }
774 }
775
776 // from 3.0.17 we use more secure way to store cache config files and need update advanced-cache.php
777 // if the site url or folder changed we also update advanced-cache.php
778 $advanced_cache_current_version = $this->get_advanced_cache_version();
779 $is_the_site_url_or_folder_changed = $this->is_the_site_url_or_folder_changed();
780
781 if ($advanced_cache_current_version && version_compare($advanced_cache_current_version, $this->_minimum_advanced_cache_file_version, '>=') && !$is_the_site_url_or_folder_changed) return;
782
783 // Purge the cache when the site url or folder changed.
784 if ($is_the_site_url_or_folder_changed) {
785 $this->purge();
786 }
787
788 if (!$this->write_advanced_cache()) {
789 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
790 } else {
791 $this->update_cache_config();
792 }
793 }
794
795 /**
796 * Check if the site url or folder doesn't equal to values from advanced-cache.php
797 *
798 * @return bool
799 */
800 private function is_the_site_url_or_folder_changed() {
801 if (!is_file($this->get_advanced_cache_filename())) return false;
802
803 $content = file_get_contents($this->get_advanced_cache_filename());
804
805 if (false === $content) return false;
806
807 if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\) (\(homeurl: (.+)\)) (\(abspath: (.+)\)) \(do not change/Ui', $content, $match)) {
808 $wpo_home_url = trailingslashit(home_url());
809 $abspath = ABSPATH;
810 return ($wpo_home_url != $match[3] || $abspath != $match[5]);
811 } elseif (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
812 return true;
813 }
814
815 return false;
816 }
817
818 /**
819 * Show notification when advanced-cache.php could not be updated.
820 */
821 public function notice_advanced_cache_autoupdate_error() {
822 $this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
823 ' <a href="'.admin_url('admin.php?page=wpo_cache').'">'.__('Please try to disable and then re-enable the WP-Optimize cache manually.', 'wp-optimize').'</a>', 'error');
824 }
825
826 /**
827 * Get WPO version number from advanced-cache.php file.
828 *
829 * @return bool|mixed
830 */
831 public function get_advanced_cache_version() {
832 if (!is_file($this->get_advanced_cache_filename())) return false;
833
834 $version = false;
835 $content = file_get_contents($this->get_advanced_cache_filename());
836
837 if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
838 $version = $match[1];
839 }
840
841 return $version;
842 }
843
844 /**
845 * Set WP_CACHE on or off in wp-config.php
846 *
847 * @param boolean $status value of WP_CACHE.
848 * @return boolean true if the value was set, false otherwise
849 */
850 private function write_wp_config($status = true) {
851 // If we changed the value in wp-config, save it, in case we need to change it again in the same run.
852 static $changed = false;
853
854 if (defined('WP_CACHE') && WP_CACHE === $status && !$changed) {
855 return true;
856 }
857
858 $config_path = $this->_get_wp_config();
859
860 // Couldn't find wp-config.php.
861 if (!$config_path) {
862 return false;
863 }
864
865 $config_file_string = file_get_contents($config_path);
866
867 // Config file is empty. Maybe couldn't read it?
868 if (empty($config_file_string)) {
869 return false;
870 }
871
872 $config_file = preg_split("#(\n|\r\n)#", $config_file_string);
873 $line_key = false;
874
875 foreach ($config_file as $key => $line) {
876 if (!preg_match('/^\s*define\(\s*(\'|")([A-Z_]+)(\'|")(.*)/i', $line, $match)) {
877 continue;
878 }
879
880 if ('WP_CACHE' === $match[2]) {
881 $line_key = $key;
882 }
883 }
884
885 if (false !== $line_key) {
886 unset($config_file[$line_key]);
887 }
888
889
890 if ($status) {
891 array_shift($config_file);
892 array_unshift($config_file, '<?php', "define('WP_CACHE', true); // WP-Optimize Cache");
893 }
894
895 foreach ($config_file as $key => $line) {
896 if ('' === $line) {
897 unset($config_file[$key]);
898 }
899 }
900 if (!file_put_contents($config_path, implode(PHP_EOL, $config_file))) {
901 return false;
902 }
903 $changed = true;
904 return true;
905 }
906
907 /**
908 * Verify we can write to the file system
909 *
910 * @return boolean
911 */
912 private function verify_cache() {
913 if (function_exists('clearstatcache')) {
914 clearstatcache();
915 }
916 $errors = 0;
917
918 // First check wp-config.php.
919 if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
920 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
921 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
922 }
923
924 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
925
926 // Now check wp-content. We need to be able to create files of the same user as this file.
927 if ((!file_exists($advanced_cache_file) || false === strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) && !is_writable($advanced_cache_file) && !is_writable(untrailingslashit(WP_CONTENT_DIR))) {
928 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
929 $this->add_error('verify_cache', __("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions", 'wp-optimize'));
930 $errors++;
931 }
932
933 if (file_exists(WPO_CACHE_FILES_DIR)) {
934 if (!is_writable(WPO_CACHE_FILES_DIR)) {
935 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
936 $this->add_warning('verify_cache', sprintf(__("Unable to write inside the cache files folder (%s); please check file/folder permissions (no cache files will be able to be created otherwise)", 'wp-optimize'), WPO_CACHE_FILES_DIR));
937 }
938 }
939
940 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
941 if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
942 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
943 // If the config exists, only send a warning. Otherwise send an error.
944 $type = 'warning';
945 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
946 $type = 'error';
947 $errors++;
948 }
949 $this->add_error('verify_cache', sprintf(__("Unable to write inside the cache configuration folder (%s); please check file/folder permissions", 'wp-optimize'), WPO_CACHE_CONFIG_DIR), $type);
950 }
951 }
952
953 return !$errors;
954 }
955
956 /**
957 * Update permalink structure in cache config
958 *
959 * @param string $old_value Old value of permalink_structure option
960 * @param string $value New value of permalink_structure option
961 * @param string $option Option name `permalink_structure`
962 */
963 public function update_option_permalink_structure($old_value, $value, $option) {
964 $current_config = $this->config->get();
965 if ($old_value != $value) {
966 $current_config[$option] = $value;
967 $this->config->update($current_config, true);
968 }
969 }
970
971 /**
972 * Update cache config. Used to support 3d party plugins.
973 *
974 * @return {boolean|WP_Error}
975 */
976 public function update_cache_config() {
977 // get current cache settings.
978 $current_config = $this->config->get();
979 // and call update to change if need cookies and query variable names.
980 return $this->config->update($current_config);
981 }
982
983 /**
984 * Delete information about cache size.
985 */
986 public function delete_cache_size_information() {
987 delete_transient('wpo_get_cache_size');
988 }
989
990 /**
991 * Get current cache size.
992 *
993 * @return array
994 */
995 public function get_cache_size() {
996 $cache_size = get_transient('wpo_get_cache_size');
997
998 if (!empty($cache_size)) return $cache_size;
999
1000 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
1001 $cache_size = array(
1002 'size' => $infos['size'],
1003 'file_count' => $infos['file_count']
1004 );
1005
1006 set_transient('wpo_get_cache_size', $cache_size);
1007
1008 return $cache_size;
1009 }
1010
1011 /**
1012 * Fetch directory information.
1013 *
1014 * @param string $dir
1015 * @return array
1016 */
1017 private function get_dir_infos($dir) {
1018 $dir_size = 0;
1019 $file_count = 0;
1020
1021 $handle = is_dir($dir) ? opendir($dir) : false;
1022
1023 if (false === $handle) {
1024 return array('size' => 0, 'file_count' => 0);
1025 }
1026
1027 $file = readdir($handle);
1028
1029 while (false !== $file) {
1030
1031 if ('.' != $file && '..' != $file) {
1032 $current_file = $dir.'/'.$file;
1033
1034 if (is_dir($current_file)) {
1035 $sub_dir_infos = $this->get_dir_infos($current_file);
1036 $dir_size += $sub_dir_infos['size'];
1037 $file_count += $sub_dir_infos['file_count'];
1038 } elseif (is_file($current_file)) {
1039 $dir_size += filesize($current_file);
1040 $file_count++;
1041 }
1042 }
1043
1044 $file = readdir($handle);
1045
1046 }
1047
1048 return array('size' => $dir_size, 'file_count' => $file_count);
1049 }
1050
1051 /**
1052 * Returns the path to wp-config
1053 *
1054 * @return string|boolean wp-config.php path.
1055 */
1056 private function _get_wp_config() {
1057
1058 $config_path = false;
1059
1060 foreach (get_included_files() as $filename) {
1061 if (preg_match('/(\\\\|\/)wp-config\.php$/i', $filename)) {
1062 $config_path = $filename;
1063 break;
1064 }
1065 }
1066
1067 // WP-CLI doesn't include wp-config.php that's why we use function from WP-CLI to locate config file.
1068 if (!$config_path && is_callable('wpo_wp_cli_locate_wp_config')) {
1069 $config_path = wpo_wp_cli_locate_wp_config();
1070 }
1071
1072 return $config_path;
1073 }
1074
1075 /**
1076 * Util to delete folders and/or files
1077 *
1078 * @param string $src
1079 * @return boolean
1080 */
1081 public static function delete($src) {
1082 return wpo_delete_files($src);
1083 }
1084
1085 /**
1086 * Delete cached files for specific url.
1087 *
1088 * @param string $url
1089 * @param bool $recursive If true child elements will deleted too
1090 *
1091 * @return bool
1092 */
1093 public static function delete_cache_by_url($url, $recursive = false) {
1094 if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1095
1096 $path = self::get_full_path_from_url($url);
1097
1098 do_action('wpo_delete_cache_by_url', $url, $recursive);
1099
1100 do_action('wpo_preload_url', $url);
1101
1102 return wpo_delete_files($path, $recursive);
1103 }
1104
1105 /**
1106 * Adds the URL to the preload list and hooks the preload task to shutdown
1107 *
1108 * @param string $url
1109 * @return void
1110 */
1111 public function maybe_preload_url($url) {
1112 if (!$this->should_auto_preload_purged_contents()) return;
1113
1114 static $urls_preloaded = array();
1115
1116 // Remove the query string from the URL
1117 $url = preg_replace('/\?.*/', '', $url);
1118
1119 if (!in_array($url, $urls_preloaded)) {
1120 $urls_preloaded[] = $url;
1121
1122 $preloader = WP_Optimize_Page_Cache_Preloader::instance();
1123 $preloader->add_url_to_preload_list($url);
1124 $url_task_creator = array($preloader, 'create_tasks_for_auto_preload_urls');
1125
1126 if (!has_action('shutdown', $url_task_creator)) {
1127 add_action('shutdown', $url_task_creator);
1128 }
1129 }
1130 }
1131
1132 /**
1133 * Delete cached files for single post.
1134 *
1135 * @param integer $post_id The post ID
1136 *
1137 * @return bool
1138 */
1139 public static function delete_single_post_cache($post_id) {
1140 $is_cache_deleted = self::really_delete_single_post_cache($post_id);
1141
1142 do_action('wpo_single_post_cache_deleted', $post_id);
1143
1144 return $is_cache_deleted;
1145 }
1146
1147 /**
1148 * Really delete cached files for single post.
1149 *
1150 * @param integer $post_id The post ID
1151 *
1152 * @return bool
1153 */
1154 public static function really_delete_single_post_cache($post_id) {
1155
1156 if (!defined('WPO_CACHE_FILES_DIR')) return;
1157
1158 $post_url = get_permalink($post_id);
1159
1160 $path = self::get_full_path_from_url($post_url);
1161
1162 // for posts with pagination run purging cache recursively.
1163 $post = get_post($post_id);
1164 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1165
1166 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1167
1168 do_action('wpo_preload_url', $post_url);
1169
1170 return wpo_delete_files($path, $recursive);
1171 }
1172
1173 /**
1174 * Delete cached home page files.
1175 */
1176 public static function delete_homepage_cache() {
1177
1178 if (!defined('WPO_CACHE_FILES_DIR')) return;
1179
1180 $homepage_url = get_home_url(get_current_blog_id());
1181
1182 $path = self::get_full_path_from_url($homepage_url);
1183
1184 $recursive = false;
1185
1186 do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);
1187
1188 do_action('wpo_preload_url', $homepage_url);
1189
1190 wpo_delete_files($path, $recursive);
1191 }
1192
1193 /**
1194 * Delete feed from cache.
1195 */
1196 public static function delete_feed_cache() {
1197 if (!defined('WPO_CACHE_FILES_DIR')) return;
1198
1199 $homepage_url = get_home_url(get_current_blog_id());
1200
1201 $path = self::get_full_path_from_url($homepage_url) . 'feed/';
1202
1203 do_action('wpo_delete_cache_by_url', $path, true);
1204
1205 wpo_delete_files($path, true);
1206 }
1207
1208 /**
1209 * Delete post feed from cache.
1210 */
1211 public static function delete_post_feed_cache($post_id) {
1212 self::really_delete_post_feed_cache($post_id);
1213
1214 do_action('wpo_single_post_feed_cache_deleted', $post_id);
1215 }
1216
1217 /**
1218 * Really delete post feed from cache.
1219 */
1220 public static function really_delete_post_feed_cache($post_id) {
1221 if (!defined('WPO_CACHE_FILES_DIR')) return;
1222
1223 $post_url = get_permalink($post_id);
1224
1225 $path = self::get_full_path_from_url($post_url) . 'feed/';
1226
1227 do_action('wpo_delete_cache_by_url', $path, true);
1228
1229 wpo_delete_files($path, true);
1230 }
1231
1232 /**
1233 * Delete comments feed from cache.
1234 */
1235 public static function delete_comments_feed() {
1236 if (!defined('WPO_CACHE_FILES_DIR')) return;
1237
1238 $comments_feed_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/feed/';
1239
1240 $path = self::get_full_path_from_url($comments_feed_url);
1241
1242 do_action('wpo_delete_cache_by_url', $comments_feed_url, true);
1243
1244 wpo_delete_files($path, true);
1245
1246 // delete empty comments dir from the cache
1247 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1248 $path = self::get_full_path_from_url($comments_url);
1249
1250 if (wpo_is_empty_dir($path)) {
1251 wpo_delete_files($path, true);
1252 }
1253 }
1254
1255 /**
1256 * Returns full path to the cache folder by url.
1257 *
1258 * @param string $url
1259 * @return string
1260 */
1261 public static function get_full_path_from_url($url) {
1262 return trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($url));
1263 }
1264
1265 /**
1266 * Admin actions
1267 *
1268 * @return void
1269 */
1270 public function admin_init() {
1271 // Maybe update the advanced cache.
1272 if ((!defined('DOING_AJAX') || !DOING_AJAX) && current_user_can('update_plugins')) {
1273 $this->maybe_update_advanced_cache();
1274 $this->cron_activate();
1275 }
1276 }
1277
1278 /**
1279 * Logs error messages
1280 *
1281 * @param string $message
1282 * @return null|void
1283 */
1284 public function log($message) {
1285 if (isset($this->logger)) {
1286 $this->logger->log($message, 'error');
1287 }
1288 }
1289
1290 /**
1291 * Returns an instance of the current class, creates one if it doesn't exist
1292 *
1293 * @return object
1294 */
1295 public static function instance() {
1296 if (empty(self::$instance)) {
1297 self::$instance = new self();
1298 }
1299 return self::$instance;
1300 }
1301
1302
1303 /**
1304 * Update gmt_offset and timezone_string cache config value, used with hook `update_gmt_offset_timezone_string_config`.
1305 *
1306 * @param {float|string} $old_value GMT offset as a float value or timezone value supported by PHP as a string (https://www.php.net/manual/en/timezones.php)
1307 * @param {float|string} $new_value
1308 * @param {string} $option gmt_offset|timezone_string
1309 *
1310 * @return null
1311 */
1312 public function update_gmt_offset_timezone_string_config($old_value, $new_value, $option) {
1313 if ('' == $new_value) return;
1314
1315 $current_config = $this->config->get();
1316 if ('timezone_string' == $option) {
1317 $timeZone = new DateTimeZone($new_value);
1318 $dateTime = new DateTime("now");
1319 $gmt_offset = $timeZone->getOffset($dateTime);
1320 $current_config['gmt_offset'] = round($gmt_offset/3600, 1);
1321 $current_config['timezone_string'] = $new_value;
1322 } elseif ('' !== $new_value) {
1323 $current_config['gmt_offset'] = $new_value;
1324 $current_config['timezone_string'] = '';
1325 }
1326 $this->config->update($current_config, true);
1327 }
1328
1329 /**
1330 * Update `date_format` cache config value, used with hook `update_option_date_format`.
1331 *
1332 * @param string $old_value Old date format
1333 * @param string $new_value New date format
1334 *
1335 * @return void
1336 */
1337 public function update_option_date_format($old_value, $new_value) {
1338 $current_config = $this->config->get();
1339 $current_config['date_format'] = $new_value;
1340 $this->config->update($current_config, true);
1341 }
1342
1343 /**
1344 * Update `time_format` cache config value, used with hook `update_option_time_format`.
1345 *
1346 * @param string $old_value Old time format
1347 * @param string $new_value New time format
1348 *
1349 * @return void
1350 */
1351 public function update_option_time_format($old_value, $new_value) {
1352 $current_config = $this->config->get();
1353 $current_config['time_format'] = $new_value;
1354 $this->config->update($current_config, true);
1355 }
1356
1357 /**
1358 * Adds an error to the error store
1359 *
1360 * @param string $code - The error code
1361 * @param string $message - The error's message
1362 * @param string $type - The error's type (error, warning)
1363 * @return void
1364 */
1365 public function add_error($code, $message, $type = 'error') {
1366 if (!isset($this->_errors[$type])) {
1367 $this->_errors[$type] = new WP_Error($code, $message);
1368 } else {
1369 $this->_errors[$type]->add($code, $message);
1370 }
1371 }
1372
1373 /**
1374 * Adds a warning to the error store
1375 *
1376 * @param string $code - The error code
1377 * @param string $message - The error's message
1378 * @return void
1379 */
1380 public function add_warning($code, $message) {
1381 $this->add_error($code, $message, 'warning');
1382 }
1383
1384 /**
1385 * Get all recorded errors
1386 *
1387 * @param string $type - The error type
1388 * @param boolean $get_messages_only - Whether to get only the messages, or the full WP_Error object
1389 * @return boolean|array|WP_Error
1390 */
1391 public function get_errors($type = 'error', $get_messages_only = true) {
1392 if (!$this->has_errors($type)) return false;
1393 $errors = $this->_errors[$type];
1394 if ($get_messages_only) {
1395 return $errors->get_error_messages();
1396 }
1397 return $errors;
1398 }
1399
1400 /**
1401 * Check if any errors were recorded
1402 *
1403 * @param string $type - The error type
1404 * @return boolean
1405 */
1406 public function has_errors($type = 'error') {
1407 return isset($this->_errors[$type]) && !empty($this->_errors[$type]) && $this->_errors[$type]->has_errors();
1408 }
1409
1410 /**
1411 * Check if any warnings were recorded
1412 *
1413 * @return boolean
1414 */
1415 public function has_warnings() {
1416 return $this->has_errors('warning');
1417 }
1418
1419 /**
1420 * Check the cache compatibility issues.
1421 */
1422 public function check_compatibility_issues() {
1423 if (!$this->is_enabled()) return;
1424
1425 if ($this->is_pagespeedninja_gzip_active()) add_action('admin_notices', array($this, 'show_pagespeedninja_gzip_notice'));
1426 if ($this->is_farfutureexpiration_gzip_active()) add_action('admin_notices', array($this, 'show_farfutureexpiration_gzip_notice'));
1427 }
1428
1429 /**
1430 * Check if PageSpeed Ninja is active and GZIP compression option is enabled.
1431 *
1432 * @return bool
1433 */
1434 public function is_pagespeedninja_gzip_active() {
1435 if (!class_exists('PagespeedNinja')) return false;
1436
1437 $options = get_option('pagespeedninja_config');
1438 $gzip = !empty($options) ? (bool) $options['psi_EnableGzipCompression'] && (bool) $options['html_gzip'] : false;
1439
1440 return $gzip;
1441 }
1442
1443 /**
1444 * Output PageSpeed Ninja Gzip notice.
1445 */
1446 public function show_pagespeedninja_gzip_notice() {
1447 echo '<div id="wp-optimize-pagespeedninja-gzip-notice" class="error wpo-notice"><p><b>'.__('WP-Optimize:', 'wp-optimize').'</b> '.__('Please disable the feature "Gzip compression" in PageSpeed Ninja to prevent conflicts.', 'wp-optimize').'</p></div>';
1448 }
1449
1450 /**
1451 * Check if Far Future Expiration is active and GZIP compression option is enabled.
1452 *
1453 * @return bool
1454 */
1455 public function is_farfutureexpiration_gzip_active() {
1456 if (!class_exists('farFutureExpiration')) return false;
1457
1458 $options = get_option('far_future_expiration_settings');
1459 $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1460
1461 return $gzip;
1462 }
1463
1464 /**
1465 * Output Far Future Expiration Gzip notice.
1466 */
1467 public function show_farfutureexpiration_gzip_notice() {
1468 echo '<div id="wp-optimize-pagespeedninja-gzip-notice" class="error wpo-notice"><p><b>'.__('WP-Optimize:', 'wp-optimize').'</b> '.__('Please disable the feature "Gzip compression" in Far Future Expiration to prevent conflicts.', 'wp-optimize').'</p></div>';
1469 }
1470
1471 /**
1472 * This is a notice to show users that writing `advanced-cache.php` failed
1473 */
1474 public function show_admin_notice_advanced_cache() {
1475 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1476 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1477 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1478 }
1479
1480 /**
1481 * Scheduler public functions to update schedulers
1482 *
1483 * @param array $schedules An array of schedules being passed.
1484 * @return array An array of schedules being returned.
1485 */
1486 public function cron_schedules($schedules) {
1487 $page_cache_length = $this->config->get_option('page_cache_length');
1488 // When the interval is less than `WP_CRON_LOCK_TIMEOUT` error messages are triggered
1489 // So, instead of `0` (now) we are making it equal to the constant value.
1490 // https://github.com/WordPress/WordPress/commit/84d19848666a81584e0007a6ab7f7ad3c990d71a
1491 if (defined('WP_CRON_LOCK_TIMEOUT') && $page_cache_length < WP_CRON_LOCK_TIMEOUT) {
1492 $page_cache_length = WP_CRON_LOCK_TIMEOUT;
1493 }
1494
1495 $schedules['wpo_purge_old_cache'] = array('interval' => false === $page_cache_length ? 86400 : $page_cache_length, 'display' => __('Every time after the cache has expired', 'wp-optimize'));
1496
1497 $interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
1498 $schedules['wpo_page_cache_preload_continue_interval'] = array(
1499 'interval' => $interval,
1500 'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
1501 );
1502
1503 $schedules['wpo_use_cache_lifespan'] = array(
1504 'interval' => $page_cache_length,
1505 'display' => sprintf(__('Same as cache lifespan: %s', 'wp-optimize'), WPO_Cache_Config::instance()->get_option('page_cache_length_value').' '.WPO_Cache_Config::instance()->get_option('page_cache_length_unit'))
1506 );
1507
1508 return $schedules;
1509 }
1510
1511 /**
1512 * Update cache config file to reflect the webp images option
1513 */
1514 public function update_webp_images_option() {
1515 if ($this->is_enabled()) {
1516 $cache_settings = WPO_Cache_Config::instance()->get();
1517 $cache_settings['use_webp_images'] = WP_Optimize()->get_options()->get_option('webp_conversion');
1518 WPO_Cache_Config::instance()->update($cache_settings);
1519 }
1520 }
1521
1522 /**
1523 * May be regenerate cache config file, in case of migrations
1524 */
1525 public function maybe_regenerate_cache_config_file() {
1526 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1527 if (!file_exists($config_file)) {
1528 $config = $this->config->get();
1529 $this->config->write($config);
1530 }
1531 }
1532
1533 /**
1534 * Checks if the given cache directory is empty
1535 *
1536 * @param string $path The path to the cache directory.
1537 * @return bool Returns true if the cache directory is empty, false otherwise.
1538 */
1539 public static function is_cache_empty($path) {
1540 if (!is_dir($path)) return true;
1541
1542 if ($handle = opendir($path)) {
1543 while (false !== ($entry = readdir($handle))) {
1544 if ("." == $entry || ".." == $entry) {
1545 continue;
1546 }
1547
1548 $full_path = $path . '/' . $entry;
1549 if (is_file($full_path)) {
1550 closedir($handle);
1551 return false;
1552 }
1553 }
1554 closedir($handle);
1555 return true;
1556 }
1557 return true;
1558 }
1559
1560 /**
1561 * Check configuration for auto preloading after cache deletion
1562 *
1563 * @return bool
1564 */
1565 public function should_auto_preload_purged_contents() {
1566 $wpo_cache = WP_Optimize()->get_page_cache();
1567 $wpo_cache_options = $wpo_cache->config->get();
1568
1569 return $wpo_cache_options['auto_preload_purged_contents'];
1570 }
1571 }
1572
1573 endif;
1574