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 +501 -246 3.5.0 → 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,31 +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'));
190 + }
130 191
131 - // Auto preload feature is commented out due to possible regression in v3.5.0
132 - // add_action('wpo_delete_cache_by_url', array($this, 'maybe_preload_url'), 10, 2);
133 - // add_action('wpo_maybe_preload_url', array($this, 'maybe_preload_url'), 10, 2);
192 + /**
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
199 + */
200 + public function should_cache_page() {
201 +
202 + if (!$this->is_enabled()) return false;
203 +
204 + return wpo_can_serve_from_cache();
134 205 }
135 206
136 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 + /**
137 245 * Activate cron job when the cache is enabled for deleting expired cache files
138 246 */
139 247 public function cron_activate() {
140 - $page_cache_length = $this->config->get_option('page_cache_length');
141 248 if ($this->is_enabled()) {
142 - if (!wp_next_scheduled('wpo_purge_old_cache')) {
143 - wp_schedule_event(time() + (false === $page_cache_length ? '86400' : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
144 - }
249 + $this->schedule_purge_old_cache();
145 250 } else {
146 - wp_clear_scheduled_hook('wpo_purge_old_cache');
251 + $this->unschedule_purge_old_cache();
147 252 }
148 253 }
149 254
150 255 /**
256 + * Schedules events to purge old cache
257 + *
258 + * @return void
259 + */
260 + private function schedule_purge_old_cache() {
261 + $page_cache_length = $this->config->get_option('page_cache_length');
262 +
263 + if (!wp_next_scheduled('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');
265 + }
266 + if (!wp_next_scheduled('wpo_prune_cache_logs')) {
267 + wp_schedule_event(time(), 'weekly', 'wpo_prune_cache_logs');
268 + }
269 + }
270 +
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 + /**
151 282 * Do required actions on activate/deactivate any plugin.
152 283 */
153 284 public function activate_deactivate_plugin() {
154 285
@@ -155,9 +286,11 @@
155 286 wp_clear_scheduled_hook('wpo_purge_old_cache');
156 287
157 288 $this->update_cache_config();
158 289
159 - $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");
160 293 }
161 294
162 295 /**
163 296 * Check if current user can purge cache.
@@ -165,15 +298,10 @@
165 298 * @return bool
166 299 */
167 300 public function can_purge_cache() {
168 301 if (!$this->is_enabled()) return false;
169 - $required_capability = is_multisite() ? 'manage_network_options' : 'manage_options';
170 302
171 - if (WP_Optimize::is_premium()) {
172 - return current_user_can($required_capability) || WP_Optimize_Premium()->can_purge_the_cache();
173 - } else {
174 - return current_user_can($required_capability);
175 - }
303 + return WP_Optimize_Utils::current_user_can_purge_cache();
176 304 }
177 305
178 306 /**
179 307 * Add Purge from cache in admin bar.
@@ -184,12 +312,13 @@
184 312 public function admin_bar_purge_cache($menu_items) {
185 313 global $pagenow;
186 314 if (!$this->can_purge_cache()) return $menu_items;
187 315
188 - $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();
189 317
190 318 $cache_size = $this->get_cache_size();
191 319 $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
320 + // translators: %d is the number of files in the cache
192 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>';
193 322
194 323 $menu_items[] = array(
195 324 'id' => 'wpo_cache_stats',
@@ -209,9 +338,9 @@
209 338 ),
210 339 'parent' => 'wpo_purge_cache',
211 340 );
212 341
213 - if (!is_admin() || 'post.php' == $pagenow) {
342 + if (!is_admin() || 'post.php' === $pagenow) {
214 343 $menu_items[] = array(
215 344 'id' => 'wpo_purge_this_page_cache',
216 345 'title' => __('Purge cache for this page', 'wp-optimize'),
217 346 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
@@ -233,22 +362,38 @@
233 362
234 363 if (!$this->can_purge_cache()) return;
235 364
236 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
237 368 if (isset($_GET['wpo_single_page_cache_purged'])) {
238 - $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';
239 371 } else {
240 - $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';
241 374 }
375 + // phpcs:enable
242 376
243 - add_action('admin_notices', array($this, $notice_function));
377 + if (is_admin()) {
378 + $admin_notices_hook = is_network_admin() ? 'network_admin_notices' : 'admin_notices';
244 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 +
245 390 return;
246 391 }
247 392
248 393 if (!isset($_GET['_wpo_purge'])) return;
249 394
250 - 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')) {
251 396 $success = false;
252 397
253 398 if (is_admin()) {
254 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
@@ -253,55 +398,56 @@
253 398 if (is_admin()) {
254 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
255 400 if ($post > 0) {
256 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);
257 403 }
258 404 } else {
259 - $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__);
260 408 }
261 409
262 - // remove nonce from url and reload page.
263 - 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);
264 415 exit;
265 416
266 - } 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')) {
267 418 $success = self::purge();
419 + $this->maybe_set_preload_cron_job();
420 + if ($success) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
268 421
269 - // remove nonce from url and reload page.
270 - wp_redirect(add_query_arg('wpo_all_pages_cache_purged', $success, remove_query_arg('_wpo_purge')));
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);
271 427 exit;
272 428 }
273 429 }
274 430
275 431 /**
276 - * 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
277 438 */
278 - public function notice_purge_single_page_cache_success() {
279 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
280 - }
439 + private function maybe_set_preload_cron_job() {
440 + $wpo_page_cache_preloader = WP_Optimize_Page_Cache_Preloader::instance();
281 441
282 - /**
283 - * Show notification when page cache wasn't purged.
284 - */
285 - public function notice_purge_single_page_cache_error() {
286 - $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 + }
287 447 }
288 -
448 +
289 449 /**
290 - * Show notification when all pages cache purged successfully.
291 - */
292 - public function notice_purge_all_pages_cache_success() {
293 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
294 - }
295 -
296 - /**
297 - * Show notification when all pages cache wasn't purged.
298 - */
299 - public function notice_purge_all_pages_cache_error() {
300 - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
301 - }
302 -
303 - /**
304 450 * Show notification in WordPress admin.
305 451 *
306 452 * @param string $message HTML (no further escaping is performed)
307 453 * @param string $type error, warning, success, or info
@@ -314,10 +460,10 @@
314 460 window.addEventListener('load', function() {
315 461 (function(wp) {
316 462 if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
317 463 wp.data.dispatch('core/notices').createNotice(
318 - '<?php echo $type; ?>',
319 - '<?php echo $message; ?>',
464 + '<?php echo esc_html($type); ?>',
465 + '<?php echo wp_kses_post($message); ?>',
320 466 {
321 467 isDismissible: true,
322 468 }
323 469 );
@@ -325,10 +471,10 @@
325 471 })(window.wp);
326 472 });
327 473 </script>
328 474 <?php else : ?>
329 - <div class="notice wpo-notice notice-<?php echo $type; ?> is-dismissible">
330 - <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>
331 477 </div>
332 478 <?php
333 479 endif;
334 480 }
@@ -340,8 +486,9 @@
340 486 *
341 487 * @return WP_Error|bool - true on success, error otherwise
342 488 */
343 489 public function enable($force_enable = false) {
490 + global $is_apache;
344 491 static $already_ran_enable = false;
345 492
346 493 if ($already_ran_enable) return $already_ran_enable;
347 494
@@ -362,11 +509,18 @@
362 509 }
363 510
364 511 $this->maybe_regenerate_cache_config_file();
365 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 +
366 520 if (!$force_enable) {
367 - $already_ran_enable = true;
368 - return true;
521 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
522 + return $already_ran_enable;
369 523 }
370 524
371 525 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
372 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
@@ -372,8 +526,9 @@
372 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
373 527 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
374 528
375 529 if (!defined('WP_CLI') || !WP_CLI) {
530 + // translators: %s is the path to advanced-cache.php folder
376 531 $message .= "\n\n1. ".sprintf(__('Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
377 532 $message .= "\n2. ".__('Edit or create a file with the name advanced-cache.php', 'wp-optimize');
378 533 $message .= "\n3. ".__('Copy and paste the following lines into the file:', 'wp-optimize');
379 534 }
@@ -392,19 +547,21 @@
392 547 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
393 548 return $already_ran_enable;
394 549 }
395 550
396 - $already_ran_enable = true;
397 -
398 - return true;
551 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
552 + return $already_ran_enable;
399 553 }
400 554
401 555 /**
402 556 * Disables page cache
403 557 *
404 - * @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.
405 560 */
406 - public function disable() {
561 + public function disable($keep_option_enabled = false) {
562 + global $is_apache;
563 +
407 564 $ret = true;
408 565
409 566 $advanced_cache_file = $this->get_advanced_cache_filename();
410 567
@@ -410,15 +567,8 @@
410 567
411 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.
412 569 $disabled_wp_config = $this->write_wp_config(false);
413 570 if (!$disabled_wp_config) {
414 - $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
415 - $action = "deactivate_".$plugin_basename."/wp-optimize.php";
416 - if (current_action() === $action) {
417 - $cache_config = WPO_Cache_Config::instance()->config;
418 - $cache_config['enable_page_caching'] = false;
419 - WPO_Cache_Config::instance()->update($cache_config, true);
420 - }
421 571 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
422 572 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
423 573 }
424 574
@@ -425,9 +575,9 @@
425 575 $disabled_advanced_cache = true;
426 576 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
427 577 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
428 578 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
429 - 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"))) {
430 580 $disabled_advanced_cache = false;
431 581 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
432 582 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
433 583 }
@@ -438,17 +588,55 @@
438 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'));
439 589 }
440 590
441 591 // Delete cache to avoid stale cache on next activation
442 - $this->purge();
592 + $is_cache_purged = $this->purge();
593 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
443 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 +
444 600 // Unschedule cron job to purge old cache
445 601 wp_clear_scheduled_hook('wpo_purge_old_cache');
446 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 +
447 615 return $ret;
448 616 }
449 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();
450 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 +
451 639 /**
452 640 * Purges the cache
453 641 *
454 642 * @return bool - true on success, false otherwise
@@ -465,10 +653,8 @@
465 653 );
466 654 $is_deactivation_action = in_array(current_action(), $purge_actions, true);
467 655 if ($is_deactivation_action && !$this->should_purge) return false;
468 656
469 - if ('' != WPO_CACHE_FILES_DIR) do_action('wpo_maybe_preload_url', WPO_CACHE_FILES_DIR, true);
470 -
471 657 if (!self::delete(WPO_CACHE_FILES_DIR)) {
472 658 $this->log("The request to the filesystem to delete the cache failed");
473 659 return false;
474 660 }
@@ -492,12 +678,12 @@
492 678 if (0 === $page_cache_length) return $log;
493 679 $expires = time() - $page_cache_length;
494 680 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
495 681 // get all directories that are a direct child of current directory
496 - if (is_dir($cache_folder) && is_writable($cache_folder)) {
682 + if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
497 683 if ($handle = opendir($cache_folder)) {
498 684 while (false !== ($d = readdir($handle))) {
499 - if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
685 + if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
500 686 continue;
501 687 }
502 688
503 689 if ($this->is_front_page_cache($d)) {
@@ -502,9 +688,9 @@
502 688
503 689 if ($this->is_front_page_cache($d)) {
504 690 $modified_time = (int) filemtime("$cache_folder/$d");
505 691 if ($modified_time <= $expires) {
506 - unlink("$cache_folder/$d");
692 + wp_delete_file("$cache_folder/$d");
507 693 }
508 694 continue;
509 695 }
510 696
@@ -516,9 +702,9 @@
516 702 $log[] = "checking if cache has expired - $d";
517 703 if ($modified_time <= $expires) {
518 704 $log[] = "deleting cache in $dir";
519 705 wpo_delete_files($dir, true);
520 - if (file_exists($dir)) rmdir($dir);
706 + if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
521 707 }
522 708 }
523 709 closedir($handle);
524 710 }
@@ -552,36 +738,40 @@
552 738 return true;
553 739 }
554 740
555 741 /**
556 - * Check if cache is enabled and working
742 + * Checks whether page caching is turned on in settings.
557 743 *
558 - * @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.
559 745 */
560 746 public function is_enabled() {
747 + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
748 + }
561 749
562 - 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);
563 759
564 - if (!defined('WP_CACHE') || !WP_CACHE) {
565 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
566 - $this->log("WP_CACHE constant is not present in wp-config.php");
567 - return false;
568 - }
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;
569 763 }
570 764
571 - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
572 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
573 - $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
574 - return false;
575 - }
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;
576 768 }
577 769
578 770 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
579 - if (!file_exists($config_file)) {
580 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
581 - $this->log("Cache config file $config_file is not present");
582 - return false;
583 - }
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;
584 774 }
585 775
586 776 return true;
587 777 }
@@ -588,23 +778,26 @@
588 778
589 779 /**
590 780 * Create the folder structure needed for cache to work
591 781 *
592 - * @return bool - true on success, false otherwise
782 + * @return bool | WP_Error - true on success, false otherwise
593 783 */
594 784 public function create_folders() {
595 785 $permissions_str = __('Please check your file permissions.', 'wp-optimize');
596 786
597 787 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
788 + // translators: %s is a directory name
598 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);
599 790 }
600 791
601 792 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
793 + // translators: %s is a directory name
602 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);
603 795 }
604 796
605 797 if (!is_dir(WPO_CACHE_FILES_DIR)) {
606 798 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
799 + // translators: %s is a directory name
607 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);
608 801 } else {
609 802 wpo_disable_cache_directories_viewing();
610 803 }
@@ -635,9 +828,9 @@
635 828 $cache_path = '/wpo-cache';
636 829 $cache_files_path = '/cache/wpo-cache';
637 830 $cache_extensions_path = WPO_CACHE_EXT_DIR;
638 831 $wpo_version = WPO_VERSION;
639 - $wpo_home_url = trailingslashit(home_url());
832 + $wpo_home_url = trailingslashit(network_home_url());
640 833 $abspath = ABSPATH;
641 834
642 835 // CS does not like heredoc
643 836 // phpcs:disable
@@ -718,15 +911,15 @@
718 911 }
719 912
720 913 // check if we can't write the advanced cache file
721 914 // case 1: the directory is read-only and the file doesn't exist
722 - 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))) {
723 916 $this->advanced_cache_file_writing_error = true;
724 917 return false;
725 918 }
726 919
727 920 // case 2: the file already exists but it's read-only
728 - if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
921 + if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
729 922 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
730 923 $this->advanced_cache_file_writing_error = true;
731 924 return false;
732 925 } else {
@@ -765,9 +958,10 @@
765 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;
766 959
767 960 // Purge the cache when the site url or folder changed.
768 961 if ($is_the_site_url_or_folder_changed) {
769 - $this->purge();
962 + $is_cache_purged = $this->purge();
963 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
770 964 }
771 965
772 966 if (!$this->write_advanced_cache()) {
773 967 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
@@ -788,11 +982,11 @@
788 982
789 983 if (false === $content) return false;
790 984
791 985 if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\) (\(homeurl: (.+)\)) (\(abspath: (.+)\)) \(do not change/Ui', $content, $match)) {
792 - $wpo_home_url = trailingslashit(home_url());
986 + $wpo_home_url = trailingslashit(network_home_url());
793 987 $abspath = ABSPATH;
794 - return ($wpo_home_url != $match[3] || $abspath != $match[5]);
988 + return ($wpo_home_url !== $match[3] || $abspath !== $match[5]);
795 989 } elseif (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
796 990 return true;
797 991 }
798 992
@@ -845,8 +1039,13 @@
845 1039 if (!$config_path) {
846 1040 return false;
847 1041 }
848 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 +
849 1048 $config_file_string = file_get_contents($config_path);
850 1049
851 1050 // Config file is empty. Maybe couldn't read it?
852 1051 if (empty($config_file_string)) {
@@ -899,9 +1098,9 @@
899 1098 }
900 1099 $errors = 0;
901 1100
902 1101 // First check wp-config.php.
903 - if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
1102 + if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
904 1103 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
905 1104 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
906 1105 }
907 1106
@@ -907,9 +1106,9 @@
907 1106
908 1107 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
909 1108
910 1109 // Now check wp-content. We need to be able to create files of the same user as this file.
911 - 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))) {
912 1111 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
913 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'));
914 1113 $errors++;
915 1114 }
@@ -914,16 +1113,17 @@
914 1113 $errors++;
915 1114 }
916 1115
917 1116 if (file_exists(WPO_CACHE_FILES_DIR)) {
918 - if (!is_writable(WPO_CACHE_FILES_DIR)) {
1117 + if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
919 1118 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
1119 + // translators: %s is cache folder path
920 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));
921 1121 }
922 1122 }
923 1123
924 1124 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
925 - if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
1125 + if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
926 1126 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
927 1127 // If the config exists, only send a warning. Otherwise send an error.
928 1128 $type = 'warning';
929 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
@@ -929,8 +1129,9 @@
929 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
930 1130 $type = 'error';
931 1131 $errors++;
932 1132 }
1133 + // translators: %s is cache config folder path
933 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);
934 1135 }
935 1136 }
936 1137
@@ -945,9 +1146,9 @@
945 1146 * @param string $option Option name `permalink_structure`
946 1147 */
947 1148 public function update_option_permalink_structure($old_value, $value, $option) {
948 1149 $current_config = $this->config->get();
949 - if ($old_value != $value) {
1150 + if ($old_value !== $value) {
950 1151 $current_config[$option] = $value;
951 1152 $this->config->update($current_config, true);
952 1153 }
953 1154 }
@@ -954,9 +1155,9 @@
954 1155
955 1156 /**
956 1157 * Update cache config. Used to support 3d party plugins.
957 1158 *
958 - * @return {boolean|WP_Error}
1159 + * @return bool | WP_Error
959 1160 */
960 1161 public function update_cache_config() {
961 1162 // get current cache settings.
962 1163 $current_config = $this->config->get();
@@ -967,18 +1168,29 @@
967 1168 /**
968 1169 * Delete information about cache size.
969 1170 */
970 1171 public function delete_cache_size_information() {
1172 + //not removing delete_transient so it clear this key from option table
971 1173 delete_transient('wpo_get_cache_size');
1174 + delete_site_transient('wpo_get_cache_size');
972 1175 }
973 1176
974 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 + /**
975 1187 * Get current cache size.
976 1188 *
977 1189 * @return array
978 1190 */
979 1191 public function get_cache_size() {
980 - $cache_size = get_transient('wpo_get_cache_size');
1192 + $cache_size = get_site_transient('wpo_get_cache_size');
981 1193
982 1194 if (!empty($cache_size)) return $cache_size;
983 1195
984 1196 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
@@ -986,9 +1198,11 @@
986 1198 'size' => $infos['size'],
987 1199 'file_count' => $infos['file_count']
988 1200 );
989 1201
990 - 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);
991 1205
992 1206 return $cache_size;
993 1207 }
994 1208
@@ -998,39 +1212,12 @@
998 1212 * @param string $dir
999 1213 * @return array
1000 1214 */
1001 1215 private function get_dir_infos($dir) {
1002 - $dir_size = 0;
1003 - $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();
1004 1218
1005 - $handle = is_dir($dir) ? opendir($dir) : false;
1006 -
1007 - if (false === $handle) {
1008 - return array('size' => 0, 'file_count' => 0);
1009 - }
1010 -
1011 - $file = readdir($handle);
1012 -
1013 - while (false !== $file) {
1014 -
1015 - if ('.' != $file && '..' != $file) {
1016 - $current_file = $dir.'/'.$file;
1017 -
1018 - if (is_dir($current_file)) {
1019 - $sub_dir_infos = $this->get_dir_infos($current_file);
1020 - $dir_size += $sub_dir_infos['size'];
1021 - $file_count += $sub_dir_infos['file_count'];
1022 - } elseif (is_file($current_file)) {
1023 - $dir_size += filesize($current_file);
1024 - $file_count++;
1025 - }
1026 - }
1027 -
1028 - $file = readdir($handle);
1029 -
1030 - }
1031 -
1032 - return array('size' => $dir_size, 'file_count' => $file_count);
1219 + return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
1033 1220 }
1034 1221
1035 1222 /**
1036 1223 * Returns the path to wp-config
@@ -1062,9 +1249,8 @@
1062 1249 * @param string $src
1063 1250 * @return boolean
1064 1251 */
1065 1252 public static function delete($src) {
1066 - do_action('wpo_maybe_preload_url', $src);
1067 1253 return wpo_delete_files($src);
1068 1254 }
1069 1255
1070 1256 /**
@@ -1075,104 +1261,46 @@
1075 1261 *
1076 1262 * @return bool
1077 1263 */
1078 1264 public static function delete_cache_by_url($url, $recursive = false) {
1079 - if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1265 + if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;
1080 1266
1081 1267 $path = self::get_full_path_from_url($url);
1082 1268
1083 1269 do_action('wpo_delete_cache_by_url', $url, $recursive);
1084 1270
1271 + do_action('wpo_preload_url', $url);
1272 +
1085 1273 return wpo_delete_files($path, $recursive);
1086 1274 }
1087 1275
1088 1276 /**
1089 - * Check if auto preload after purge is enabled, and create tasks accordingly
1277 + * Adds the URL to the preload list and hooks the preload task to shutdown
1090 1278 *
1091 - * @param string $url The full URL or path to the cached contents
1092 - * @param bool $recursive Scan subfolders and process
1279 + * @param string $url
1093 1280 * @return void
1094 1281 */
1095 - public function maybe_preload_url($url, $recursive = true) {
1282 + public function maybe_preload_url($url) {
1283 + if (!$this->should_auto_preload_purged_contents()) return;
1284 +
1096 1285 static $urls_preloaded = array();
1097 - if ($this->should_auto_preload_purged_contents()) {
1098 - if (filter_var($url, FILTER_VALIDATE_URL)) {
1099 - $src = self::get_full_path_from_url($url);
1100 - } else {
1101 - $src = $url;
1102 - }
1103 -
1104 - if ($recursive && is_dir($src)) {
1105 - $dir_handle = @opendir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1106 - if (false !== $dir_handle) {
1107 - while (false !== ($file = readdir($dir_handle))) {
1108 - if ('.' == $file || '..' == $file) {
1109 - continue;
1110 - }
1111 -
1112 - $full_path = $src . '/' . $file;
1113 -
1114 - $this->maybe_preload_url($full_path, $recursive);
1115 - }
1116 - closedir($dir_handle);
1117 - }
1118 - } else {
1119 - $dirname = dirname($src);
1120 - $url = $this->convert_path_to_site_url($dirname);
1121 -
1122 - if (!in_array($url, $urls_preloaded)) {
1123 - $urls_preloaded[] = $url;
1124 1286
1125 - $preloader = WP_Optimize_Page_Cache_Preloader::instance();
1287 + // Remove the query string from the URL
1288 + $url = preg_replace('/\?.*/', '', $url);
1126 1289
1127 - $preloader->add_url_to_preload_list($url);
1290 + if (!in_array($url, $urls_preloaded)) {
1291 + $urls_preloaded[] = $url;
1128 1292
1129 - $url_task_creator = array($preloader, 'create_tasks_for_auto_preload_urls');
1130 - if (!has_action('shutdown', $url_task_creator)) {
1131 - add_action('shutdown', $url_task_creator);
1132 - }
1133 - }
1134 -
1135 - }
1136 - }
1137 - }
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');
1138 1296
1139 - /**
1140 - * Remove from the path everything up to `wpo-cache` folder so we can recreate the original URL that created the cache file
1141 - * Examples:
1142 - * Input: `/var/www/html/wpo/wp-content/cache/wpo-cache/localhost/wpo/wpo-cache`
1143 - * Output: `localhost/wpo/wpo-cache`
1144 - *
1145 - * @param string $src Full path to the cached file
1146 - * @return string
1147 - */
1148 - public function convert_path_to_site_url($src) {
1149 - $cache_dir = preg_replace('#^.*?wpo-cache[/]?#', '', $src);
1150 - $cache_path = explode('/', $cache_dir);
1297 + $preloader->maybe_dont_close_browser_connection();
1151 1298
1152 - // remove domain from the path
1153 - array_shift($cache_path);
1154 -
1155 - // strip sub-folder installation from path
1156 - $site_url_parts = wp_parse_url(site_url());
1157 - if ((false != $site_url_parts) && isset($site_url_parts['path'])) {
1158 - $site_url_path = explode("/", $site_url_parts['path']);
1159 -
1160 - if ('' == $site_url_path[0]) {
1161 - array_shift($site_url_path);
1299 + if (!has_action('shutdown', $url_task_creator)) {
1300 + add_action('shutdown', $url_task_creator);
1162 1301 }
1163 - } else {
1164 - $site_url_path = array();
1165 1302 }
1166 -
1167 - while (0 < count($site_url_path) && 0 < count($cache_path) && ($site_url_path[0] == $cache_path[0])) {
1168 - array_shift($site_url_path);
1169 - array_shift($cache_path);
1170 - }
1171 -
1172 - $clean_path = implode("/", $cache_path);
1173 -
1174 - return site_url($clean_path);
1175 1303 }
1176 1304
1177 1305 /**
1178 1306 * Delete cached files for single post.
@@ -1197,9 +1325,9 @@
1197 1325 * @return bool
1198 1326 */
1199 1327 public static function really_delete_single_post_cache($post_id) {
1200 1328
1201 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1329 + if (!defined('WPO_CACHE_FILES_DIR')) return false;
1202 1330
1203 1331 $post_url = get_permalink($post_id);
1204 1332
1205 1333 $path = self::get_full_path_from_url($post_url);
@@ -1209,8 +1337,10 @@
1209 1337 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1210 1338
1211 1339 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1212 1340
1341 + do_action('wpo_preload_url', $post_url);
1342 +
1213 1343 return wpo_delete_files($path, $recursive);
1214 1344 }
1215 1345
1216 1346 /**
@@ -1227,8 +1357,10 @@
1227 1357 $recursive = false;
1228 1358
1229 1359 do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);
1230 1360
1361 + do_action('wpo_preload_url', $homepage_url);
1362 +
1231 1363 wpo_delete_files($path, $recursive);
1232 1364 }
1233 1365
1234 1366 /**
@@ -1247,8 +1379,10 @@
1247 1379 }
1248 1380
1249 1381 /**
1250 1382 * Delete post feed from cache.
1383 + *
1384 + * @param int $post_id Post ID
1251 1385 */
1252 1386 public static function delete_post_feed_cache($post_id) {
1253 1387 self::really_delete_post_feed_cache($post_id);
1254 1388
@@ -1256,8 +1390,10 @@
1256 1390 }
1257 1391
1258 1392 /**
1259 1393 * Really delete post feed from cache.
1394 + *
1395 + * @param int $post_id Post ID
1260 1396 */
1261 1397 public static function really_delete_post_feed_cache($post_id) {
1262 1398 if (!defined('WPO_CACHE_FILES_DIR')) return;
1263 1399
@@ -1286,10 +1422,8 @@
1286 1422
1287 1423 // delete empty comments dir from the cache
1288 1424 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1289 1425 $path = self::get_full_path_from_url($comments_url);
1290 -
1291 - do_action('wpo_delete_cache_by_url', $comments_url, true);
1292 1426
1293 1427 if (wpo_is_empty_dir($path)) {
1294 1428 wpo_delete_files($path, true);
1295 1429 }
@@ -1311,9 +1445,9 @@
1311 1445 * @return void
1312 1446 */
1313 1447 public function admin_init() {
1314 1448 // Maybe update the advanced cache.
1315 - 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')) {
1316 1450 $this->maybe_update_advanced_cache();
1317 1451 $this->cron_activate();
1318 1452 }
1319 1453 }
@@ -1332,9 +1466,9 @@
1332 1466
1333 1467 /**
1334 1468 * Returns an instance of the current class, creates one if it doesn't exist
1335 1469 *
1336 - * @return object
1470 + * @return self
1337 1471 */
1338 1472 public static function instance() {
1339 1473 if (empty(self::$instance)) {
1340 1474 self::$instance = new self();
@@ -1345,19 +1479,19 @@
1345 1479
1346 1480 /**
1347 1481 * Update gmt_offset and timezone_string cache config value, used with hook `update_gmt_offset_timezone_string_config`.
1348 1482 *
1349 - * @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)
1350 - * @param {float|string} $new_value
1351 - * @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
1352 1486 *
1353 1487 * @return null
1354 1488 */
1355 1489 public function update_gmt_offset_timezone_string_config($old_value, $new_value, $option) {
1356 - if ('' == $new_value) return;
1490 + if ('' === $new_value) return;
1357 1491
1358 1492 $current_config = $this->config->get();
1359 - if ('timezone_string' == $option) {
1493 + if ('timezone_string' === $option) {
1360 1494 $timeZone = new DateTimeZone($new_value);
1361 1495 $dateTime = new DateTime("now");
1362 1496 $gmt_offset = $timeZone->getOffset($dateTime);
1363 1497 $current_config['gmt_offset'] = round($gmt_offset/3600, 1);
@@ -1477,9 +1611,9 @@
1477 1611 public function is_pagespeedninja_gzip_active() {
1478 1612 if (!class_exists('PagespeedNinja')) return false;
1479 1613
1480 1614 $options = get_option('pagespeedninja_config');
1481 - $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'];
1482 1616
1483 1617 return $gzip;
1484 1618 }
1485 1619
@@ -1486,9 +1620,9 @@
1486 1620 /**
1487 1621 * Output PageSpeed Ninja Gzip notice.
1488 1622 */
1489 1623 public function show_pagespeedninja_gzip_notice() {
1490 - 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>';
1491 1625 }
1492 1626
1493 1627 /**
1494 1628 * Check if Far Future Expiration is active and GZIP compression option is enabled.
@@ -1498,9 +1632,9 @@
1498 1632 public function is_farfutureexpiration_gzip_active() {
1499 1633 if (!class_exists('farFutureExpiration')) return false;
1500 1634
1501 1635 $options = get_option('far_future_expiration_settings');
1502 - $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1636 + $gzip = !empty($options) && $options['enable_gzip'];
1503 1637
1504 1638 return $gzip;
1505 1639 }
1506 1640
@@ -1507,9 +1641,9 @@
1507 1641 /**
1508 1642 * Output Far Future Expiration Gzip notice.
1509 1643 */
1510 1644 public function show_farfutureexpiration_gzip_notice() {
1511 - 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>';
1512 1646 }
1513 1647
1514 1648 /**
1515 1649 * This is a notice to show users that writing `advanced-cache.php` failed
@@ -1514,8 +1648,9 @@
1514 1648 /**
1515 1649 * This is a notice to show users that writing `advanced-cache.php` failed
1516 1650 */
1517 1651 public function show_admin_notice_advanced_cache() {
1652 + // translators: %s is advanced-cache.php file path
1518 1653 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1519 1654 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1520 1655 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1521 1656 }
@@ -1539,13 +1674,15 @@
1539 1674
1540 1675 $interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
1541 1676 $schedules['wpo_page_cache_preload_continue_interval'] = array(
1542 1677 'interval' => $interval,
1678 + // translators: %d is the number of minutes
1543 1679 'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
1544 1680 );
1545 1681
1546 1682 $schedules['wpo_use_cache_lifespan'] = array(
1547 1683 'interval' => $page_cache_length,
1684 + // translators: %s is the cache lifespan
1548 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'))
1549 1686 );
1550 1687
1551 1688 return $schedules;
@@ -1562,9 +1699,9 @@
1562 1699 }
1563 1700 }
1564 1701
1565 1702 /**
1566 - * May be regenerate cache config file, in case of migrations
1703 + * Maybe regenerate cache config file, in case of migrations
1567 1704 */
1568 1705 public function maybe_regenerate_cache_config_file() {
1569 1706 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1570 1707 if (!file_exists($config_file)) {
@@ -1583,9 +1720,9 @@
1583 1720 if (!is_dir($path)) return true;
1584 1721
1585 1722 if ($handle = opendir($path)) {
1586 1723 while (false !== ($entry = readdir($handle))) {
1587 - if ("." == $entry || ".." == $entry) {
1724 + if ("." === $entry || ".." === $entry) {
1588 1725 continue;
1589 1726 }
1590 1727
1591 1728 $full_path = $path . '/' . $entry;
@@ -1600,15 +1737,133 @@
1600 1737 return true;
1601 1738 }
1602 1739
1603 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 + /**
1604 1753 * Check configuration for auto preloading after cache deletion
1605 1754 *
1606 1755 * @return bool
1607 1756 */
1608 1757 public function should_auto_preload_purged_contents() {
1609 - $enabled = WP_Optimize()->get_options()->get_option('auto_preload_purged_contents');
1610 - return 'true' == $enabled;
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;
1611 1866 }
1612 1867 }
1613 1868
1614 1869 endif;