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

1,337 lines 40.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 specifc 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 /**
93 * Set everything up here
94 */
95 public function __construct() {
96 $this->config = WPO_Cache_Config::instance();
97 $this->rules = WPO_Cache_Rules::instance();
98 $this->logger = new Updraft_PHP_Logger();
99
100 add_action('activated_plugin', array($this, 'activate_deactivate_plugin'));
101 add_action('deactivate_plugin', array($this, 'activate_deactivate_plugin'));
102 add_action('wpo_purge_old_cache', array($this, 'purge_old'));
103
104 /**
105 * Regenerate config file on cache flush.
106 */
107 add_action('wpo_cache_flush', array($this, 'update_cache_config'));
108 add_action('wpo_cache_flush', array($this, 'delete_cache_size_information'));
109 add_action('update_option_permalink_structure', array($this, 'update_option_permalink_structure'), 10, 3);
110
111 // Add purge cache link to admin bar.
112 add_filter('wpo_cache_admin_bar_menu_items', array($this, 'admin_bar_purge_cache'), 20, 1);
113
114 // Handle single page purge.
115 add_action('wp_loaded', array($this, 'handle_purge_single_page_cache'));
116
117 add_action('admin_init', array($this, 'admin_init'));
118
119 $this->check_compatibility_issues();
120
121 add_filter('cron_schedules', array($this, 'cron_schedules'));
122 add_action('wpo_save_images_settings', array($this, 'update_webp_images_option'));
123 }
124
125 /**
126 * Activate cron job for deleting expired cache files
127 */
128 public function cron_activate() {
129 $page_cache_length = $this->config->get_option('page_cache_length');
130 if (!wp_next_scheduled('wpo_purge_old_cache')) {
131 wp_schedule_event(time() + (false === $page_cache_length ? '86400' : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
132 }
133 }
134
135 /**
136 * Do required actions on activate/deactivate any plugin.
137 */
138 public function activate_deactivate_plugin() {
139
140 wp_clear_scheduled_hook('wpo_purge_old_cache');
141
142 $this->update_cache_config();
143
144 /**
145 * Filters whether activating / deactivating a plugin will purge the cache.
146 */
147 if (apply_filters('wpo_purge_page_cache_on_activate_deactivate_plugin', true)) {
148 $this->purge();
149 }
150 }
151
152 /**
153 * Check if current user can purge cache.
154 *
155 * @return bool
156 */
157 public function can_purge_cache() {
158 if (is_multisite()) return $this->is_enabled() && (current_user_can('manage_network_options') || WP_Optimize()->can_purge_the_cache());
159 return $this->is_enabled() && (current_user_can('manage_options') || WP_Optimize()->can_purge_the_cache());
160 }
161
162 /**
163 * Add Purge from cache in admin bar.
164 *
165 * @param array $menu_items
166 * @return array
167 */
168 public function admin_bar_purge_cache($menu_items) {
169 global $pagenow;
170 if (!$this->can_purge_cache()) return $menu_items;
171
172 $act_url = remove_query_arg(array('wpo_single_page_cache_purged', 'wpo_all_pages_cache_purged'));
173
174 $cache_size = $this->get_cache_size();
175 $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
176 $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>';
177
178 $menu_items[] = array(
179 'id' => 'wpo_cache_stats',
180 'title' => $cache_size_info,
181 'meta' => array(
182 'class' => 'wpo-cache-stats',
183 ),
184 'parent' => 'wpo_purge_cache',
185 );
186
187 $menu_items[] = array(
188 'id' => 'wpo_purge_all_pages_cache',
189 'title' => __('Purge cache for all pages', 'wp-optimize'),
190 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_all_pages_cache'), $act_url),
191 'meta' => array(
192 'title' => __('Purge cache for all pages', 'wp-optimize'),
193 ),
194 'parent' => 'wpo_purge_cache',
195 );
196
197 if (!is_admin() || 'post.php' == $pagenow) {
198 $menu_items[] = array(
199 'id' => 'wpo_purge_this_page_cache',
200 'title' => __('Purge cache for this page', 'wp-optimize'),
201 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
202 'meta' => array(
203 'title' => __('Purge cache for this page', 'wp-optimize'),
204 ),
205 'parent' => 'wpo_purge_cache',
206 );
207 }
208
209 return $menu_items;
210
211 }
212
213 /**
214 * Check if purge single page action sent and purge cache.
215 */
216 public function handle_purge_single_page_cache() {
217
218 if (!$this->can_purge_cache()) return;
219
220 if (isset($_GET['wpo_single_page_cache_purged']) || isset($_GET['wpo_all_pages_cache_purged'])) {
221 if (isset($_GET['wpo_single_page_cache_purged'])) {
222 $notice_function = $_GET['wpo_single_page_cache_purged'] ? 'notice_purge_single_page_cache_success' : 'notice_purge_single_page_cache_error';
223 } else {
224 $notice_function = $_GET['wpo_all_pages_cache_purged'] ? 'notice_purge_all_pages_cache_success' : 'notice_purge_all_pages_cache_error';
225 }
226
227 add_action('admin_notices', array($this, $notice_function));
228
229 return;
230 }
231
232 if (!isset($_GET['_wpo_purge'])) return;
233
234 if (wp_verify_nonce($_GET['_wpo_purge'], 'wpo_purge_single_page_cache')) {
235 $success = false;
236
237 if (is_admin()) {
238 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
239 if ($post > 0) {
240 $success = self::delete_single_post_cache($post);
241 }
242 } else {
243 $success = self::delete_cache_by_url(wpo_current_url());
244 }
245
246 // remove nonce from url and reload page.
247 wp_redirect(add_query_arg('wpo_single_page_cache_purged', $success, remove_query_arg('_wpo_purge')));
248 exit;
249
250 } elseif (wp_verify_nonce($_GET['_wpo_purge'], 'wpo_purge_all_pages_cache')) {
251 $success = self::purge();
252
253 // remove nonce from url and reload page.
254 wp_redirect(add_query_arg('wpo_all_pages_cache_purged', $success, remove_query_arg('_wpo_purge')));
255 exit;
256 }
257 }
258
259 /**
260 * Show notification when page cache purged successfully.
261 */
262 public function notice_purge_single_page_cache_success() {
263 $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
264 }
265
266 /**
267 * Show notification when page cache wasn't purged.
268 */
269 public function notice_purge_single_page_cache_error() {
270 $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
271 }
272
273 /**
274 * Show notification when all pages cache purged successfully.
275 */
276 public function notice_purge_all_pages_cache_success() {
277 $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
278 }
279
280 /**
281 * Show notification when all pages cache wasn't purged.
282 */
283 public function notice_purge_all_pages_cache_error() {
284 $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
285 }
286
287 /**
288 * Show notification in WordPress admin.
289 *
290 * @param string $message HTML (no further escaping is performed)
291 * @param string $type error, warning, success, or info
292 */
293 public function show_notice($message, $type) {
294 global $current_screen;
295
296 if ($current_screen && is_callable(array($current_screen, 'is_block_editor')) && $current_screen->is_block_editor()) : ?>
297 <script>
298 window.addEventListener('load', function() {
299 (function(wp) {
300 if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
301 wp.data.dispatch('core/notices').createNotice(
302 '<?php echo $type; ?>',
303 '<?php echo $message; ?>',
304 {
305 isDismissible: true,
306 }
307 );
308 }
309 })(window.wp);
310 });
311 </script>
312 <?php else : ?>
313 <div class="notice wpo-notice notice-<?php echo $type; ?> is-dismissible">
314 <p><?php echo $message; ?></p>
315 </div>
316 <?php
317 endif;
318 }
319
320 /**
321 * Enables page cache
322 *
323 * @param bool $force_enable - Force regenerating everything. E.g. we want to do that when saving the settings
324 *
325 * @return WP_Error|bool - true on success, error otherwise
326 */
327 public function enable($force_enable = false) {
328 static $already_ran_enable = false;
329
330 if ($already_ran_enable) return $already_ran_enable;
331
332 $folders_created = $this->create_folders();
333 if (is_wp_error($folders_created)) {
334 $already_ran_enable = $folders_created;
335 return $already_ran_enable;
336 }
337
338 // if WPO_ADVANCED_CACHE isn't set, or environment doesn't contain the right constant, force regeneration
339 if (!defined('WPO_ADVANCED_CACHE') || !defined('WP_CACHE')) {
340 $force_enable = true;
341 }
342
343 $this->maybe_regenerate_cache_config_file();
344
345 if (!$force_enable) {
346 $already_ran_enable = true;
347 return true;
348 }
349
350 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
351 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
352 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
353
354 if (!defined('WP_CLI') || !WP_CLI) {
355 $message .= "\n\n".sprintf(__('1. Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
356 $message .= "\n".__('2. Edit or create a file with the name advanced-cache.php', 'wp-optimize');
357 $message .= "\n".__('3. Copy and paste the following lines into the file:', 'wp-optimize');
358 }
359
360 $already_ran_enable = new WP_Error("write_advanced_cache", $message);
361 return $already_ran_enable;
362 }
363
364 if (!$this->write_wp_config(true)) {
365 $already_ran_enable = new WP_Error("write_wp_config", "Could not turn on the WP_CACHE constant in wp-config.php. Check your permissions.");
366 return $already_ran_enable;
367 }
368
369 if (!$this->verify_cache()) {
370 $errors = $this->get_errors();
371 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
372 return $already_ran_enable;
373 }
374
375 $already_ran_enable = true;
376
377 return true;
378 }
379
380 /**
381 * Disables page cache
382 *
383 * @return bool - true on success, false otherwise
384 */
385 public function disable() {
386 $ret = true;
387
388 $advanced_cache_file = $this->get_advanced_cache_filename();
389
390 // 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.
391 $disabled_wp_config = $this->write_wp_config(false);
392 if (!$disabled_wp_config) {
393 $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
394 $action = "deactivate_".$plugin_basename."/wp-optimize.php";
395 if (current_action() === $action) {
396 $cache_config = WPO_Cache_Config::instance()->config;
397 $cache_config['enable_page_caching'] = false;
398 WPO_Cache_Config::instance()->update($cache_config, true);
399 }
400 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
401 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
402 }
403
404 $disabled_advanced_cache = true;
405 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
406 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
407 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
408 if (file_exists($advanced_cache_file) && (!unlink($advanced_cache_file) && false === file_put_contents($advanced_cache_file, "<?php\n// WP-Optimize: page cache disabled"))) {
409 $disabled_advanced_cache = false;
410 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
411 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
412 }
413 }
414
415 // If both actions failed, the cache wasn't disabled. So we send an error. If only one succeeds, it will still be disabled.
416 if (!$disabled_wp_config && !$disabled_advanced_cache) {
417 $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'));
418 }
419
420 // Delete cache to avoid stale cache on next activation
421 $this->purge();
422
423 return $ret;
424 }
425
426
427 /**
428 * Purges the cache
429 *
430 * @return bool - true on success, false otherwise
431 */
432 public function purge() {
433
434 if (!self::delete(WPO_CACHE_FILES_DIR)) {
435 $this->log("The request to the filesystem to delete the cache failed");
436 return false;
437 }
438
439 /**
440 * Fires after purging the cache
441 */
442 do_action('wpo_cache_flush');
443
444 return true;
445 }
446
447 /**
448 * Purge cache files older than cache life span
449 */
450 public function purge_old() {
451 $page_cache_length = $this->config->get_option('page_cache_length');
452 $expires = time() - $page_cache_length;
453 $log = array();
454 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
455 // get all directories that are a direct child of current directory
456 if (is_dir($cache_folder) && is_writable($cache_folder)) {
457 if ($handle = opendir($cache_folder)) {
458 while (false !== ($d = readdir($handle))) {
459 if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
460 continue;
461 }
462
463 if ($this->is_front_page_cache($d)) {
464 $modified_time = (int) filemtime("$cache_folder/$d");
465 if ($modified_time <= $expires) {
466 unlink("$cache_folder/$d");
467 }
468 continue;
469 }
470
471 $dir = $cache_folder.'/'.$d;
472 if (!is_dir($dir)) continue;
473
474 $stat = stat($dir);
475 $modified_time = $stat['mtime'];
476 $log[] = "checking if cache has expired - $d";
477 if ($modified_time <= $expires) {
478 $log[] = "deleting cache in $dir";
479 wpo_delete_files($dir, true);
480 if (file_exists($dir)) rmdir($dir);
481 }
482 }
483 closedir($handle);
484 }
485 }
486 return $log;
487 }
488
489 /**
490 * Finds out given cache file is of front page or not
491 *
492 * @return bool
493 */
494 private function is_front_page_cache($d) {
495 return in_array($d, array('index.html', 'index.html.gz'));
496 }
497
498 /**
499 * Purges the cache
500 *
501 * @return bool - true on success, false otherwise
502 */
503 public function clean_up() {
504
505 $this->disable();
506
507 if (!self::delete(WPO_CACHE_DIR, true)) {
508 $this->log("The request to the filesystem to clean up the cache failed");
509 return false;
510 }
511
512 return true;
513 }
514
515 /**
516 * Check if cache is enabled and working
517 *
518 * @return bool - true on success, false otherwise
519 */
520 public function is_enabled() {
521
522 if (!$this->config->get_option('enable_page_caching')) return false;
523
524 if (!defined('WP_CACHE') || !WP_CACHE) {
525 if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
526 $this->log("WP_CACHE constant is not present in wp-config.php");
527 return false;
528 }
529 }
530
531 if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
532 if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
533 $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
534 return false;
535 }
536 }
537
538 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
539 if (!file_exists($config_file)) {
540 $this->log("Cache config file $config_file is not present");
541 return false;
542 }
543
544 return true;
545 }
546
547 /**
548 * Create the folder structure needed for cache to work
549 *
550 * @return bool - true on success, false otherwise
551 */
552 private function create_folders() {
553
554 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
555 return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s. Please check your file permissions.'), str_ireplace(ABSPATH, '', WPO_CACHE_DIR)));
556 }
557
558 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
559 return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s. Please check your file permissions.'), str_ireplace(ABSPATH, '', WPO_CACHE_CONFIG_DIR)));
560 }
561
562 if (!is_dir(WPO_CACHE_FILES_DIR)) {
563 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
564 return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s. Please check your file permissions.'), str_ireplace(ABSPATH, '', WPO_CACHE_FILES_DIR)));
565 } else {
566 wpo_disable_cache_directories_viewing();
567 }
568 }
569
570 return true;
571 }
572
573 /**
574 * Get advanced-cache.php file name with full path.
575 *
576 * @return string
577 */
578 public function get_advanced_cache_filename() {
579 return untrailingslashit(WP_CONTENT_DIR) . '/advanced-cache.php';
580 }
581
582 /**
583 * Writes advanced-cache.php
584 *
585 * @param boolean $update_required - Whether the update is required or not.
586 * @return bool
587 */
588 private function write_advanced_cache($update_required = false) {
589 $config_file_basename = $this->config->get_cache_config_filename();
590 $cache_file_basename = untrailingslashit(plugin_dir_path(__FILE__));
591 $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
592 $cache_path = '/wpo-cache';
593 $cache_files_path = '/cache/wpo-cache';
594 $cache_extensions_path = WPO_CACHE_EXT_DIR;
595 $wpo_version = WPO_VERSION;
596 $wpo_home_url = trailingslashit(home_url());
597
598 // CS does not like heredoc
599 // phpcs:disable
600 $this->advanced_cache_file_content = <<<EOF
601 <?php
602
603 if (!defined('ABSPATH')) die('No direct access allowed');
604
605 // WP-Optimize advanced-cache.php (written by version: $wpo_version) (do not change this line, it is used for correctness checks)
606
607 if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);
608
609 \$possible_plugin_locations = array(
610 defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR.'/$plugin_basename/cache' : false,
611 defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR.'/plugins/$plugin_basename/cache' : false,
612 dirname(__FILE__).'/plugins/$plugin_basename/cache',
613 '$cache_file_basename',
614 );
615
616 \$plugin_location = false;
617
618 foreach (\$possible_plugin_locations as \$possible_location) {
619 if (false !== \$possible_location && @file_exists(\$possible_location.'/file-based-page-cache.php')) {
620 \$plugin_location = \$possible_location;
621 break;
622 }
623 }
624
625 if (false === \$plugin_location) {
626 if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND')) define('WPO_PLUGIN_LOCATION_NOT_FOUND', true);
627 \$protocol = \$_SERVER['REQUEST_SCHEME'];
628 \$host = \$_SERVER['HTTP_HOST'];
629 \$request_uri = \$_SERVER['REQUEST_URI'];
630 if (strcasecmp('$wpo_home_url', \$protocol . '://' . \$host . \$request_uri) === 0) {
631 error_log('WP-Optimize: No caching took place, because the plugin location could not be found');
632 }
633 } else {
634 if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND')) define('WPO_PLUGIN_LOCATION_NOT_FOUND', false);
635 }
636
637 if (is_admin()) { return; }
638
639 if (!defined('WPO_CACHE_DIR')) define('WPO_CACHE_DIR', WP_CONTENT_DIR.'$cache_path');
640 if (!defined('WPO_CACHE_CONFIG_DIR')) define('WPO_CACHE_CONFIG_DIR', WPO_CACHE_DIR.'/config');
641 if (!defined('WPO_CACHE_FILES_DIR')) define('WPO_CACHE_FILES_DIR', WP_CONTENT_DIR.'$cache_files_path');
642 if (false !== \$plugin_location) {
643 if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', \$plugin_location.'/extensions');
644 } else {
645 if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', '$cache_extensions_path');
646 }
647
648 if (!@file_exists(WPO_CACHE_CONFIG_DIR . '/$config_file_basename')) { return; }
649
650 \$GLOBALS['wpo_cache_config'] = @json_decode(file_get_contents(WPO_CACHE_CONFIG_DIR . '/$config_file_basename'), true);
651
652 if (empty(\$GLOBALS['wpo_cache_config'])) {
653 include_once(WPO_CACHE_CONFIG_DIR . '/$config_file_basename');
654 }
655
656 if (empty(\$GLOBALS['wpo_cache_config']) || empty(\$GLOBALS['wpo_cache_config']['enable_page_caching'])) { return; }
657
658 if (false !== \$plugin_location) { include_once(\$plugin_location.'/file-based-page-cache.php'); }
659
660 EOF;
661
662 // phpcs:enable
663 $advanced_cache_filename = $this->get_advanced_cache_filename();
664
665 // If the file content is already up to date, success
666 if (is_file($advanced_cache_filename) && file_get_contents($advanced_cache_filename) === $this->advanced_cache_file_content) {
667 $this->advanced_cache_file_writing_error = false;
668 return true;
669 }
670
671 // check if we can't write the advanced cache file
672 // case 1: the directory is read-only and the file doesn't exist
673 if (!is_file($advanced_cache_filename) && !is_writable(dirname($advanced_cache_filename))) {
674 $this->advanced_cache_file_writing_error = true;
675 return false;
676 }
677
678 // case 2: the file already exists but it's read-only
679 if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
680 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
681 $this->advanced_cache_file_writing_error = true;
682 return false;
683 } else {
684 $this->advanced_cache_file_writing_error = false;
685 return true;
686 }
687 }
688
689 if (!file_put_contents($this->get_advanced_cache_filename(), $this->advanced_cache_file_content)) {
690 $this->advanced_cache_file_writing_error = true;
691 return false;
692 }
693
694 $this->advanced_cache_file_writing_error = false;
695 return true;
696 }
697
698 /**
699 * Update advanced cache version if needed.
700 */
701 public function maybe_update_advanced_cache() {
702
703 if (!$this->is_enabled()) return;
704
705 if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND') || (defined('WPO_PLUGIN_LOCATION_NOT_FOUND') && true === WPO_PLUGIN_LOCATION_NOT_FOUND)) {
706 if (!$this->write_advanced_cache(true)) {
707 add_action('admin_notices', array($this, 'show_admin_notice_advanced_cache'));
708 }
709 }
710
711 // from 3.0.17 we use more secure way to store cache config files and need update advanced-cache.php
712 $advanced_cache_current_version = $this->get_advanced_cache_version();
713 if ($advanced_cache_current_version && version_compare($advanced_cache_current_version, $this->_minimum_advanced_cache_file_version, '>=')) return;
714
715 if (!$this->write_advanced_cache()) {
716 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
717 } else {
718 $this->update_cache_config();
719 }
720 }
721
722 /**
723 * Show notification when advanced-cache.php could not be updated.
724 */
725 public function notice_advanced_cache_autoupdate_error() {
726 $this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
727 ' <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');
728 }
729
730 /**
731 * Get WPO version number from advanced-cache.php file.
732 *
733 * @return bool|mixed
734 */
735 public function get_advanced_cache_version() {
736 if (!is_file($this->get_advanced_cache_filename())) return false;
737
738 $version = false;
739 $content = file_get_contents($this->get_advanced_cache_filename());
740
741 if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
742 $version = $match[1];
743 }
744
745 return $version;
746 }
747
748 /**
749 * Set WP_CACHE on or off in wp-config.php
750 *
751 * @param boolean $status value of WP_CACHE.
752 * @return boolean true if the value was set, false otherwise
753 */
754 private function write_wp_config($status = true) {
755 // If we changed the value in wp-config, save it, in case we need to change it again in the same run.
756 static $changed = false;
757
758 if (defined('WP_CACHE') && WP_CACHE === $status && !$changed) {
759 return true;
760 }
761
762 $config_path = $this->_get_wp_config();
763
764 // Couldn't find wp-config.php.
765 if (!$config_path) {
766 return false;
767 }
768
769 $config_file_string = file_get_contents($config_path);
770
771 // Config file is empty. Maybe couldn't read it?
772 if (empty($config_file_string)) {
773 return false;
774 }
775
776 $config_file = preg_split("#(\n|\r\n)#", $config_file_string);
777 $line_key = false;
778
779 foreach ($config_file as $key => $line) {
780 if (!preg_match('/^\s*define\(\s*(\'|")([A-Z_]+)(\'|")(.*)/i', $line, $match)) {
781 continue;
782 }
783
784 if ('WP_CACHE' === $match[2]) {
785 $line_key = $key;
786 }
787 }
788
789 if (false !== $line_key) {
790 unset($config_file[$line_key]);
791 }
792
793
794 if ($status) {
795 array_shift($config_file);
796 array_unshift($config_file, '<?php', "define('WP_CACHE', true); // WP-Optimize Cache");
797 }
798
799 foreach ($config_file as $key => $line) {
800 if ('' === $line) {
801 unset($config_file[$key]);
802 }
803 }
804 if (!file_put_contents($config_path, implode(PHP_EOL, $config_file))) {
805 return false;
806 }
807 $changed = true;
808 return true;
809 }
810
811 /**
812 * Verify we can write to the file system
813 *
814 * @return boolean
815 */
816 private function verify_cache() {
817 if (function_exists('clearstatcache')) {
818 clearstatcache();
819 }
820 $errors = 0;
821
822 // First check wp-config.php.
823 if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
824 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
825 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
826 }
827
828 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
829
830 // Now check wp-content. We need to be able to create files of the same user as this file.
831 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))) {
832 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
833 $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'));
834 $errors++;
835 }
836
837 if (file_exists(WPO_CACHE_FILES_DIR)) {
838 if (!is_writable(WPO_CACHE_FILES_DIR)) {
839 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
840 $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));
841 }
842 }
843
844 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
845 if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
846 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
847 // If the config exists, only send a warning. Otherwise send an error.
848 $type = 'warning';
849 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
850 $type = 'error';
851 $errors++;
852 }
853 $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);
854 }
855 }
856
857 return !$errors;
858 }
859
860 /**
861 * Update permalink strucutre in cache config
862 *
863 * @param string $old_value Old value of permalink_structure option
864 * @param string $value New value of permalink_structure option
865 * @param string $option Option name `permalink_structure`
866 */
867 public function update_option_permalink_structure($old_value, $value, $option) {
868 $current_config = $this->config->get();
869 if ($old_value != $value) {
870 $current_config[$option] = $value;
871 $this->config->update($current_config, true);
872 }
873 }
874
875 /**
876 * Update cache config. Used to support 3d party plugins.
877 */
878 public function update_cache_config() {
879 // get current cache settings.
880 $current_config = $this->config->get();
881 // and call update to change if need cookies and query variable names.
882 $this->config->update($current_config, true);
883 }
884
885 /**
886 * Delete information about cache size.
887 */
888 public function delete_cache_size_information() {
889 delete_transient('wpo_get_cache_size');
890 }
891
892 /**
893 * Get current cache size.
894 *
895 * @return array
896 */
897 public function get_cache_size() {
898 $cache_size = get_transient('wpo_get_cache_size');
899
900 if (!empty($cache_size)) return $cache_size;
901
902 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
903 $cache_size = array(
904 'size' => $infos['size'],
905 'file_count' => $infos['file_count']
906 );
907
908 set_transient('wpo_get_cache_size', $cache_size);
909
910 return $cache_size;
911 }
912
913 /**
914 * Fetch directory informations.
915 *
916 * @param string $dir
917 * @return array
918 */
919 private function get_dir_infos($dir) {
920 $dir_size = 0;
921 $file_count = 0;
922
923 $handle = is_dir($dir) ? opendir($dir) : false;
924
925 if (false === $handle) {
926 return array('size' => 0, 'file_count' => 0);
927 }
928
929 $file = readdir($handle);
930
931 while (false !== $file) {
932
933 if ('.' != $file && '..' != $file) {
934 $current_file = $dir.'/'.$file;
935
936 if (is_dir($current_file)) {
937 $sub_dir_infos = $this->get_dir_infos($current_file);
938 $dir_size += $sub_dir_infos['size'];
939 $file_count += $sub_dir_infos['file_count'];
940 } elseif (is_file($current_file)) {
941 $dir_size += filesize($current_file);
942 $file_count++;
943 }
944 }
945
946 $file = readdir($handle);
947
948 }
949
950 return array('size' => $dir_size, 'file_count' => $file_count);
951 }
952
953 /**
954 * Returns the path to wp-config
955 *
956 * @return string|boolean wp-config.php path.
957 */
958 private function _get_wp_config() {
959
960 $config_path = false;
961
962 foreach (get_included_files() as $filename) {
963 if (preg_match('/(\\\\|\/)wp-config\.php$/i', $filename)) {
964 $config_path = $filename;
965 break;
966 }
967 }
968
969 // WP-CLI doesn't include wp-config.php that's why we use function from WP-CLI to locate config file.
970 if (!$config_path && is_callable('wpo_wp_cli_locate_wp_config')) {
971 $config_path = wpo_wp_cli_locate_wp_config();
972 }
973
974 return $config_path;
975 }
976
977 /**
978 * Util to delete folders and/or files
979 *
980 * @param string $src
981 * @return boolean
982 */
983 public static function delete($src) {
984
985 return wpo_delete_files($src);
986
987 }
988
989 /**
990 * Delete cached files for specific url.
991 *
992 * @param string $url
993 * @param bool $recursive If true child elements will deleted too
994 *
995 * @return bool
996 */
997 public static function delete_cache_by_url($url, $recursive = false) {
998 if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
999
1000 $path = self::get_full_path_from_url($url);
1001
1002 do_action('wpo_delete_cache_by_url', $url, $recursive);
1003
1004 return wpo_delete_files($path, $recursive);
1005 }
1006
1007 /**
1008 * Delete cached files for single post.
1009 *
1010 * @param integer $post_id The post ID
1011 *
1012 * @return bool
1013 */
1014 public static function delete_single_post_cache($post_id) {
1015
1016 if (!defined('WPO_CACHE_FILES_DIR')) return;
1017
1018 $post_url = get_permalink($post_id);
1019
1020 $path = self::get_full_path_from_url($post_url);
1021
1022 // for posts with pagination run purging cache recursively.
1023 $post = get_post($post_id);
1024 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1025
1026 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1027
1028 return wpo_delete_files($path, $recursive);
1029 }
1030
1031 /**
1032 * Delete cached home page files.
1033 */
1034 public static function delete_homepage_cache() {
1035
1036 if (!defined('WPO_CACHE_FILES_DIR')) return;
1037
1038 $homepage_url = get_home_url(get_current_blog_id());
1039
1040 $path = self::get_full_path_from_url($homepage_url);
1041
1042 do_action('wpo_delete_cache_by_url', $homepage_url, false);
1043
1044 wpo_delete_files($path, false);
1045 }
1046
1047 /**
1048 * Delete sitemap cahche.
1049 */
1050 public static function delete_sitemap_cache() {
1051 if (!defined('WPO_CACHE_FILES_DIR')) return;
1052
1053 $homepage_url = get_home_url(get_current_blog_id());
1054
1055 $path = trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($homepage_url));
1056
1057 if (!is_dir($path)) return;
1058
1059 $handle = opendir($path);
1060
1061 if (false !== $handle) {
1062 $file = readdir($handle);
1063
1064 while (false !== $file) {
1065
1066 if ('.' != $file && '..' != $file && is_dir($path . $file) && preg_match('/.*sitemap.*\.xml/i', $file)) {
1067 do_action('wpo_delete_cache_by_url', $path . $file, false);
1068 wpo_delete_files($path . $file, true);
1069 }
1070
1071 $file = readdir($handle);
1072 }
1073 }
1074
1075 closedir($handle);
1076 }
1077
1078 /**
1079 * Delete feed from cache.
1080 */
1081 public static function delete_feed_cache() {
1082 if (!defined('WPO_CACHE_FILES_DIR')) return;
1083
1084 $homepage_url = get_home_url(get_current_blog_id());
1085
1086 $path = self::get_full_path_from_url($homepage_url) . 'feed/';
1087
1088 do_action('wpo_delete_cache_by_url', $path, true);
1089
1090 wpo_delete_files($path, true);
1091 }
1092
1093 /**
1094 * Delete post feed from cache.
1095 */
1096 public static function delete_post_feed_cache($post_id) {
1097 if (!defined('WPO_CACHE_FILES_DIR')) return;
1098
1099 $post_url = get_permalink($post_id);
1100
1101 $path = self::get_full_path_from_url($post_url) . 'feed/';
1102
1103 do_action('wpo_delete_cache_by_url', $path, true);
1104
1105 wpo_delete_files($path, true);
1106 }
1107
1108 /**
1109 * Delete comments feed from cache.
1110 */
1111 public static function delete_comments_feed() {
1112 if (!defined('WPO_CACHE_FILES_DIR')) return;
1113
1114 $comments_feed_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/feed/';
1115
1116 $path = self::get_full_path_from_url($comments_feed_url);
1117
1118 do_action('wpo_delete_cache_by_url', $comments_feed_url, true);
1119
1120 wpo_delete_files($path, true);
1121
1122 // delete empty comments dir from the cache
1123 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1124 $path = self::get_full_path_from_url($comments_url);
1125
1126 if (wpo_is_empty_dir($path)) {
1127 wpo_delete_files($path, true);
1128 }
1129 }
1130
1131 /**
1132 * Returns full path to the cache folder by url.
1133 *
1134 * @param string $url
1135 * @return string
1136 */
1137 private static function get_full_path_from_url($url) {
1138 return trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($url));
1139 }
1140
1141 /**
1142 * Admin actions
1143 *
1144 * @return void
1145 */
1146 public function admin_init() {
1147 // Maybe update the advanced cache.
1148 if ((!defined('DOING_AJAX') || !DOING_AJAX) && current_user_can('update_plugins')) {
1149 $this->maybe_update_advanced_cache();
1150 $this->cron_activate();
1151 }
1152 }
1153
1154 /**
1155 * Logs error messages
1156 *
1157 * @param string $message
1158 * @return null|void
1159 */
1160 public function log($message) {
1161 if (isset($this->logger)) {
1162 $this->logger->log($message, 'error');
1163 }
1164 }
1165
1166 /**
1167 * Returns an instance of the current class, creates one if it doesn't exist
1168 *
1169 * @return object
1170 */
1171 public static function instance() {
1172 if (empty(self::$instance)) {
1173 self::$instance = new self();
1174 }
1175 return self::$instance;
1176 }
1177
1178 /**
1179 * Adds an error to the error store
1180 *
1181 * @param string $code - The error code
1182 * @param string $message - The error's message
1183 * @param string $type - The error's type (error, warning)
1184 * @return void
1185 */
1186 public function add_error($code, $message, $type = 'error') {
1187 if (!isset($this->_errors[$type])) {
1188 $this->_errors[$type] = new WP_Error($code, $message);
1189 } else {
1190 $this->_errors[$type]->add($code, $message);
1191 }
1192 }
1193
1194 /**
1195 * Adds a warning to the error store
1196 *
1197 * @param string $code - The error code
1198 * @param string $message - The error's message
1199 * @return void
1200 */
1201 public function add_warning($code, $message) {
1202 $this->add_error($code, $message, 'warning');
1203 }
1204
1205 /**
1206 * Get all recorded errors
1207 *
1208 * @param string $type - The error type
1209 * @param boolean $get_messages_only - Whether to get only the messages, or the full WP_Error object
1210 * @return boolean|array|WP_Error
1211 */
1212 public function get_errors($type = 'error', $get_messages_only = true) {
1213 if (!$this->has_errors($type)) return false;
1214 $errors = $this->_errors[$type];
1215 if ($get_messages_only) {
1216 return $errors->get_error_messages();
1217 }
1218 return $errors;
1219 }
1220
1221 /**
1222 * Check if any errors were recorded
1223 *
1224 * @param string $type - The error type
1225 * @return boolean
1226 */
1227 public function has_errors($type = 'error') {
1228 return isset($this->_errors[$type]) && !empty($this->_errors[$type]) && $this->_errors[$type]->has_errors();
1229 }
1230
1231 /**
1232 * Check if any warnings were recorded
1233 *
1234 * @return boolean
1235 */
1236 public function has_warnings() {
1237 return $this->has_errors('warning');
1238 }
1239
1240 /**
1241 * Check the cache compatibility issues.
1242 */
1243 public function check_compatibility_issues() {
1244 if (!$this->is_enabled()) return;
1245
1246 if ($this->is_pagespeedninja_gzip_active()) add_action('admin_notices', array($this, 'show_pagespeedninja_gzip_notice'));
1247 if ($this->is_farfutureexpiration_gzip_active()) add_action('admin_notices', array($this, 'show_farfutureexpiration_gzip_notice'));
1248 }
1249
1250 /**
1251 * Check if PageSpeed Ninja is active and GZIP compression option is enabled.
1252 *
1253 * @return bool
1254 */
1255 public function is_pagespeedninja_gzip_active() {
1256 if (!class_exists('PagespeedNinja')) return false;
1257
1258 $options = get_option('pagespeedninja_config');
1259 $gzip = !empty($options) ? (bool) $options['psi_EnableGzipCompression'] && (bool) $options['html_gzip'] : false;
1260
1261 return $gzip;
1262 }
1263
1264 /**
1265 * Output PageSpeed Ninja Gzip notice.
1266 */
1267 public function show_pagespeedninja_gzip_notice() {
1268 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>';
1269 }
1270
1271 /**
1272 * Check if Far Future Expiration is active and GZIP compression option is enabled.
1273 *
1274 * @return bool
1275 */
1276 public function is_farfutureexpiration_gzip_active() {
1277 if (!class_exists('farFutureExpiration')) return false;
1278
1279 $options = get_option('far_future_expiration_settings');
1280 $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1281
1282 return $gzip;
1283 }
1284
1285 /**
1286 * Output Far Future Expiration Gzip notice.
1287 */
1288 public function show_farfutureexpiration_gzip_notice() {
1289 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>';
1290 }
1291
1292 /**
1293 * This is a notice to show users that writing `advanced-cache.php` failed
1294 */
1295 public function show_admin_notice_advanced_cache() {
1296 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1297 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1298 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1299 }
1300
1301 /**
1302 * Scheduler public functions to update schedulers
1303 *
1304 * @param array $schedules An array of schedules being passed.
1305 * @return array An array of schedules being returned.
1306 */
1307 public function cron_schedules($schedules) {
1308 $page_cache_length = $this->config->get_option('page_cache_length');
1309 $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'));
1310 return $schedules;
1311 }
1312
1313 /**
1314 * Update cache config file to reflect the webp images option
1315 */
1316 public function update_webp_images_option() {
1317 if ($this->is_enabled()) {
1318 $cache_settings = WPO_Cache_Config::instance()->get();
1319 $cache_settings['use_webp_images'] = WP_Optimize()->get_options()->get_option('webp_conversion');
1320 WPO_Cache_Config::instance()->update($cache_settings);
1321 }
1322 }
1323
1324 /**
1325 * May be regenerate cache config file, in case of migrations
1326 */
1327 private function maybe_regenerate_cache_config_file() {
1328 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1329 if (!file_exists($config_file)) {
1330 $config = $this->config->get();
1331 $this->config->write($config);
1332 }
1333 }
1334 }
1335
1336 endif;
1337