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

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