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 +564 -162 3.3.2 → 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
@@ -354,11 +509,18 @@
354 509 }
355 510
356 511 $this->maybe_regenerate_cache_config_file();
357 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 +
358 520 if (!$force_enable) {
359 - $already_ran_enable = true;
360 - return true;
521 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
522 + return $already_ran_enable;
361 523 }
362 524
363 525 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
364 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
@@ -364,8 +526,9 @@
364 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
365 527 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
366 528
367 529 if (!defined('WP_CLI') || !WP_CLI) {
530 + // translators: %s is the path to advanced-cache.php folder
368 531 $message .= "\n\n1. ".sprintf(__('Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
369 532 $message .= "\n2. ".__('Edit or create a file with the name advanced-cache.php', 'wp-optimize');
370 533 $message .= "\n3. ".__('Copy and paste the following lines into the file:', 'wp-optimize');
371 534 }
@@ -384,19 +547,21 @@
384 547 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
385 548 return $already_ran_enable;
386 549 }
387 550
388 - $already_ran_enable = true;
389 -
390 - return true;
551 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
552 + return $already_ran_enable;
391 553 }
392 554
393 555 /**
394 556 * Disables page cache
395 557 *
396 - * @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.
397 560 */
398 - public function disable() {
561 + public function disable($keep_option_enabled = false) {
562 + global $is_apache;
563 +
399 564 $ret = true;
400 565
401 566 $advanced_cache_file = $this->get_advanced_cache_filename();
402 567
@@ -402,15 +567,8 @@
402 567
403 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.
404 569 $disabled_wp_config = $this->write_wp_config(false);
405 570 if (!$disabled_wp_config) {
406 - $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
407 - $action = "deactivate_".$plugin_basename."/wp-optimize.php";
408 - if (current_action() === $action) {
409 - $cache_config = WPO_Cache_Config::instance()->config;
410 - $cache_config['enable_page_caching'] = false;
411 - WPO_Cache_Config::instance()->update($cache_config, true);
412 - }
413 571 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
414 572 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
415 573 }
416 574
@@ -417,9 +575,9 @@
417 575 $disabled_advanced_cache = true;
418 576 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
419 577 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
420 578 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
421 - 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"))) {
422 580 $disabled_advanced_cache = false;
423 581 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
424 582 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
425 583 }
@@ -430,14 +588,55 @@
430 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'));
431 589 }
432 590
433 591 // Delete cache to avoid stale cache on next activation
434 - $this->purge();
592 + $is_cache_purged = $this->purge();
593 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
435 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 +
436 615 return $ret;
437 616 }
438 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();
439 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 +
440 639 /**
441 640 * Purges the cache
442 641 *
443 642 * @return bool - true on success, false otherwise
@@ -479,12 +678,12 @@
479 678 if (0 === $page_cache_length) return $log;
480 679 $expires = time() - $page_cache_length;
481 680 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
482 681 // get all directories that are a direct child of current directory
483 - if (is_dir($cache_folder) && is_writable($cache_folder)) {
682 + if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
484 683 if ($handle = opendir($cache_folder)) {
485 684 while (false !== ($d = readdir($handle))) {
486 - if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
685 + if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
487 686 continue;
488 687 }
489 688
490 689 if ($this->is_front_page_cache($d)) {
@@ -489,9 +688,9 @@
489 688
490 689 if ($this->is_front_page_cache($d)) {
491 690 $modified_time = (int) filemtime("$cache_folder/$d");
492 691 if ($modified_time <= $expires) {
493 - unlink("$cache_folder/$d");
692 + wp_delete_file("$cache_folder/$d");
494 693 }
495 694 continue;
496 695 }
497 696
@@ -503,9 +702,9 @@
503 702 $log[] = "checking if cache has expired - $d";
504 703 if ($modified_time <= $expires) {
505 704 $log[] = "deleting cache in $dir";
506 705 wpo_delete_files($dir, true);
507 - if (file_exists($dir)) rmdir($dir);
706 + if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
508 707 }
509 708 }
510 709 closedir($handle);
511 710 }
@@ -539,36 +738,40 @@
539 738 return true;
540 739 }
541 740
542 741 /**
543 - * Check if cache is enabled and working
742 + * Checks whether page caching is turned on in settings.
544 743 *
545 - * @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.
546 745 */
547 746 public function is_enabled() {
747 + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
748 + }
548 749
549 - 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);
550 759
551 - if (!defined('WP_CACHE') || !WP_CACHE) {
552 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
553 - $this->log("WP_CACHE constant is not present in wp-config.php");
554 - return false;
555 - }
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;
556 763 }
557 764
558 - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
559 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
560 - $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
561 - return false;
562 - }
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;
563 768 }
564 769
565 770 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
566 - if (!file_exists($config_file)) {
567 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
568 - $this->log("Cache config file $config_file is not present");
569 - return false;
570 - }
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;
571 774 }
572 775
573 776 return true;
574 777 }
@@ -575,23 +778,26 @@
575 778
576 779 /**
577 780 * Create the folder structure needed for cache to work
578 781 *
579 - * @return bool - true on success, false otherwise
782 + * @return bool | WP_Error - true on success, false otherwise
580 783 */
581 784 public function create_folders() {
582 785 $permissions_str = __('Please check your file permissions.', 'wp-optimize');
583 786
584 787 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
788 + // translators: %s is a directory name
585 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);
586 790 }
587 791
588 792 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
793 + // translators: %s is a directory name
589 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);
590 795 }
591 796
592 797 if (!is_dir(WPO_CACHE_FILES_DIR)) {
593 798 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
799 + // translators: %s is a directory name
594 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);
595 801 } else {
596 802 wpo_disable_cache_directories_viewing();
597 803 }
@@ -622,9 +828,10 @@
622 828 $cache_path = '/wpo-cache';
623 829 $cache_files_path = '/cache/wpo-cache';
624 830 $cache_extensions_path = WPO_CACHE_EXT_DIR;
625 831 $wpo_version = WPO_VERSION;
626 - $wpo_home_url = trailingslashit(home_url());
832 + $wpo_home_url = trailingslashit(network_home_url());
833 + $abspath = ABSPATH;
627 834
628 835 // CS does not like heredoc
629 836 // phpcs:disable
630 837 $this->advanced_cache_file_content = <<<EOF
@@ -631,9 +838,9 @@
631 838 <?php
632 839
633 840 if (!defined('ABSPATH')) die('No direct access allowed');
634 841
635 -// 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)
636 843
637 844 if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);
638 845
639 846 \$possible_plugin_locations = array(
@@ -704,15 +911,15 @@
704 911 }
705 912
706 913 // check if we can't write the advanced cache file
707 914 // case 1: the directory is read-only and the file doesn't exist
708 - 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))) {
709 916 $this->advanced_cache_file_writing_error = true;
710 917 return false;
711 918 }
712 919
713 920 // case 2: the file already exists but it's read-only
714 - if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
921 + if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
715 922 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
716 923 $this->advanced_cache_file_writing_error = true;
717 924 return false;
718 925 } else {
@@ -743,11 +950,20 @@
743 950 }
744 951 }
745 952
746 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
747 955 $advanced_cache_current_version = $this->get_advanced_cache_version();
748 - 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();
749 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 +
750 966 if (!$this->write_advanced_cache()) {
751 967 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
752 968 } else {
753 969 $this->update_cache_config();
@@ -754,8 +970,31 @@
754 970 }
755 971 }
756 972
757 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 + /**
758 997 * Show notification when advanced-cache.php could not be updated.
759 998 */
760 999 public function notice_advanced_cache_autoupdate_error() {
761 1000 $this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
@@ -800,8 +1039,13 @@
800 1039 if (!$config_path) {
801 1040 return false;
802 1041 }
803 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 +
804 1048 $config_file_string = file_get_contents($config_path);
805 1049
806 1050 // Config file is empty. Maybe couldn't read it?
807 1051 if (empty($config_file_string)) {
@@ -854,9 +1098,9 @@
854 1098 }
855 1099 $errors = 0;
856 1100
857 1101 // First check wp-config.php.
858 - if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
1102 + if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
859 1103 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
860 1104 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
861 1105 }
862 1106
@@ -862,9 +1106,9 @@
862 1106
863 1107 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
864 1108
865 1109 // Now check wp-content. We need to be able to create files of the same user as this file.
866 - 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))) {
867 1111 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
868 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'));
869 1113 $errors++;
870 1114 }
@@ -869,16 +1113,17 @@
869 1113 $errors++;
870 1114 }
871 1115
872 1116 if (file_exists(WPO_CACHE_FILES_DIR)) {
873 - if (!is_writable(WPO_CACHE_FILES_DIR)) {
1117 + if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
874 1118 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
1119 + // translators: %s is cache folder path
875 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));
876 1121 }
877 1122 }
878 1123
879 1124 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
880 - if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
1125 + if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
881 1126 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
882 1127 // If the config exists, only send a warning. Otherwise send an error.
883 1128 $type = 'warning';
884 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
@@ -884,8 +1129,9 @@
884 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
885 1130 $type = 'error';
886 1131 $errors++;
887 1132 }
1133 + // translators: %s is cache config folder path
888 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);
889 1135 }
890 1136 }
891 1137
@@ -900,9 +1146,9 @@
900 1146 * @param string $option Option name `permalink_structure`
901 1147 */
902 1148 public function update_option_permalink_structure($old_value, $value, $option) {
903 1149 $current_config = $this->config->get();
904 - if ($old_value != $value) {
1150 + if ($old_value !== $value) {
905 1151 $current_config[$option] = $value;
906 1152 $this->config->update($current_config, true);
907 1153 }
908 1154 }
@@ -909,9 +1155,9 @@
909 1155
910 1156 /**
911 1157 * Update cache config. Used to support 3d party plugins.
912 1158 *
913 - * @return {boolean|WP_Error}
1159 + * @return bool | WP_Error
914 1160 */
915 1161 public function update_cache_config() {
916 1162 // get current cache settings.
917 1163 $current_config = $this->config->get();
@@ -922,18 +1168,29 @@
922 1168 /**
923 1169 * Delete information about cache size.
924 1170 */
925 1171 public function delete_cache_size_information() {
1172 + //not removing delete_transient so it clear this key from option table
926 1173 delete_transient('wpo_get_cache_size');
1174 + delete_site_transient('wpo_get_cache_size');
927 1175 }
928 1176
929 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 + /**
930 1187 * Get current cache size.
931 1188 *
932 1189 * @return array
933 1190 */
934 1191 public function get_cache_size() {
935 - $cache_size = get_transient('wpo_get_cache_size');
1192 + $cache_size = get_site_transient('wpo_get_cache_size');
936 1193
937 1194 if (!empty($cache_size)) return $cache_size;
938 1195
939 1196 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
@@ -941,9 +1198,11 @@
941 1198 'size' => $infos['size'],
942 1199 'file_count' => $infos['file_count']
943 1200 );
944 1201
945 - 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);
946 1205
947 1206 return $cache_size;
948 1207 }
949 1208
@@ -953,39 +1212,12 @@
953 1212 * @param string $dir
954 1213 * @return array
955 1214 */
956 1215 private function get_dir_infos($dir) {
957 - $dir_size = 0;
958 - $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();
959 1218
960 - $handle = is_dir($dir) ? opendir($dir) : false;
961 -
962 - if (false === $handle) {
963 - return array('size' => 0, 'file_count' => 0);
964 - }
965 -
966 - $file = readdir($handle);
967 -
968 - while (false !== $file) {
969 -
970 - if ('.' != $file && '..' != $file) {
971 - $current_file = $dir.'/'.$file;
972 -
973 - if (is_dir($current_file)) {
974 - $sub_dir_infos = $this->get_dir_infos($current_file);
975 - $dir_size += $sub_dir_infos['size'];
976 - $file_count += $sub_dir_infos['file_count'];
977 - } elseif (is_file($current_file)) {
978 - $dir_size += filesize($current_file);
979 - $file_count++;
980 - }
981 - }
982 -
983 - $file = readdir($handle);
984 -
985 - }
986 -
987 - return array('size' => $dir_size, 'file_count' => $file_count);
1219 + return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
988 1220 }
989 1221
990 1222 /**
991 1223 * Returns the path to wp-config
@@ -1017,11 +1249,9 @@
1017 1249 * @param string $src
1018 1250 * @return boolean
1019 1251 */
1020 1252 public static function delete($src) {
1021 -
1022 1253 return wpo_delete_files($src);
1023 -
1024 1254 }
1025 1255
1026 1256 /**
1027 1257 * Delete cached files for specific url.
@@ -1031,18 +1261,49 @@
1031 1261 *
1032 1262 * @return bool
1033 1263 */
1034 1264 public static function delete_cache_by_url($url, $recursive = false) {
1035 - if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1265 + if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;
1036 1266
1037 1267 $path = self::get_full_path_from_url($url);
1038 1268
1039 1269 do_action('wpo_delete_cache_by_url', $url, $recursive);
1040 1270
1271 + do_action('wpo_preload_url', $url);
1272 +
1041 1273 return wpo_delete_files($path, $recursive);
1042 1274 }
1043 1275
1044 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 + /**
1045 1306 * Delete cached files for single post.
1046 1307 *
1047 1308 * @param integer $post_id The post ID
1048 1309 *
@@ -1064,9 +1325,9 @@
1064 1325 * @return bool
1065 1326 */
1066 1327 public static function really_delete_single_post_cache($post_id) {
1067 1328
1068 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1329 + if (!defined('WPO_CACHE_FILES_DIR')) return false;
1069 1330
1070 1331 $post_url = get_permalink($post_id);
1071 1332
1072 1333 $path = self::get_full_path_from_url($post_url);
@@ -1076,8 +1337,10 @@
1076 1337 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1077 1338
1078 1339 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1079 1340
1341 + do_action('wpo_preload_url', $post_url);
1342 +
1080 1343 return wpo_delete_files($path, $recursive);
1081 1344 }
1082 1345
1083 1346 /**
@@ -1090,11 +1353,15 @@
1090 1353 $homepage_url = get_home_url(get_current_blog_id());
1091 1354
1092 1355 $path = self::get_full_path_from_url($homepage_url);
1093 1356
1094 - do_action('wpo_delete_cache_by_url', $homepage_url, false);
1357 + $recursive = false;
1095 1358
1096 - wpo_delete_files($path, false);
1359 + do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);
1360 +
1361 + do_action('wpo_preload_url', $homepage_url);
1362 +
1363 + wpo_delete_files($path, $recursive);
1097 1364 }
1098 1365
1099 1366 /**
1100 1367 * Delete feed from cache.
@@ -1112,8 +1379,10 @@
1112 1379 }
1113 1380
1114 1381 /**
1115 1382 * Delete post feed from cache.
1383 + *
1384 + * @param int $post_id Post ID
1116 1385 */
1117 1386 public static function delete_post_feed_cache($post_id) {
1118 1387 self::really_delete_post_feed_cache($post_id);
1119 1388
@@ -1121,8 +1390,10 @@
1121 1390 }
1122 1391
1123 1392 /**
1124 1393 * Really delete post feed from cache.
1394 + *
1395 + * @param int $post_id Post ID
1125 1396 */
1126 1397 public static function really_delete_post_feed_cache($post_id) {
1127 1398 if (!defined('WPO_CACHE_FILES_DIR')) return;
1128 1399
@@ -1151,9 +1422,9 @@
1151 1422
1152 1423 // delete empty comments dir from the cache
1153 1424 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1154 1425 $path = self::get_full_path_from_url($comments_url);
1155 -
1426 +
1156 1427 if (wpo_is_empty_dir($path)) {
1157 1428 wpo_delete_files($path, true);
1158 1429 }
1159 1430 }
@@ -1174,9 +1445,9 @@
1174 1445 * @return void
1175 1446 */
1176 1447 public function admin_init() {
1177 1448 // Maybe update the advanced cache.
1178 - 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')) {
1179 1450 $this->maybe_update_advanced_cache();
1180 1451 $this->cron_activate();
1181 1452 }
1182 1453 }
@@ -1195,9 +1466,9 @@
1195 1466
1196 1467 /**
1197 1468 * Returns an instance of the current class, creates one if it doesn't exist
1198 1469 *
1199 - * @return object
1470 + * @return self
1200 1471 */
1201 1472 public static function instance() {
1202 1473 if (empty(self::$instance)) {
1203 1474 self::$instance = new self();
@@ -1208,19 +1479,19 @@
1208 1479
1209 1480 /**
1210 1481 * Update gmt_offset and timezone_string cache config value, used with hook `update_gmt_offset_timezone_string_config`.
1211 1482 *
1212 - * @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)
1213 - * @param {float|string} $new_value
1214 - * @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
1215 1486 *
1216 1487 * @return null
1217 1488 */
1218 1489 public function update_gmt_offset_timezone_string_config($old_value, $new_value, $option) {
1219 - if ('' == $new_value) return;
1490 + if ('' === $new_value) return;
1220 1491
1221 1492 $current_config = $this->config->get();
1222 - if ('timezone_string' == $option) {
1493 + if ('timezone_string' === $option) {
1223 1494 $timeZone = new DateTimeZone($new_value);
1224 1495 $dateTime = new DateTime("now");
1225 1496 $gmt_offset = $timeZone->getOffset($dateTime);
1226 1497 $current_config['gmt_offset'] = round($gmt_offset/3600, 1);
@@ -1340,9 +1611,9 @@
1340 1611 public function is_pagespeedninja_gzip_active() {
1341 1612 if (!class_exists('PagespeedNinja')) return false;
1342 1613
1343 1614 $options = get_option('pagespeedninja_config');
1344 - $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'];
1345 1616
1346 1617 return $gzip;
1347 1618 }
1348 1619
@@ -1349,9 +1620,9 @@
1349 1620 /**
1350 1621 * Output PageSpeed Ninja Gzip notice.
1351 1622 */
1352 1623 public function show_pagespeedninja_gzip_notice() {
1353 - 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>';
1354 1625 }
1355 1626
1356 1627 /**
1357 1628 * Check if Far Future Expiration is active and GZIP compression option is enabled.
@@ -1361,9 +1632,9 @@
1361 1632 public function is_farfutureexpiration_gzip_active() {
1362 1633 if (!class_exists('farFutureExpiration')) return false;
1363 1634
1364 1635 $options = get_option('far_future_expiration_settings');
1365 - $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1636 + $gzip = !empty($options) && $options['enable_gzip'];
1366 1637
1367 1638 return $gzip;
1368 1639 }
1369 1640
@@ -1370,9 +1641,9 @@
1370 1641 /**
1371 1642 * Output Far Future Expiration Gzip notice.
1372 1643 */
1373 1644 public function show_farfutureexpiration_gzip_notice() {
1374 - 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>';
1375 1646 }
1376 1647
1377 1648 /**
1378 1649 * This is a notice to show users that writing `advanced-cache.php` failed
@@ -1377,8 +1648,9 @@
1377 1648 /**
1378 1649 * This is a notice to show users that writing `advanced-cache.php` failed
1379 1650 */
1380 1651 public function show_admin_notice_advanced_cache() {
1652 + // translators: %s is advanced-cache.php file path
1381 1653 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1382 1654 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1383 1655 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1384 1656 }
@@ -1402,13 +1674,15 @@
1402 1674
1403 1675 $interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
1404 1676 $schedules['wpo_page_cache_preload_continue_interval'] = array(
1405 1677 'interval' => $interval,
1678 + // translators: %d is the number of minutes
1406 1679 'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
1407 1680 );
1408 1681
1409 1682 $schedules['wpo_use_cache_lifespan'] = array(
1410 1683 'interval' => $page_cache_length,
1684 + // translators: %s is the cache lifespan
1411 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'))
1412 1686 );
1413 1687
1414 1688 return $schedules;
@@ -1425,9 +1699,9 @@
1425 1699 }
1426 1700 }
1427 1701
1428 1702 /**
1429 - * May be regenerate cache config file, in case of migrations
1703 + * Maybe regenerate cache config file, in case of migrations
1430 1704 */
1431 1705 public function maybe_regenerate_cache_config_file() {
1432 1706 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1433 1707 if (!file_exists($config_file)) {
@@ -1446,9 +1720,9 @@
1446 1720 if (!is_dir($path)) return true;
1447 1721
1448 1722 if ($handle = opendir($path)) {
1449 1723 while (false !== ($entry = readdir($handle))) {
1450 - if ("." == $entry || ".." == $entry) {
1724 + if ("." === $entry || ".." === $entry) {
1451 1725 continue;
1452 1726 }
1453 1727
1454 1728 $full_path = $path . '/' . $entry;
@@ -1460,8 +1734,136 @@
1460 1734 closedir($handle);
1461 1735 return true;
1462 1736 }
1463 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;
1464 1866 }
1465 1867 }
1466 1868
1467 1869 endif;