PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
← All changes | cache/class-wpo-page-cache.php +596 -191 3.2.21 → 4.7.0 View file →
@@ -21,19 +21,10 @@
21 21 * Directory that stores config and related files
22 22 */
23 23 if (!defined('WPO_CACHE_CONFIG_DIR')) define('WPO_CACHE_CONFIG_DIR', WPO_CACHE_DIR.'/config');
24 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 25 require_once dirname(__FILE__) . '/file-based-page-cache-functions.php';
31 26
32 -if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
33 - require_once dirname(__FILE__) . '/php-5.3-functions.php';
34 -}
35 -
36 27 wpo_cache_load_extensions();
37 28
38 29 if (!class_exists('WPO_Page_Cache')) :
39 30
@@ -53,11 +44,18 @@
53 44 */
54 45 public $logger;
55 46
56 47 /**
48 + * File Logger for this class
49 + *
50 + * @var mixed
51 + */
52 + private $file_logger;
53 +
54 + /**
57 55 * Instance of this class
58 56 *
59 - * @var mixed
57 + * @var WPO_Page_Cache | null
60 58 */
61 59 public static $instance;
62 60
63 61 /**
@@ -88,21 +86,44 @@
88 86 * @var string
89 87 */
90 88 private $_minimum_advanced_cache_file_version = '3.0.17';
91 89
90 + /**
91 + * Flag to decide whether to purge or not
92 + *
93 + * @var bool
94 + */
92 95 public $should_purge;
93 96
94 97 /**
98 + * Flag to decide whether to purge or not
99 + *
100 + * @var bool
101 + */
102 + private $files_to_ignore = array('index.php','.htaccess');
103 +
104 + /**
95 105 * Set everything up here
96 106 */
97 107 public function __construct() {
98 108 $this->config = WPO_Cache_Config::instance();
109 + if (empty($GLOBALS['wpo_cache_config'])) {
110 + $config_file_path = $this->config->get_config_file_path();
111 + if (file_exists($config_file_path)) {
112 + include_once($config_file_path);
113 + }
114 + }
115 +
99 116 WPO_Cache_Rules::instance();
117 + WP_Optimize_Page_Cache_Preloader::instance();
118 +
100 119 $this->logger = new Updraft_PHP_Logger();
120 + $this->file_logger = new Updraft_File_Logger(WP_Optimize_Utils::get_log_file_path('cache'));
101 121
102 122 add_action('activated_plugin', array($this, 'activate_deactivate_plugin'));
103 123 add_action('deactivate_plugin', array($this, 'activate_deactivate_plugin'));
104 124 add_action('wpo_purge_old_cache', array($this, 'purge_old'));
125 + add_action('wpo_prune_cache_logs', array($this, 'prune_cache_logs'));
105 126
106 127 /**
107 128 * Regenerate config file on cache flush.
108 129 */
@@ -107,8 +128,9 @@
107 128 * Regenerate config file on cache flush.
108 129 */
109 130 add_action('wpo_cache_flush', array($this, 'update_cache_config'));
110 131 add_action('wpo_cache_flush', array($this, 'delete_cache_size_information'));
132 + add_action('wpo_cache_flush', array($this, 'update_cache_cleared_time'));
111 133 add_action('update_option_permalink_structure', array($this, 'update_option_permalink_structure'), 10, 3);
112 134
113 135 // Add purge cache link to admin bar.
114 136 add_filter('wpo_cache_admin_bar_menu_items', array($this, 'admin_bar_purge_cache'), 20, 1);
@@ -124,23 +146,140 @@
124 146 add_action('update_option_time_format', array($this, 'update_option_time_format'), 10, 2);
125 147
126 148 $this->check_compatibility_issues();
127 149
150 + $this->ensure_cron_schedules_filter_registered();
151 +
152 + add_action('wpo_save_images_settings', array($this, 'update_webp_images_option'));
153 +
154 + add_action('wpo_preload_url', array($this, 'maybe_preload_url'));
155 +
156 + // Setup filters for exceptions.
157 + add_filter('wpo_restricted_cache_page_type', 'wpo_restricted_cache_page_type');
158 + add_filter('wpo_url_in_conditional_tags_exceptions', 'wpo_url_in_conditional_tags_exceptions');
159 + }
160 +
161 + /**
162 + * Ensures that the cron-related filter is registered.
163 + *
164 + * If `init` has already run, the filter is registered immediately.
165 + * Otherwise, it schedules the registration to occur during `init`.
166 + *
167 + * @return void
168 + */
169 + private function ensure_cron_schedules_filter_registered() {
170 + if (did_action('init')) {
171 + $this->register_cron_schedules_filter();
172 + } else {
173 + add_action('init', array($this, 'register_cron_schedules_filter'));
174 + }
175 + }
176 +
177 + /**
178 + * Registers the custom cron schedule filter for WP-Cron.
179 + *
180 + * This method hooks the class's `cron_schedules()` method into WordPress's
181 + * `cron_schedules` filter, allowing the plugin to define custom cron intervals.
182 + *
183 + * It is recommended to call this method on or after the `init` action to ensure
184 + * that translations and other dependencies are properly loaded before the filter runs.
185 + *
186 + * @return void
187 + */
188 + public function register_cron_schedules_filter() {
128 189 add_filter('cron_schedules', array($this, 'cron_schedules'));
129 - add_action('wpo_save_images_settings', array($this, 'update_webp_images_option'));
130 190 }
131 191
132 192 /**
133 - * Activate cron job for deleting expired cache files
193 + * Determines whether the current page should be cached.
194 + *
195 + * This method checks cache rules to identify if caching is possible.
196 + * If caching is not allowed, it adds appropriate HTTP headers and debug messages.
197 + *
198 + * @return bool|array True if the page should be cached, false otherwise or not empty array with the reasons why the page isn't cached
134 199 */
200 + public function should_cache_page() {
201 +
202 + if (!$this->is_enabled()) return false;
203 +
204 + return wpo_can_serve_from_cache();
205 + }
206 +
207 + /**
208 + * Maybe add information about why the page is not cached to the headers and footer output
209 + *
210 + * @param array $no_cache_because
211 + * @return void
212 + */
213 + public function maybe_add_no_cache_because_info($no_cache_because) {
214 + // Add http header
215 + if (!wp_doing_cron() && !empty($no_cache_because)) {
216 + $no_cache_because_message = join(", ", $no_cache_because);
217 + wpo_cache_add_nocache_http_header_with_send_headers_action($no_cache_because_message);
218 +
219 + $not_cached_details = "";
220 +
221 + // Output the reason only when the user has turned on debugging
222 + if ((defined('WP_DEBUG') && WP_DEBUG) || !empty(TeamUpdraft\WP_Optimize\Includes\Fragments\fetch_superglobal('get', 'wpo_cache_debug'))) {
223 + $not_cached_details = "because: ".$no_cache_because_message;
224 + }
225 + wpo_cache_add_footer_output(sprintf("Page not served from cache %s", $not_cached_details));
226 + }
227 + }
228 +
229 + /**
230 + * Handle cache settings update
231 + *
232 + * @param array $new_settings
233 + * @return void
234 + */
235 + public function cache_settings_updated($new_settings) {
236 +
237 + $this->unschedule_purge_old_cache();
238 +
239 + if ($new_settings['enable_page_caching']) {
240 + $this->schedule_purge_old_cache();
241 + }
242 + }
243 +
244 + /**
245 + * Activate cron job when the cache is enabled for deleting expired cache files
246 + */
135 247 public function cron_activate() {
248 + if ($this->is_enabled()) {
249 + $this->schedule_purge_old_cache();
250 + } else {
251 + $this->unschedule_purge_old_cache();
252 + }
253 + }
254 +
255 + /**
256 + * Schedules events to purge old cache
257 + *
258 + * @return void
259 + */
260 + private function schedule_purge_old_cache() {
136 261 $page_cache_length = $this->config->get_option('page_cache_length');
262 +
137 263 if (!wp_next_scheduled('wpo_purge_old_cache')) {
138 - wp_schedule_event(time() + (false === $page_cache_length ? '86400' : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
264 + wp_schedule_event(time() + (false === $page_cache_length ? 86400 : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
139 265 }
266 + if (!wp_next_scheduled('wpo_prune_cache_logs')) {
267 + wp_schedule_event(time(), 'weekly', 'wpo_prune_cache_logs');
268 + }
140 269 }
141 270
142 271 /**
272 + * Unschedules events to purge old cache
273 + *
274 + * @return void
275 + */
276 + private function unschedule_purge_old_cache() {
277 + wp_clear_scheduled_hook('wpo_purge_old_cache');
278 + wp_clear_scheduled_hook('wpo_prune_cache_logs');
279 + }
280 +
281 + /**
143 282 * Do required actions on activate/deactivate any plugin.
144 283 */
145 284 public function activate_deactivate_plugin() {
146 285
@@ -147,9 +286,11 @@
147 286 wp_clear_scheduled_hook('wpo_purge_old_cache');
148 287
149 288 $this->update_cache_config();
150 289
151 - $this->purge();
290 + $is_cache_purged = $this->purge();
291 +
292 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: plugin activation/deactivation");
152 293 }
153 294
154 295 /**
155 296 * Check if current user can purge cache.
@@ -157,15 +298,10 @@
157 298 * @return bool
158 299 */
159 300 public function can_purge_cache() {
160 301 if (!$this->is_enabled()) return false;
161 - $required_capability = is_multisite() ? 'manage_network_options' : 'manage_options';
162 302
163 - if (WP_Optimize::is_premium()) {
164 - return current_user_can($required_capability) || WP_Optimize_Premium()->can_purge_the_cache();
165 - } else {
166 - return current_user_can($required_capability);
167 - }
303 + return WP_Optimize_Utils::current_user_can_purge_cache();
168 304 }
169 305
170 306 /**
171 307 * Add Purge from cache in admin bar.
@@ -176,12 +312,13 @@
176 312 public function admin_bar_purge_cache($menu_items) {
177 313 global $pagenow;
178 314 if (!$this->can_purge_cache()) return $menu_items;
179 315
180 - $act_url = remove_query_arg(array('wpo_single_page_cache_purged', 'wpo_all_pages_cache_purged'));
316 + $act_url = WP_Optimize_Utils::get_url_without_cache_purge_params();
181 317
182 318 $cache_size = $this->get_cache_size();
183 319 $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
320 + // translators: %d is the number of files in the cache
184 321 $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>';
185 322
186 323 $menu_items[] = array(
187 324 'id' => 'wpo_cache_stats',
@@ -201,9 +338,9 @@
201 338 ),
202 339 'parent' => 'wpo_purge_cache',
203 340 );
204 341
205 - if (!is_admin() || 'post.php' == $pagenow) {
342 + if (!is_admin() || 'post.php' === $pagenow) {
206 343 $menu_items[] = array(
207 344 'id' => 'wpo_purge_this_page_cache',
208 345 'title' => __('Purge cache for this page', 'wp-optimize'),
209 346 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
@@ -225,22 +362,38 @@
225 362
226 363 if (!$this->can_purge_cache()) return;
227 364
228 365 if (isset($_GET['wpo_single_page_cache_purged']) || isset($_GET['wpo_all_pages_cache_purged'])) {
366 + // phpcs:disable
367 + // We are not using $_GET values, just checks if the variable is set and then use hard coded string
229 368 if (isset($_GET['wpo_single_page_cache_purged'])) {
230 - $notice_function = $_GET['wpo_single_page_cache_purged'] ? 'notice_purge_single_page_cache_success' : 'notice_purge_single_page_cache_error';
369 + $message = $_GET['wpo_single_page_cache_purged'] ? __('Cache purged for this page', 'wp-optimize') : __('Error purging cache for this page', 'wp-optimize');
370 + $type = $_GET['wpo_single_page_cache_purged'] ? 'success' : 'error';
231 371 } else {
232 - $notice_function = $_GET['wpo_all_pages_cache_purged'] ? 'notice_purge_all_pages_cache_success' : 'notice_purge_all_pages_cache_error';
372 + $message = $_GET['wpo_all_pages_cache_purged'] ? __('Cache purged for all pages', 'wp-optimize') : __('Error purging cache for all pages', 'wp-optimize');
373 + $type = $_GET['wpo_all_pages_cache_purged'] ? 'success' : 'error';
233 374 }
375 + // phpcs:enable
234 376
235 - add_action('admin_notices', array($this, $notice_function));
377 + if (is_admin()) {
378 + $admin_notices_hook = is_network_admin() ? 'network_admin_notices' : 'admin_notices';
236 379
380 + add_action($admin_notices_hook, function() use ($message, $type) {
381 + $this->show_notice($message, $type);
382 + });
383 +
384 + add_action('admin_head', array('WP_Optimize_Utils', 'script_to_remove_cache_purge_params_from_url'));
385 + } else {
386 + printf('<script>window.onload = function() {alert("%s");}</script>', esc_js($message));
387 + WP_Optimize_Utils::script_to_remove_cache_purge_params_from_url();
388 + }
389 +
237 390 return;
238 391 }
239 392
240 393 if (!isset($_GET['_wpo_purge'])) return;
241 394
242 - if (wp_verify_nonce($_GET['_wpo_purge'], 'wpo_purge_single_page_cache')) {
395 + if (wp_verify_nonce(sanitize_key($_GET['_wpo_purge']), 'wpo_purge_single_page_cache')) {
243 396 $success = false;
244 397
245 398 if (is_admin()) {
246 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
@@ -245,55 +398,56 @@
245 398 if (is_admin()) {
246 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
247 400 if ($post > 0) {
248 401 $success = self::delete_single_post_cache($post);
402 + if ($success) $this->file_log("Cache for URL: {{URL}} has been purged, triggered by: " . __METHOD__, $post);
249 403 }
250 404 } else {
251 - $success = self::delete_cache_by_url(wpo_current_url());
405 + $url = wpo_current_url();
406 + $success = self::delete_cache_by_url($url);
407 + if ($success) $this->file_log("Cache for URL: " . self::remove_query_params($url) . " has been purged, triggered by: " . __METHOD__);
252 408 }
253 409
254 - // remove nonce from url and reload page.
255 - wp_redirect(add_query_arg('wpo_single_page_cache_purged', $success, remove_query_arg('_wpo_purge')));
410 + // Remove all existing purge-related parameters and add the new one
411 + $redirect_url = WP_Optimize_Utils::get_url_without_cache_purge_params();
412 + $redirect_url = add_query_arg('wpo_single_page_cache_purged', $success, $redirect_url);
413 +
414 + wp_redirect($redirect_url);
256 415 exit;
257 416
258 - } elseif (wp_verify_nonce($_GET['_wpo_purge'], 'wpo_purge_all_pages_cache')) {
417 + } elseif (wp_verify_nonce(sanitize_key($_GET['_wpo_purge']), 'wpo_purge_all_pages_cache')) {
259 418 $success = self::purge();
419 + $this->maybe_set_preload_cron_job();
420 + if ($success) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
260 421
261 - // remove nonce from url and reload page.
262 - wp_redirect(add_query_arg('wpo_all_pages_cache_purged', $success, remove_query_arg('_wpo_purge')));
422 + // Remove all existing purge-related parameters and add the new one
423 + $redirect_url = WP_Optimize_Utils::get_url_without_cache_purge_params();
424 + $redirect_url = add_query_arg('wpo_all_pages_cache_purged', $success, $redirect_url);
425 +
426 + wp_redirect($redirect_url);
263 427 exit;
264 428 }
265 429 }
266 430
267 431 /**
268 - * Show notification when page cache purged successfully.
432 + * Sets a preload cron job to run after 30 seconds
433 + *
434 + * As preloading may take time for larger sites, we are opting for an immediate cron job instead
435 + * of instant preloading to prevent the current request from slowing down the page load
436 + *
437 + * @return void
269 438 */
270 - public function notice_purge_single_page_cache_success() {
271 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
272 - }
439 + private function maybe_set_preload_cron_job() {
440 + $wpo_page_cache_preloader = WP_Optimize_Page_Cache_Preloader::instance();
273 441
274 - /**
275 - * Show notification when page cache wasn't purged.
276 - */
277 - public function notice_purge_single_page_cache_error() {
278 - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
442 + if ($wpo_page_cache_preloader->is_scheduled_preload_enabled() || $this->should_auto_preload_purged_contents()) {
443 + if (!wp_next_scheduled('wpo_page_cache_run_preload')) {
444 + wp_schedule_single_event(time() + 30, 'wpo_page_cache_run_preload');
445 + }
446 + }
279 447 }
280 -
448 +
281 449 /**
282 - * Show notification when all pages cache purged successfully.
283 - */
284 - public function notice_purge_all_pages_cache_success() {
285 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
286 - }
287 -
288 - /**
289 - * Show notification when all pages cache wasn't purged.
290 - */
291 - public function notice_purge_all_pages_cache_error() {
292 - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
293 - }
294 -
295 - /**
296 450 * Show notification in WordPress admin.
297 451 *
298 452 * @param string $message HTML (no further escaping is performed)
299 453 * @param string $type error, warning, success, or info
@@ -306,10 +460,10 @@
306 460 window.addEventListener('load', function() {
307 461 (function(wp) {
308 462 if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
309 463 wp.data.dispatch('core/notices').createNotice(
310 - '<?php echo $type; ?>',
311 - '<?php echo $message; ?>',
464 + '<?php echo esc_html($type); ?>',
465 + '<?php echo wp_kses_post($message); ?>',
312 466 {
313 467 isDismissible: true,
314 468 }
315 469 );
@@ -317,10 +471,10 @@
317 471 })(window.wp);
318 472 });
319 473 </script>
320 474 <?php else : ?>
321 - <div class="notice wpo-notice notice-<?php echo $type; ?> is-dismissible">
322 - <p><?php echo $message; ?></p>
475 + <div class="notice wpo-notice notice-<?php echo esc_attr($type); ?> is-dismissible">
476 + <p><?php echo wp_kses_post($message); ?></p>
323 477 </div>
324 478 <?php
325 479 endif;
326 480 }
@@ -332,8 +486,9 @@
332 486 *
333 487 * @return WP_Error|bool - true on success, error otherwise
334 488 */
335 489 public function enable($force_enable = false) {
490 + global $is_apache;
336 491 static $already_ran_enable = false;
337 492
338 493 if ($already_ran_enable) return $already_ran_enable;
339 494
@@ -342,8 +497,13 @@
342 497 $already_ran_enable = $folders_created;
343 498 return $already_ran_enable;
344 499 }
345 500
501 + if (!file_exists($this->config->get_config_file_path())) {
502 + $result = $this->update_cache_config();
503 + if (is_wp_error($result)) return $result;
504 + }
505 +
346 506 // if WPO_ADVANCED_CACHE isn't set, or environment doesn't contain the right constant, force regeneration
347 507 if (!defined('WPO_ADVANCED_CACHE') || !defined('WP_CACHE')) {
348 508 $force_enable = true;
349 509 }
@@ -349,11 +509,18 @@
349 509 }
350 510
351 511 $this->maybe_regenerate_cache_config_file();
352 512
513 + // Enable `.htaccess` rules to serve cached files
514 + if ($is_apache && apply_filters('wpo_serve_cache_via_htaccess', false)) {
515 + wpo_disable_cache_directories_viewing();
516 + wpo_allow_access_to_index_cache_files();
517 + $this->update_serve_cache_rules_htaccess_section(true);
518 + }
519 +
353 520 if (!$force_enable) {
354 - $already_ran_enable = true;
355 - return true;
521 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
522 + return $already_ran_enable;
356 523 }
357 524
358 525 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
359 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
@@ -359,8 +526,9 @@
359 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
360 527 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
361 528
362 529 if (!defined('WP_CLI') || !WP_CLI) {
530 + // translators: %s is the path to advanced-cache.php folder
363 531 $message .= "\n\n1. ".sprintf(__('Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
364 532 $message .= "\n2. ".__('Edit or create a file with the name advanced-cache.php', 'wp-optimize');
365 533 $message .= "\n3. ".__('Copy and paste the following lines into the file:', 'wp-optimize');
366 534 }
@@ -379,19 +547,21 @@
379 547 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
380 548 return $already_ran_enable;
381 549 }
382 550
383 - $already_ran_enable = true;
384 -
385 - return true;
551 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
552 + return $already_ran_enable;
386 553 }
387 554
388 555 /**
389 556 * Disables page cache
390 557 *
391 - * @return bool - true on success, false otherwise
558 + * @param bool $keep_option_enabled - Whether to keep the option `enable_page_caching` enabled or not. This is useful when we want to disable the cache temporarily, e.g. when deactivating the plugin, but we want to keep the option enabled so that it can be re-enabled when the plugin is reactivated.
559 + * @return bool|WP_Error - true on success or WP_Error on failure.
392 560 */
393 - public function disable() {
561 + public function disable($keep_option_enabled = false) {
562 + global $is_apache;
563 +
394 564 $ret = true;
395 565
396 566 $advanced_cache_file = $this->get_advanced_cache_filename();
397 567
@@ -397,15 +567,8 @@
397 567
398 568 // 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.
399 569 $disabled_wp_config = $this->write_wp_config(false);
400 570 if (!$disabled_wp_config) {
401 - $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
402 - $action = "deactivate_".$plugin_basename."/wp-optimize.php";
403 - if (current_action() === $action) {
404 - $cache_config = WPO_Cache_Config::instance()->config;
405 - $cache_config['enable_page_caching'] = false;
406 - WPO_Cache_Config::instance()->update($cache_config, true);
407 - }
408 571 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
409 572 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
410 573 }
411 574
@@ -412,9 +575,9 @@
412 575 $disabled_advanced_cache = true;
413 576 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
414 577 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
415 578 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
416 - if (file_exists($advanced_cache_file) && (!unlink($advanced_cache_file) && false === file_put_contents($advanced_cache_file, "<?php\n// WP-Optimize: page cache disabled"))) {
579 + if (file_exists($advanced_cache_file) && (!WP_Optimize_Utils::wp_delete_file($advanced_cache_file) && false === file_put_contents($advanced_cache_file, "<?php\n// WP-Optimize: page cache disabled"))) {
417 580 $disabled_advanced_cache = false;
418 581 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
419 582 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
420 583 }
@@ -425,14 +588,55 @@
425 588 $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'));
426 589 }
427 590
428 591 // Delete cache to avoid stale cache on next activation
429 - $this->purge();
592 + $is_cache_purged = $this->purge();
593 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
430 594
595 + // Delete `.htaccess` rules to serve cached files
596 + if ($is_apache && apply_filters('wpo_serve_cache_via_htaccess', false)) {
597 + $this->update_serve_cache_rules_htaccess_section(false);
598 + }
599 +
600 + // Unschedule cron job to purge old cache
601 + wp_clear_scheduled_hook('wpo_purge_old_cache');
602 +
603 + if (!is_wp_error($ret)) {
604 + wp_clear_scheduled_hook('wpo_prune_cache_logs');
605 +
606 + if (!$keep_option_enabled) {
607 + $ret = $this->update_page_cache_enabled_state(false);
608 + }
609 + }
610 +
611 + if (!is_wp_error($ret)) {
612 + do_action('wpo_page_cache_disabled');
613 + }
614 +
431 615 return $ret;
432 616 }
433 617
618 + /**
619 + * Update page cache enabled state.
620 + * Only updates `enable_page_caching` if the option exists and the value has changed.
621 + *
622 + * @param bool $enabled Whether page caching should be marked as enabled.
623 + *
624 + * @return true|WP_Error true on success, WP_Error on failure.
625 + */
626 + private function update_page_cache_enabled_state($enabled) {
627 + $config = WPO_Cache_Config::instance();
628 + $cache_config = $config->get();
434 629
630 + if (isset($cache_config['enable_page_caching']) && $cache_config['enable_page_caching'] === $enabled) {
631 + return true;
632 + }
633 +
634 + $cache_config['enable_page_caching'] = $enabled;
635 +
636 + return $config->update($cache_config, true);
637 + }
638 +
435 639 /**
436 640 * Purges the cache
437 641 *
438 642 * @return bool - true on success, false otherwise
@@ -474,12 +678,12 @@
474 678 if (0 === $page_cache_length) return $log;
475 679 $expires = time() - $page_cache_length;
476 680 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
477 681 // get all directories that are a direct child of current directory
478 - if (is_dir($cache_folder) && is_writable($cache_folder)) {
682 + if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
479 683 if ($handle = opendir($cache_folder)) {
480 684 while (false !== ($d = readdir($handle))) {
481 - if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
685 + if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
482 686 continue;
483 687 }
484 688
485 689 if ($this->is_front_page_cache($d)) {
@@ -484,9 +688,9 @@
484 688
485 689 if ($this->is_front_page_cache($d)) {
486 690 $modified_time = (int) filemtime("$cache_folder/$d");
487 691 if ($modified_time <= $expires) {
488 - unlink("$cache_folder/$d");
692 + wp_delete_file("$cache_folder/$d");
489 693 }
490 694 continue;
491 695 }
492 696
@@ -498,9 +702,9 @@
498 702 $log[] = "checking if cache has expired - $d";
499 703 if ($modified_time <= $expires) {
500 704 $log[] = "deleting cache in $dir";
501 705 wpo_delete_files($dir, true);
502 - if (file_exists($dir)) rmdir($dir);
706 + if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
503 707 }
504 708 }
505 709 closedir($handle);
506 710 }
@@ -534,36 +738,40 @@
534 738 return true;
535 739 }
536 740
537 741 /**
538 - * Check if cache is enabled and working
742 + * Checks whether page caching is turned on in settings.
539 743 *
540 - * @return bool - true on success, false otherwise
744 + * @return bool true if the `enable_page_caching` option is set & environment is ready to cache, false otherwise.
541 745 */
542 746 public function is_enabled() {
747 + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
748 + }
543 749
544 - if (!$this->config->get_option('enable_page_caching')) return false;
750 + /**
751 + * Checks whether the cache delivery chain is actually intact and able to serve cached pages.
752 + *
753 + * Verifies the WP_CACHE and WPO_ADVANCED_CACHE constants are defined and true, and that the cache config file exists on disk.
754 + *
755 + * @return bool True if the delivery chain is functional, false otherwise.
756 + */
757 + private function is_environment_functional() {
758 + $is_cli_or_ajax = (defined('WP_CLI') && WP_CLI) || (defined('DOING_AJAX') && DOING_AJAX);
545 759
546 - if (!defined('WP_CACHE') || !WP_CACHE) {
547 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
548 - $this->log("WP_CACHE constant is not present in wp-config.php");
549 - return false;
550 - }
760 + if (!$is_cli_or_ajax && (!defined('WP_CACHE') || !WP_CACHE)) {
761 + $this->file_log("WP_CACHE constant is not present in wp-config.php");
762 + return false;
551 763 }
552 764
553 - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
554 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
555 - $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
556 - return false;
557 - }
765 + if (!$is_cli_or_ajax && (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE)) {
766 + $this->file_log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
767 + return false;
558 768 }
559 769
560 770 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
561 - if (!file_exists($config_file)) {
562 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
563 - $this->log("Cache config file $config_file is not present");
564 - return false;
565 - }
771 + if (!$is_cli_or_ajax && !file_exists($config_file)) {
772 + $this->file_log("Cache config file $config_file is not present");
773 + return false;
566 774 }
567 775
568 776 return true;
569 777 }
@@ -570,23 +778,26 @@
570 778
571 779 /**
572 780 * Create the folder structure needed for cache to work
573 781 *
574 - * @return bool - true on success, false otherwise
782 + * @return bool | WP_Error - true on success, false otherwise
575 783 */
576 784 public function create_folders() {
577 785 $permissions_str = __('Please check your file permissions.', 'wp-optimize');
578 786
579 787 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
788 + // translators: %s is a directory name
580 789 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);
581 790 }
582 791
583 792 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
793 + // translators: %s is a directory name
584 794 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);
585 795 }
586 796
587 797 if (!is_dir(WPO_CACHE_FILES_DIR)) {
588 798 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
799 + // translators: %s is a directory name
589 800 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);
590 801 } else {
591 802 wpo_disable_cache_directories_viewing();
592 803 }
@@ -617,9 +828,10 @@
617 828 $cache_path = '/wpo-cache';
618 829 $cache_files_path = '/cache/wpo-cache';
619 830 $cache_extensions_path = WPO_CACHE_EXT_DIR;
620 831 $wpo_version = WPO_VERSION;
621 - $wpo_home_url = trailingslashit(home_url());
832 + $wpo_home_url = trailingslashit(network_home_url());
833 + $abspath = ABSPATH;
622 834
623 835 // CS does not like heredoc
624 836 // phpcs:disable
625 837 $this->advanced_cache_file_content = <<<EOF
@@ -626,9 +838,9 @@
626 838 <?php
627 839
628 840 if (!defined('ABSPATH')) die('No direct access allowed');
629 841
630 -// WP-Optimize advanced-cache.php (written by version: $wpo_version) (do not change this line, it is used for correctness checks)
842 +// 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)
631 843
632 844 if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);
633 845
634 846 \$possible_plugin_locations = array(
@@ -699,15 +911,15 @@
699 911 }
700 912
701 913 // check if we can't write the advanced cache file
702 914 // case 1: the directory is read-only and the file doesn't exist
703 - if (!is_file($advanced_cache_filename) && !is_writable(dirname($advanced_cache_filename))) {
915 + if (!is_file($advanced_cache_filename) && !wp_is_writable(dirname($advanced_cache_filename))) {
704 916 $this->advanced_cache_file_writing_error = true;
705 917 return false;
706 918 }
707 919
708 920 // case 2: the file already exists but it's read-only
709 - if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
921 + if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
710 922 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
711 923 $this->advanced_cache_file_writing_error = true;
712 924 return false;
713 925 } else {
@@ -738,11 +950,20 @@
738 950 }
739 951 }
740 952
741 953 // from 3.0.17 we use more secure way to store cache config files and need update advanced-cache.php
954 + // if the site url or folder changed we also update advanced-cache.php
742 955 $advanced_cache_current_version = $this->get_advanced_cache_version();
743 - if ($advanced_cache_current_version && version_compare($advanced_cache_current_version, $this->_minimum_advanced_cache_file_version, '>=')) return;
956 + $is_the_site_url_or_folder_changed = $this->is_the_site_url_or_folder_changed();
744 957
958 + 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;
959 +
960 + // Purge the cache when the site url or folder changed.
961 + if ($is_the_site_url_or_folder_changed) {
962 + $is_cache_purged = $this->purge();
963 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
964 + }
965 +
745 966 if (!$this->write_advanced_cache()) {
746 967 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
747 968 } else {
748 969 $this->update_cache_config();
@@ -749,8 +970,31 @@
749 970 }
750 971 }
751 972
752 973 /**
974 + * Check if the site url or folder doesn't equal to values from advanced-cache.php
975 + *
976 + * @return bool
977 + */
978 + private function is_the_site_url_or_folder_changed() {
979 + if (!is_file($this->get_advanced_cache_filename())) return false;
980 +
981 + $content = file_get_contents($this->get_advanced_cache_filename());
982 +
983 + if (false === $content) return false;
984 +
985 + if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\) (\(homeurl: (.+)\)) (\(abspath: (.+)\)) \(do not change/Ui', $content, $match)) {
986 + $wpo_home_url = trailingslashit(network_home_url());
987 + $abspath = ABSPATH;
988 + return ($wpo_home_url !== $match[3] || $abspath !== $match[5]);
989 + } elseif (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
990 + return true;
991 + }
992 +
993 + return false;
994 + }
995 +
996 + /**
753 997 * Show notification when advanced-cache.php could not be updated.
754 998 */
755 999 public function notice_advanced_cache_autoupdate_error() {
756 1000 $this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
@@ -795,8 +1039,13 @@
795 1039 if (!$config_path) {
796 1040 return false;
797 1041 }
798 1042
1043 + // Bail early if wp-config.php is not writable, to avoid a PHP warning.
1044 + if (!wp_is_writable($config_path)) {
1045 + return false;
1046 + }
1047 +
799 1048 $config_file_string = file_get_contents($config_path);
800 1049
801 1050 // Config file is empty. Maybe couldn't read it?
802 1051 if (empty($config_file_string)) {
@@ -849,9 +1098,9 @@
849 1098 }
850 1099 $errors = 0;
851 1100
852 1101 // First check wp-config.php.
853 - if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
1102 + if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
854 1103 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
855 1104 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
856 1105 }
857 1106
@@ -857,9 +1106,9 @@
857 1106
858 1107 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
859 1108
860 1109 // Now check wp-content. We need to be able to create files of the same user as this file.
861 - 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))) {
1110 + if ((!file_exists($advanced_cache_file) || false === strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) && !wp_is_writable($advanced_cache_file) && !wp_is_writable(untrailingslashit(WP_CONTENT_DIR))) {
862 1111 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
863 1112 $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'));
864 1113 $errors++;
865 1114 }
@@ -864,16 +1113,17 @@
864 1113 $errors++;
865 1114 }
866 1115
867 1116 if (file_exists(WPO_CACHE_FILES_DIR)) {
868 - if (!is_writable(WPO_CACHE_FILES_DIR)) {
1117 + if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
869 1118 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
1119 + // translators: %s is cache folder path
870 1120 $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));
871 1121 }
872 1122 }
873 1123
874 1124 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
875 - if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
1125 + if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
876 1126 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
877 1127 // If the config exists, only send a warning. Otherwise send an error.
878 1128 $type = 'warning';
879 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
@@ -879,8 +1129,9 @@
879 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
880 1130 $type = 'error';
881 1131 $errors++;
882 1132 }
1133 + // translators: %s is cache config folder path
883 1134 $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);
884 1135 }
885 1136 }
886 1137
@@ -895,9 +1146,9 @@
895 1146 * @param string $option Option name `permalink_structure`
896 1147 */
897 1148 public function update_option_permalink_structure($old_value, $value, $option) {
898 1149 $current_config = $this->config->get();
899 - if ($old_value != $value) {
1150 + if ($old_value !== $value) {
900 1151 $current_config[$option] = $value;
901 1152 $this->config->update($current_config, true);
902 1153 }
903 1154 }
@@ -903,14 +1154,16 @@
903 1154 }
904 1155
905 1156 /**
906 1157 * Update cache config. Used to support 3d party plugins.
1158 + *
1159 + * @return bool | WP_Error
907 1160 */
908 1161 public function update_cache_config() {
909 1162 // get current cache settings.
910 1163 $current_config = $this->config->get();
911 1164 // and call update to change if need cookies and query variable names.
912 - $this->config->update($current_config, true);
1165 + return $this->config->update($current_config);
913 1166 }
914 1167
915 1168 /**
916 1169 * Delete information about cache size.
@@ -915,18 +1168,29 @@
915 1168 /**
916 1169 * Delete information about cache size.
917 1170 */
918 1171 public function delete_cache_size_information() {
1172 + //not removing delete_transient so it clear this key from option table
919 1173 delete_transient('wpo_get_cache_size');
1174 + delete_site_transient('wpo_get_cache_size');
920 1175 }
921 1176
922 1177 /**
1178 + * Update cache cleared time
1179 + */
1180 + public function update_cache_cleared_time(): void {
1181 + $config = $this->config->get();
1182 + $config['last_cleared'] = time();
1183 + $this->config->update($config);
1184 + }
1185 +
1186 + /**
923 1187 * Get current cache size.
924 1188 *
925 1189 * @return array
926 1190 */
927 1191 public function get_cache_size() {
928 - $cache_size = get_transient('wpo_get_cache_size');
1192 + $cache_size = get_site_transient('wpo_get_cache_size');
929 1193
930 1194 if (!empty($cache_size)) return $cache_size;
931 1195
932 1196 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
@@ -934,9 +1198,11 @@
934 1198 'size' => $infos['size'],
935 1199 'file_count' => $infos['file_count']
936 1200 );
937 1201
938 - set_transient('wpo_get_cache_size', $cache_size);
1202 + $expiration = apply_filters('wpo_cache_size_expiration', DAY_IN_SECONDS);
1203 + $expiration = is_numeric($expiration) ? (int) $expiration : DAY_IN_SECONDS;
1204 + set_site_transient('wpo_get_cache_size', $cache_size, $expiration);
939 1205
940 1206 return $cache_size;
941 1207 }
942 1208
@@ -946,39 +1212,12 @@
946 1212 * @param string $dir
947 1213 * @return array
948 1214 */
949 1215 private function get_dir_infos($dir) {
950 - $dir_size = 0;
951 - $file_count = 0;
1216 + $filtered = apply_filters('wpo_cache_size_files_to_ignore', $this->files_to_ignore);
1217 + $this->files_to_ignore = is_array($filtered) ? $filtered : array();
952 1218
953 - $handle = is_dir($dir) ? opendir($dir) : false;
954 -
955 - if (false === $handle) {
956 - return array('size' => 0, 'file_count' => 0);
957 - }
958 -
959 - $file = readdir($handle);
960 -
961 - while (false !== $file) {
962 -
963 - if ('.' != $file && '..' != $file) {
964 - $current_file = $dir.'/'.$file;
965 -
966 - if (is_dir($current_file)) {
967 - $sub_dir_infos = $this->get_dir_infos($current_file);
968 - $dir_size += $sub_dir_infos['size'];
969 - $file_count += $sub_dir_infos['file_count'];
970 - } elseif (is_file($current_file)) {
971 - $dir_size += filesize($current_file);
972 - $file_count++;
973 - }
974 - }
975 -
976 - $file = readdir($handle);
977 -
978 - }
979 -
980 - return array('size' => $dir_size, 'file_count' => $file_count);
1219 + return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
981 1220 }
982 1221
983 1222 /**
984 1223 * Returns the path to wp-config
@@ -1010,11 +1249,9 @@
1010 1249 * @param string $src
1011 1250 * @return boolean
1012 1251 */
1013 1252 public static function delete($src) {
1014 -
1015 1253 return wpo_delete_files($src);
1016 -
1017 1254 }
1018 1255
1019 1256 /**
1020 1257 * Delete cached files for specific url.
@@ -1024,18 +1261,49 @@
1024 1261 *
1025 1262 * @return bool
1026 1263 */
1027 1264 public static function delete_cache_by_url($url, $recursive = false) {
1028 - if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1265 + if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;
1029 1266
1030 1267 $path = self::get_full_path_from_url($url);
1031 1268
1032 1269 do_action('wpo_delete_cache_by_url', $url, $recursive);
1033 1270
1271 + do_action('wpo_preload_url', $url);
1272 +
1034 1273 return wpo_delete_files($path, $recursive);
1035 1274 }
1036 1275
1037 1276 /**
1277 + * Adds the URL to the preload list and hooks the preload task to shutdown
1278 + *
1279 + * @param string $url
1280 + * @return void
1281 + */
1282 + public function maybe_preload_url($url) {
1283 + if (!$this->should_auto_preload_purged_contents()) return;
1284 +
1285 + static $urls_preloaded = array();
1286 +
1287 + // Remove the query string from the URL
1288 + $url = preg_replace('/\?.*/', '', $url);
1289 +
1290 + if (!in_array($url, $urls_preloaded)) {
1291 + $urls_preloaded[] = $url;
1292 +
1293 + $preloader = WP_Optimize_Page_Cache_Preloader::instance();
1294 + $preloader->add_url_to_preload_list($url);
1295 + $url_task_creator = array($preloader, 'create_tasks_for_auto_preload_urls');
1296 +
1297 + $preloader->maybe_dont_close_browser_connection();
1298 +
1299 + if (!has_action('shutdown', $url_task_creator)) {
1300 + add_action('shutdown', $url_task_creator);
1301 + }
1302 + }
1303 + }
1304 +
1305 + /**
1038 1306 * Delete cached files for single post.
1039 1307 *
1040 1308 * @param integer $post_id The post ID
1041 1309 *
@@ -1057,9 +1325,9 @@
1057 1325 * @return bool
1058 1326 */
1059 1327 public static function really_delete_single_post_cache($post_id) {
1060 1328
1061 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1329 + if (!defined('WPO_CACHE_FILES_DIR')) return false;
1062 1330
1063 1331 $post_url = get_permalink($post_id);
1064 1332
1065 1333 $path = self::get_full_path_from_url($post_url);
@@ -1069,8 +1337,10 @@
1069 1337 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1070 1338
1071 1339 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1072 1340
1341 + do_action('wpo_preload_url', $post_url);
1342 +
1073 1343 return wpo_delete_files($path, $recursive);
1074 1344 }
1075 1345
1076 1346 /**
@@ -1083,42 +1353,15 @@
1083 1353 $homepage_url = get_home_url(get_current_blog_id());
1084 1354
1085 1355 $path = self::get_full_path_from_url($homepage_url);
1086 1356
1087 - do_action('wpo_delete_cache_by_url', $homepage_url, false);
1357 + $recursive = false;
1088 1358
1089 - wpo_delete_files($path, false);
1090 - }
1359 + do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);
1091 1360
1092 - /**
1093 - * Delete sitemap cache.
1094 - */
1095 - public static function delete_sitemap_cache() {
1096 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1361 + do_action('wpo_preload_url', $homepage_url);
1097 1362
1098 - $homepage_url = get_home_url(get_current_blog_id());
1099 -
1100 - $path = trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($homepage_url));
1101 -
1102 - if (!is_dir($path)) return;
1103 -
1104 - $handle = opendir($path);
1105 -
1106 - if (false !== $handle) {
1107 - $file = readdir($handle);
1108 -
1109 - while (false !== $file) {
1110 -
1111 - if ('.' != $file && '..' != $file && is_dir($path . $file) && preg_match('/.*sitemap.*\.xml/i', $file)) {
1112 - do_action('wpo_delete_cache_by_url', $path . $file, false);
1113 - wpo_delete_files($path . $file, true);
1114 - }
1115 -
1116 - $file = readdir($handle);
1117 - }
1118 - }
1119 -
1120 - closedir($handle);
1363 + wpo_delete_files($path, $recursive);
1121 1364 }
1122 1365
1123 1366 /**
1124 1367 * Delete feed from cache.
@@ -1136,8 +1379,10 @@
1136 1379 }
1137 1380
1138 1381 /**
1139 1382 * Delete post feed from cache.
1383 + *
1384 + * @param int $post_id Post ID
1140 1385 */
1141 1386 public static function delete_post_feed_cache($post_id) {
1142 1387 self::really_delete_post_feed_cache($post_id);
1143 1388
@@ -1145,8 +1390,10 @@
1145 1390 }
1146 1391
1147 1392 /**
1148 1393 * Really delete post feed from cache.
1394 + *
1395 + * @param int $post_id Post ID
1149 1396 */
1150 1397 public static function really_delete_post_feed_cache($post_id) {
1151 1398 if (!defined('WPO_CACHE_FILES_DIR')) return;
1152 1399
@@ -1175,9 +1422,9 @@
1175 1422
1176 1423 // delete empty comments dir from the cache
1177 1424 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1178 1425 $path = self::get_full_path_from_url($comments_url);
1179 -
1426 +
1180 1427 if (wpo_is_empty_dir($path)) {
1181 1428 wpo_delete_files($path, true);
1182 1429 }
1183 1430 }
@@ -1187,9 +1434,9 @@
1187 1434 *
1188 1435 * @param string $url
1189 1436 * @return string
1190 1437 */
1191 - private static function get_full_path_from_url($url) {
1438 + public static function get_full_path_from_url($url) {
1192 1439 return trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($url));
1193 1440 }
1194 1441
1195 1442 /**
@@ -1198,9 +1445,9 @@
1198 1445 * @return void
1199 1446 */
1200 1447 public function admin_init() {
1201 1448 // Maybe update the advanced cache.
1202 - if ((!defined('DOING_AJAX') || !DOING_AJAX) && current_user_can('update_plugins')) {
1449 + if ((!defined('DOING_AJAX') || !DOING_AJAX) && WP_Optimize()->current_user_can('update_plugins')) {
1203 1450 $this->maybe_update_advanced_cache();
1204 1451 $this->cron_activate();
1205 1452 }
1206 1453 }
@@ -1219,9 +1466,9 @@
1219 1466
1220 1467 /**
1221 1468 * Returns an instance of the current class, creates one if it doesn't exist
1222 1469 *
1223 - * @return object
1470 + * @return self
1224 1471 */
1225 1472 public static function instance() {
1226 1473 if (empty(self::$instance)) {
1227 1474 self::$instance = new self();
@@ -1232,19 +1479,19 @@
1232 1479
1233 1480 /**
1234 1481 * Update gmt_offset and timezone_string cache config value, used with hook `update_gmt_offset_timezone_string_config`.
1235 1482 *
1236 - * @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)
1237 - * @param {float|string} $new_value
1238 - * @param {string} $option gmt_offset|timezone_string
1483 + * @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)
1484 + * @param float | string $new_value
1485 + * @param string $option gmt_offset|timezone_string
1239 1486 *
1240 1487 * @return null
1241 1488 */
1242 1489 public function update_gmt_offset_timezone_string_config($old_value, $new_value, $option) {
1243 - if ('' == $new_value) return;
1490 + if ('' === $new_value) return;
1244 1491
1245 1492 $current_config = $this->config->get();
1246 - if ('timezone_string' == $option) {
1493 + if ('timezone_string' === $option) {
1247 1494 $timeZone = new DateTimeZone($new_value);
1248 1495 $dateTime = new DateTime("now");
1249 1496 $gmt_offset = $timeZone->getOffset($dateTime);
1250 1497 $current_config['gmt_offset'] = round($gmt_offset/3600, 1);
@@ -1364,9 +1611,9 @@
1364 1611 public function is_pagespeedninja_gzip_active() {
1365 1612 if (!class_exists('PagespeedNinja')) return false;
1366 1613
1367 1614 $options = get_option('pagespeedninja_config');
1368 - $gzip = !empty($options) ? (bool) $options['psi_EnableGzipCompression'] && (bool) $options['html_gzip'] : false;
1615 + $gzip = isset($options['psi_EnableGzipCompression']) && isset($options['html_gzip']) && (bool) $options['psi_EnableGzipCompression'] && (bool) $options['html_gzip'];
1369 1616
1370 1617 return $gzip;
1371 1618 }
1372 1619
@@ -1373,9 +1620,9 @@
1373 1620 /**
1374 1621 * Output PageSpeed Ninja Gzip notice.
1375 1622 */
1376 1623 public function show_pagespeedninja_gzip_notice() {
1377 - 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>';
1624 + echo '<div id="wp-optimize-pagespeedninja-gzip-notice" class="error wpo-notice"><p><b>'.esc_html__('WP-Optimize:', 'wp-optimize').'</b> '.esc_html__('Please disable the feature "Gzip compression" in PageSpeed Ninja to prevent conflicts.', 'wp-optimize').'</p></div>';
1378 1625 }
1379 1626
1380 1627 /**
1381 1628 * Check if Far Future Expiration is active and GZIP compression option is enabled.
@@ -1385,9 +1632,9 @@
1385 1632 public function is_farfutureexpiration_gzip_active() {
1386 1633 if (!class_exists('farFutureExpiration')) return false;
1387 1634
1388 1635 $options = get_option('far_future_expiration_settings');
1389 - $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1636 + $gzip = !empty($options) && $options['enable_gzip'];
1390 1637
1391 1638 return $gzip;
1392 1639 }
1393 1640
@@ -1394,9 +1641,9 @@
1394 1641 /**
1395 1642 * Output Far Future Expiration Gzip notice.
1396 1643 */
1397 1644 public function show_farfutureexpiration_gzip_notice() {
1398 - 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>';
1645 + echo '<div id="wp-optimize-pagespeedninja-gzip-notice" class="error wpo-notice"><p><b>'.esc_html__('WP-Optimize:', 'wp-optimize').'</b> '.esc_html__('Please disable the feature "Gzip compression" in Far Future Expiration to prevent conflicts.', 'wp-optimize').'</p></div>';
1399 1646 }
1400 1647
1401 1648 /**
1402 1649 * This is a notice to show users that writing `advanced-cache.php` failed
@@ -1401,8 +1648,9 @@
1401 1648 /**
1402 1649 * This is a notice to show users that writing `advanced-cache.php` failed
1403 1650 */
1404 1651 public function show_admin_notice_advanced_cache() {
1652 + // translators: %s is advanced-cache.php file path
1405 1653 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1406 1654 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1407 1655 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1408 1656 }
@@ -1426,13 +1674,15 @@
1426 1674
1427 1675 $interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
1428 1676 $schedules['wpo_page_cache_preload_continue_interval'] = array(
1429 1677 'interval' => $interval,
1678 + // translators: %d is the number of minutes
1430 1679 'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
1431 1680 );
1432 1681
1433 1682 $schedules['wpo_use_cache_lifespan'] = array(
1434 1683 'interval' => $page_cache_length,
1684 + // translators: %s is the cache lifespan
1435 1685 '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'))
1436 1686 );
1437 1687
1438 1688 return $schedules;
@@ -1449,9 +1699,9 @@
1449 1699 }
1450 1700 }
1451 1701
1452 1702 /**
1453 - * May be regenerate cache config file, in case of migrations
1703 + * Maybe regenerate cache config file, in case of migrations
1454 1704 */
1455 1705 public function maybe_regenerate_cache_config_file() {
1456 1706 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1457 1707 if (!file_exists($config_file)) {
@@ -1457,8 +1707,163 @@
1457 1707 if (!file_exists($config_file)) {
1458 1708 $config = $this->config->get();
1459 1709 $this->config->write($config);
1460 1710 }
1711 + }
1712 +
1713 + /**
1714 + * Checks if the given cache directory is empty
1715 + *
1716 + * @param string $path The path to the cache directory.
1717 + * @return bool Returns true if the cache directory is empty, false otherwise.
1718 + */
1719 + public static function is_cache_empty($path) {
1720 + if (!is_dir($path)) return true;
1721 +
1722 + if ($handle = opendir($path)) {
1723 + while (false !== ($entry = readdir($handle))) {
1724 + if ("." === $entry || ".." === $entry) {
1725 + continue;
1726 + }
1727 +
1728 + $full_path = $path . '/' . $entry;
1729 + if (is_file($full_path)) {
1730 + closedir($handle);
1731 + return false;
1732 + }
1733 + }
1734 + closedir($handle);
1735 + return true;
1736 + }
1737 + return true;
1738 + }
1739 +
1740 + /**
1741 + * Remove query params from request url
1742 + *
1743 + * @param string $url The request url.
1744 + * @return string Returns url without query params.
1745 + */
1746 + private static function remove_query_params($url) {
1747 + $parsed_url = wp_parse_url($url);
1748 + if (!$parsed_url) return '';
1749 + return $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'];
1750 + }
1751 +
1752 + /**
1753 + * Check configuration for auto preloading after cache deletion
1754 + *
1755 + * @return bool
1756 + */
1757 + public function should_auto_preload_purged_contents() {
1758 +
1759 + if (!$this->is_enabled()) return false; // as Page Cache is not enabled we should not auto preload purged content.
1760 +
1761 + $wpo_cache = WP_Optimize()->get_page_cache();
1762 + $wpo_cache_options = $wpo_cache->config->get();
1763 +
1764 + return $wpo_cache_options['auto_preload_purged_contents'];
1765 + }
1766 +
1767 + /**
1768 + * Logging of interesting messages related to Cache Purging
1769 + *
1770 + * @param string $message
1771 + * @param int $post_id
1772 + */
1773 + public function file_log($message, $post_id = 0) {
1774 + if (!empty($post_id)) {
1775 + if (false !== strpos($message, '{{URL}}')) {
1776 + $message = str_replace('{{URL}}', get_permalink($post_id), $message);
1777 + }
1778 + if (false !== strpos($message, '{{title}}')) {
1779 + $message = str_replace('{{title}}', '"' . get_the_title($post_id) . '"', $message);
1780 + }
1781 + }
1782 + $this->file_logger->info($message);
1783 + }
1784 +
1785 + /**
1786 + * Prunes the log file
1787 + */
1788 + public function prune_cache_logs() {
1789 + $this->file_log("Pruning the Cache log file");
1790 + $this->file_logger->prune_logs();
1791 + }
1792 +
1793 + /**
1794 + * Update .htaccess file to serve cached files directly.
1795 + *
1796 + * @param bool $enable
1797 + */
1798 + public function update_serve_cache_rules_htaccess_section($enable) {
1799 + $htaccess = new WP_Optimize_Htaccess();
1800 +
1801 + $section_title = 'WP-Optimize rules to serve cached files';
1802 +
1803 + if ($enable) {
1804 + if (!$htaccess->is_commented_section_exists($section_title)) {
1805 + $htaccess->update_commented_section($this->prepare_apache_cache_handle_section(), $section_title, true);
1806 + $htaccess->write_file();
1807 + }
1808 + } elseif ($htaccess->is_commented_section_exists($section_title)) {
1809 + $htaccess->remove_commented_section($section_title);
1810 + $htaccess->write_file();
1811 + }
1812 +
1813 + }
1814 +
1815 + /**
1816 + * Get the .htaccess section with rules to serve cached files
1817 + *
1818 + * @return array
1819 + */
1820 + private function prepare_apache_cache_handle_section() {
1821 +
1822 + $site_root = $this->get_site_root_path();
1823 +
1824 + return array(
1825 + array(
1826 + '<IfModule mod_rewrite.c>',
1827 + 'RewriteEngine On',
1828 + 'RewriteCond %{REQUEST_METHOD} GET|HEAD',
1829 + 'RewriteCond %{QUERY_STRING} =""',
1830 + 'RewriteCond %{HTTP:Cookie} =""',
1831 + 'RewriteCond %{REQUEST_URI} !^/(wp-(?:admin|login|register|comments-post|cron|json))/ [NC]',
1832 + 'RewriteCond %{HTTP_USER_AGENT} !(android|blackberry|ipad|iphone|ipod|windows.phone|iEMobile|opera.mini|opera.mobile|webos|symbian|windows.mobile|kindle|googlebot.mobile) [NC]',
1833 + 'RewriteCond %{DOCUMENT_ROOT}'.$site_root.'wp-content/cache/wpo-cache/%{HTTP_HOST}%{REQUEST_URI}index.html -f',
1834 + 'RewriteRule ^(.*)$ '.$site_root.'wp-content/cache/wpo-cache/%{HTTP_HOST}%{REQUEST_URI}index.html [L]',
1835 + array(
1836 + '<IfModule mod_setenvif.c>',
1837 + 'SetEnvIf Request_URI "/wp-content/cache/wpo-cache/" FROM_CACHE=1',
1838 + '</IfModule>',
1839 + ),
1840 + array(
1841 + '<IfModule mod_headers.c>',
1842 + 'Header always set X-WP-Optimize-Cache-Header "Loaded from WP-Optimize cache" env=FROM_CACHE',
1843 + '</IfModule>',
1844 + ),
1845 + '</IfModule>',
1846 + ),
1847 + );
1848 + }
1849 +
1850 + /**
1851 + * Get relative site root path, if site is placed in the subdirectory then it returns /subdirectory otherwise /
1852 + *
1853 + * @param string $default
1854 + *
1855 + * @return string
1856 + */
1857 + private function get_site_root_path($default = '/') {
1858 + $site_root = wp_parse_url(site_url());
1859 + if (isset($site_root['path'])) {
1860 + $site_root = trailingslashit($site_root['path']);
1861 + } else {
1862 + $site_root = $default;
1863 + }
1864 +
1865 + return $site_root;
1461 1866 }
1462 1867 }
1463 1868
1464 1869 endif;