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 +536 -166 3.4.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,27 +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 /**
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();
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 + /**
133 245 * Activate cron job when the cache is enabled for deleting expired cache files
134 246 */
135 247 public function cron_activate() {
136 - $page_cache_length = $this->config->get_option('page_cache_length');
137 248 if ($this->is_enabled()) {
138 - if (!wp_next_scheduled('wpo_purge_old_cache')) {
139 - wp_schedule_event(time() + (false === $page_cache_length ? '86400' : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
140 - }
249 + $this->schedule_purge_old_cache();
141 250 } else {
142 - wp_clear_scheduled_hook('wpo_purge_old_cache');
251 + $this->unschedule_purge_old_cache();
143 252 }
144 253 }
145 254
146 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 + /**
147 282 * Do required actions on activate/deactivate any plugin.
148 283 */
149 284 public function activate_deactivate_plugin() {
150 285
@@ -151,9 +286,11 @@
151 286 wp_clear_scheduled_hook('wpo_purge_old_cache');
152 287
153 288 $this->update_cache_config();
154 289
155 - $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");
156 293 }
157 294
158 295 /**
159 296 * Check if current user can purge cache.
@@ -161,15 +298,10 @@
161 298 * @return bool
162 299 */
163 300 public function can_purge_cache() {
164 301 if (!$this->is_enabled()) return false;
165 - $required_capability = is_multisite() ? 'manage_network_options' : 'manage_options';
166 302
167 - if (WP_Optimize::is_premium()) {
168 - return current_user_can($required_capability) || WP_Optimize_Premium()->can_purge_the_cache();
169 - } else {
170 - return current_user_can($required_capability);
171 - }
303 + return WP_Optimize_Utils::current_user_can_purge_cache();
172 304 }
173 305
174 306 /**
175 307 * Add Purge from cache in admin bar.
@@ -180,12 +312,13 @@
180 312 public function admin_bar_purge_cache($menu_items) {
181 313 global $pagenow;
182 314 if (!$this->can_purge_cache()) return $menu_items;
183 315
184 - $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();
185 317
186 318 $cache_size = $this->get_cache_size();
187 319 $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
320 + // translators: %d is the number of files in the cache
188 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>';
189 322
190 323 $menu_items[] = array(
191 324 'id' => 'wpo_cache_stats',
@@ -205,9 +338,9 @@
205 338 ),
206 339 'parent' => 'wpo_purge_cache',
207 340 );
208 341
209 - if (!is_admin() || 'post.php' == $pagenow) {
342 + if (!is_admin() || 'post.php' === $pagenow) {
210 343 $menu_items[] = array(
211 344 'id' => 'wpo_purge_this_page_cache',
212 345 'title' => __('Purge cache for this page', 'wp-optimize'),
213 346 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
@@ -229,22 +362,38 @@
229 362
230 363 if (!$this->can_purge_cache()) return;
231 364
232 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
233 368 if (isset($_GET['wpo_single_page_cache_purged'])) {
234 - $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';
235 371 } else {
236 - $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';
237 374 }
375 + // phpcs:enable
238 376
239 - add_action('admin_notices', array($this, $notice_function));
377 + if (is_admin()) {
378 + $admin_notices_hook = is_network_admin() ? 'network_admin_notices' : 'admin_notices';
240 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 +
241 390 return;
242 391 }
243 392
244 393 if (!isset($_GET['_wpo_purge'])) return;
245 394
246 - 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')) {
247 396 $success = false;
248 397
249 398 if (is_admin()) {
250 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
@@ -249,55 +398,56 @@
249 398 if (is_admin()) {
250 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
251 400 if ($post > 0) {
252 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);
253 403 }
254 404 } else {
255 - $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__);
256 408 }
257 409
258 - // remove nonce from url and reload page.
259 - 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);
260 415 exit;
261 416
262 - } 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')) {
263 418 $success = self::purge();
419 + $this->maybe_set_preload_cron_job();
420 + if ($success) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
264 421
265 - // remove nonce from url and reload page.
266 - 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);
267 427 exit;
268 428 }
269 429 }
270 430
271 431 /**
272 - * 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
273 438 */
274 - public function notice_purge_single_page_cache_success() {
275 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
276 - }
439 + private function maybe_set_preload_cron_job() {
440 + $wpo_page_cache_preloader = WP_Optimize_Page_Cache_Preloader::instance();
277 441
278 - /**
279 - * Show notification when page cache wasn't purged.
280 - */
281 - public function notice_purge_single_page_cache_error() {
282 - $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 + }
283 447 }
284 -
448 +
285 449 /**
286 - * Show notification when all pages cache purged successfully.
287 - */
288 - public function notice_purge_all_pages_cache_success() {
289 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
290 - }
291 -
292 - /**
293 - * Show notification when all pages cache wasn't purged.
294 - */
295 - public function notice_purge_all_pages_cache_error() {
296 - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
297 - }
298 -
299 - /**
300 450 * Show notification in WordPress admin.
301 451 *
302 452 * @param string $message HTML (no further escaping is performed)
303 453 * @param string $type error, warning, success, or info
@@ -310,10 +460,10 @@
310 460 window.addEventListener('load', function() {
311 461 (function(wp) {
312 462 if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
313 463 wp.data.dispatch('core/notices').createNotice(
314 - '<?php echo $type; ?>',
315 - '<?php echo $message; ?>',
464 + '<?php echo esc_html($type); ?>',
465 + '<?php echo wp_kses_post($message); ?>',
316 466 {
317 467 isDismissible: true,
318 468 }
319 469 );
@@ -321,10 +471,10 @@
321 471 })(window.wp);
322 472 });
323 473 </script>
324 474 <?php else : ?>
325 - <div class="notice wpo-notice notice-<?php echo $type; ?> is-dismissible">
326 - <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>
327 477 </div>
328 478 <?php
329 479 endif;
330 480 }
@@ -336,8 +486,9 @@
336 486 *
337 487 * @return WP_Error|bool - true on success, error otherwise
338 488 */
339 489 public function enable($force_enable = false) {
490 + global $is_apache;
340 491 static $already_ran_enable = false;
341 492
342 493 if ($already_ran_enable) return $already_ran_enable;
343 494
@@ -358,11 +509,18 @@
358 509 }
359 510
360 511 $this->maybe_regenerate_cache_config_file();
361 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 +
362 520 if (!$force_enable) {
363 - $already_ran_enable = true;
364 - return true;
521 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
522 + return $already_ran_enable;
365 523 }
366 524
367 525 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
368 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
@@ -368,8 +526,9 @@
368 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
369 527 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
370 528
371 529 if (!defined('WP_CLI') || !WP_CLI) {
530 + // translators: %s is the path to advanced-cache.php folder
372 531 $message .= "\n\n1. ".sprintf(__('Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
373 532 $message .= "\n2. ".__('Edit or create a file with the name advanced-cache.php', 'wp-optimize');
374 533 $message .= "\n3. ".__('Copy and paste the following lines into the file:', 'wp-optimize');
375 534 }
@@ -388,19 +547,21 @@
388 547 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
389 548 return $already_ran_enable;
390 549 }
391 550
392 - $already_ran_enable = true;
393 -
394 - return true;
551 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
552 + return $already_ran_enable;
395 553 }
396 554
397 555 /**
398 556 * Disables page cache
399 557 *
400 - * @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.
401 560 */
402 - public function disable() {
561 + public function disable($keep_option_enabled = false) {
562 + global $is_apache;
563 +
403 564 $ret = true;
404 565
405 566 $advanced_cache_file = $this->get_advanced_cache_filename();
406 567
@@ -406,15 +567,8 @@
406 567
407 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.
408 569 $disabled_wp_config = $this->write_wp_config(false);
409 570 if (!$disabled_wp_config) {
410 - $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
411 - $action = "deactivate_".$plugin_basename."/wp-optimize.php";
412 - if (current_action() === $action) {
413 - $cache_config = WPO_Cache_Config::instance()->config;
414 - $cache_config['enable_page_caching'] = false;
415 - WPO_Cache_Config::instance()->update($cache_config, true);
416 - }
417 571 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
418 572 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
419 573 }
420 574
@@ -421,9 +575,9 @@
421 575 $disabled_advanced_cache = true;
422 576 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
423 577 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
424 578 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
425 - 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"))) {
426 580 $disabled_advanced_cache = false;
427 581 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
428 582 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
429 583 }
@@ -434,17 +588,55 @@
434 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'));
435 589 }
436 590
437 591 // Delete cache to avoid stale cache on next activation
438 - $this->purge();
592 + $is_cache_purged = $this->purge();
593 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
439 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 +
440 600 // Unschedule cron job to purge old cache
441 601 wp_clear_scheduled_hook('wpo_purge_old_cache');
442 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 +
443 615 return $ret;
444 616 }
445 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();
446 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 +
447 639 /**
448 640 * Purges the cache
449 641 *
450 642 * @return bool - true on success, false otherwise
@@ -486,12 +678,12 @@
486 678 if (0 === $page_cache_length) return $log;
487 679 $expires = time() - $page_cache_length;
488 680 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
489 681 // get all directories that are a direct child of current directory
490 - if (is_dir($cache_folder) && is_writable($cache_folder)) {
682 + if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
491 683 if ($handle = opendir($cache_folder)) {
492 684 while (false !== ($d = readdir($handle))) {
493 - if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
685 + if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
494 686 continue;
495 687 }
496 688
497 689 if ($this->is_front_page_cache($d)) {
@@ -496,9 +688,9 @@
496 688
497 689 if ($this->is_front_page_cache($d)) {
498 690 $modified_time = (int) filemtime("$cache_folder/$d");
499 691 if ($modified_time <= $expires) {
500 - unlink("$cache_folder/$d");
692 + wp_delete_file("$cache_folder/$d");
501 693 }
502 694 continue;
503 695 }
504 696
@@ -510,9 +702,9 @@
510 702 $log[] = "checking if cache has expired - $d";
511 703 if ($modified_time <= $expires) {
512 704 $log[] = "deleting cache in $dir";
513 705 wpo_delete_files($dir, true);
514 - if (file_exists($dir)) rmdir($dir);
706 + if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
515 707 }
516 708 }
517 709 closedir($handle);
518 710 }
@@ -546,36 +738,40 @@
546 738 return true;
547 739 }
548 740
549 741 /**
550 - * Check if cache is enabled and working
742 + * Checks whether page caching is turned on in settings.
551 743 *
552 - * @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.
553 745 */
554 746 public function is_enabled() {
747 + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
748 + }
555 749
556 - 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);
557 759
558 - if (!defined('WP_CACHE') || !WP_CACHE) {
559 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
560 - $this->log("WP_CACHE constant is not present in wp-config.php");
561 - return false;
562 - }
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;
563 763 }
564 764
565 - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
566 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
567 - $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
568 - return false;
569 - }
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;
570 768 }
571 769
572 770 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
573 - if (!file_exists($config_file)) {
574 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
575 - $this->log("Cache config file $config_file is not present");
576 - return false;
577 - }
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;
578 774 }
579 775
580 776 return true;
581 777 }
@@ -582,23 +778,26 @@
582 778
583 779 /**
584 780 * Create the folder structure needed for cache to work
585 781 *
586 - * @return bool - true on success, false otherwise
782 + * @return bool | WP_Error - true on success, false otherwise
587 783 */
588 784 public function create_folders() {
589 785 $permissions_str = __('Please check your file permissions.', 'wp-optimize');
590 786
591 787 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
788 + // translators: %s is a directory name
592 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);
593 790 }
594 791
595 792 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
793 + // translators: %s is a directory name
596 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);
597 795 }
598 796
599 797 if (!is_dir(WPO_CACHE_FILES_DIR)) {
600 798 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
799 + // translators: %s is a directory name
601 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);
602 801 } else {
603 802 wpo_disable_cache_directories_viewing();
604 803 }
@@ -629,9 +828,9 @@
629 828 $cache_path = '/wpo-cache';
630 829 $cache_files_path = '/cache/wpo-cache';
631 830 $cache_extensions_path = WPO_CACHE_EXT_DIR;
632 831 $wpo_version = WPO_VERSION;
633 - $wpo_home_url = trailingslashit(home_url());
832 + $wpo_home_url = trailingslashit(network_home_url());
634 833 $abspath = ABSPATH;
635 834
636 835 // CS does not like heredoc
637 836 // phpcs:disable
@@ -712,15 +911,15 @@
712 911 }
713 912
714 913 // check if we can't write the advanced cache file
715 914 // case 1: the directory is read-only and the file doesn't exist
716 - 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))) {
717 916 $this->advanced_cache_file_writing_error = true;
718 917 return false;
719 918 }
720 919
721 920 // case 2: the file already exists but it's read-only
722 - if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
921 + if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
723 922 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
724 923 $this->advanced_cache_file_writing_error = true;
725 924 return false;
726 925 } else {
@@ -753,10 +952,18 @@
753 952
754 953 // from 3.0.17 we use more secure way to store cache config files and need update advanced-cache.php
755 954 // if the site url or folder changed we also update advanced-cache.php
756 955 $advanced_cache_current_version = $this->get_advanced_cache_version();
757 - if ($advanced_cache_current_version && version_compare($advanced_cache_current_version, $this->_minimum_advanced_cache_file_version, '>=') && !$this->is_the_site_url_or_folder_changed()) return;
956 + $is_the_site_url_or_folder_changed = $this->is_the_site_url_or_folder_changed();
758 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 +
759 966 if (!$this->write_advanced_cache()) {
760 967 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
761 968 } else {
762 969 $this->update_cache_config();
@@ -775,11 +982,11 @@
775 982
776 983 if (false === $content) return false;
777 984
778 985 if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\) (\(homeurl: (.+)\)) (\(abspath: (.+)\)) \(do not change/Ui', $content, $match)) {
779 - $wpo_home_url = trailingslashit(home_url());
986 + $wpo_home_url = trailingslashit(network_home_url());
780 987 $abspath = ABSPATH;
781 - return ($wpo_home_url != $match[3] || $abspath != $match[5]);
988 + return ($wpo_home_url !== $match[3] || $abspath !== $match[5]);
782 989 } elseif (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
783 990 return true;
784 991 }
785 992
@@ -832,8 +1039,13 @@
832 1039 if (!$config_path) {
833 1040 return false;
834 1041 }
835 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 +
836 1048 $config_file_string = file_get_contents($config_path);
837 1049
838 1050 // Config file is empty. Maybe couldn't read it?
839 1051 if (empty($config_file_string)) {
@@ -886,9 +1098,9 @@
886 1098 }
887 1099 $errors = 0;
888 1100
889 1101 // First check wp-config.php.
890 - if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
1102 + if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
891 1103 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
892 1104 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
893 1105 }
894 1106
@@ -894,9 +1106,9 @@
894 1106
895 1107 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
896 1108
897 1109 // Now check wp-content. We need to be able to create files of the same user as this file.
898 - 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))) {
899 1111 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
900 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'));
901 1113 $errors++;
902 1114 }
@@ -901,16 +1113,17 @@
901 1113 $errors++;
902 1114 }
903 1115
904 1116 if (file_exists(WPO_CACHE_FILES_DIR)) {
905 - if (!is_writable(WPO_CACHE_FILES_DIR)) {
1117 + if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
906 1118 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
1119 + // translators: %s is cache folder path
907 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));
908 1121 }
909 1122 }
910 1123
911 1124 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
912 - if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
1125 + if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
913 1126 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
914 1127 // If the config exists, only send a warning. Otherwise send an error.
915 1128 $type = 'warning';
916 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
@@ -916,8 +1129,9 @@
916 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
917 1130 $type = 'error';
918 1131 $errors++;
919 1132 }
1133 + // translators: %s is cache config folder path
920 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);
921 1135 }
922 1136 }
923 1137
@@ -932,9 +1146,9 @@
932 1146 * @param string $option Option name `permalink_structure`
933 1147 */
934 1148 public function update_option_permalink_structure($old_value, $value, $option) {
935 1149 $current_config = $this->config->get();
936 - if ($old_value != $value) {
1150 + if ($old_value !== $value) {
937 1151 $current_config[$option] = $value;
938 1152 $this->config->update($current_config, true);
939 1153 }
940 1154 }
@@ -941,9 +1155,9 @@
941 1155
942 1156 /**
943 1157 * Update cache config. Used to support 3d party plugins.
944 1158 *
945 - * @return {boolean|WP_Error}
1159 + * @return bool | WP_Error
946 1160 */
947 1161 public function update_cache_config() {
948 1162 // get current cache settings.
949 1163 $current_config = $this->config->get();
@@ -954,18 +1168,29 @@
954 1168 /**
955 1169 * Delete information about cache size.
956 1170 */
957 1171 public function delete_cache_size_information() {
1172 + //not removing delete_transient so it clear this key from option table
958 1173 delete_transient('wpo_get_cache_size');
1174 + delete_site_transient('wpo_get_cache_size');
959 1175 }
960 1176
961 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 + /**
962 1187 * Get current cache size.
963 1188 *
964 1189 * @return array
965 1190 */
966 1191 public function get_cache_size() {
967 - $cache_size = get_transient('wpo_get_cache_size');
1192 + $cache_size = get_site_transient('wpo_get_cache_size');
968 1193
969 1194 if (!empty($cache_size)) return $cache_size;
970 1195
971 1196 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
@@ -973,9 +1198,11 @@
973 1198 'size' => $infos['size'],
974 1199 'file_count' => $infos['file_count']
975 1200 );
976 1201
977 - 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);
978 1205
979 1206 return $cache_size;
980 1207 }
981 1208
@@ -985,39 +1212,12 @@
985 1212 * @param string $dir
986 1213 * @return array
987 1214 */
988 1215 private function get_dir_infos($dir) {
989 - $dir_size = 0;
990 - $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();
991 1218
992 - $handle = is_dir($dir) ? opendir($dir) : false;
993 -
994 - if (false === $handle) {
995 - return array('size' => 0, 'file_count' => 0);
996 - }
997 -
998 - $file = readdir($handle);
999 -
1000 - while (false !== $file) {
1001 -
1002 - if ('.' != $file && '..' != $file) {
1003 - $current_file = $dir.'/'.$file;
1004 -
1005 - if (is_dir($current_file)) {
1006 - $sub_dir_infos = $this->get_dir_infos($current_file);
1007 - $dir_size += $sub_dir_infos['size'];
1008 - $file_count += $sub_dir_infos['file_count'];
1009 - } elseif (is_file($current_file)) {
1010 - $dir_size += filesize($current_file);
1011 - $file_count++;
1012 - }
1013 - }
1014 -
1015 - $file = readdir($handle);
1016 -
1017 - }
1018 -
1019 - return array('size' => $dir_size, 'file_count' => $file_count);
1219 + return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
1020 1220 }
1021 1221
1022 1222 /**
1023 1223 * Returns the path to wp-config
@@ -1049,11 +1249,9 @@
1049 1249 * @param string $src
1050 1250 * @return boolean
1051 1251 */
1052 1252 public static function delete($src) {
1053 -
1054 1253 return wpo_delete_files($src);
1055 -
1056 1254 }
1057 1255
1058 1256 /**
1059 1257 * Delete cached files for specific url.
@@ -1063,18 +1261,49 @@
1063 1261 *
1064 1262 * @return bool
1065 1263 */
1066 1264 public static function delete_cache_by_url($url, $recursive = false) {
1067 - if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1265 + if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;
1068 1266
1069 1267 $path = self::get_full_path_from_url($url);
1070 1268
1071 1269 do_action('wpo_delete_cache_by_url', $url, $recursive);
1072 1270
1271 + do_action('wpo_preload_url', $url);
1272 +
1073 1273 return wpo_delete_files($path, $recursive);
1074 1274 }
1075 1275
1076 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 + /**
1077 1306 * Delete cached files for single post.
1078 1307 *
1079 1308 * @param integer $post_id The post ID
1080 1309 *
@@ -1096,9 +1325,9 @@
1096 1325 * @return bool
1097 1326 */
1098 1327 public static function really_delete_single_post_cache($post_id) {
1099 1328
1100 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1329 + if (!defined('WPO_CACHE_FILES_DIR')) return false;
1101 1330
1102 1331 $post_url = get_permalink($post_id);
1103 1332
1104 1333 $path = self::get_full_path_from_url($post_url);
@@ -1108,8 +1337,10 @@
1108 1337 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1109 1338
1110 1339 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1111 1340
1341 + do_action('wpo_preload_url', $post_url);
1342 +
1112 1343 return wpo_delete_files($path, $recursive);
1113 1344 }
1114 1345
1115 1346 /**
@@ -1122,11 +1353,15 @@
1122 1353 $homepage_url = get_home_url(get_current_blog_id());
1123 1354
1124 1355 $path = self::get_full_path_from_url($homepage_url);
1125 1356
1126 - do_action('wpo_delete_cache_by_url', $homepage_url, false);
1357 + $recursive = false;
1127 1358
1128 - 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);
1129 1364 }
1130 1365
1131 1366 /**
1132 1367 * Delete feed from cache.
@@ -1144,8 +1379,10 @@
1144 1379 }
1145 1380
1146 1381 /**
1147 1382 * Delete post feed from cache.
1383 + *
1384 + * @param int $post_id Post ID
1148 1385 */
1149 1386 public static function delete_post_feed_cache($post_id) {
1150 1387 self::really_delete_post_feed_cache($post_id);
1151 1388
@@ -1153,8 +1390,10 @@
1153 1390 }
1154 1391
1155 1392 /**
1156 1393 * Really delete post feed from cache.
1394 + *
1395 + * @param int $post_id Post ID
1157 1396 */
1158 1397 public static function really_delete_post_feed_cache($post_id) {
1159 1398 if (!defined('WPO_CACHE_FILES_DIR')) return;
1160 1399
@@ -1183,9 +1422,9 @@
1183 1422
1184 1423 // delete empty comments dir from the cache
1185 1424 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1186 1425 $path = self::get_full_path_from_url($comments_url);
1187 -
1426 +
1188 1427 if (wpo_is_empty_dir($path)) {
1189 1428 wpo_delete_files($path, true);
1190 1429 }
1191 1430 }
@@ -1206,9 +1445,9 @@
1206 1445 * @return void
1207 1446 */
1208 1447 public function admin_init() {
1209 1448 // Maybe update the advanced cache.
1210 - 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')) {
1211 1450 $this->maybe_update_advanced_cache();
1212 1451 $this->cron_activate();
1213 1452 }
1214 1453 }
@@ -1227,9 +1466,9 @@
1227 1466
1228 1467 /**
1229 1468 * Returns an instance of the current class, creates one if it doesn't exist
1230 1469 *
1231 - * @return object
1470 + * @return self
1232 1471 */
1233 1472 public static function instance() {
1234 1473 if (empty(self::$instance)) {
1235 1474 self::$instance = new self();
@@ -1240,19 +1479,19 @@
1240 1479
1241 1480 /**
1242 1481 * Update gmt_offset and timezone_string cache config value, used with hook `update_gmt_offset_timezone_string_config`.
1243 1482 *
1244 - * @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)
1245 - * @param {float|string} $new_value
1246 - * @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
1247 1486 *
1248 1487 * @return null
1249 1488 */
1250 1489 public function update_gmt_offset_timezone_string_config($old_value, $new_value, $option) {
1251 - if ('' == $new_value) return;
1490 + if ('' === $new_value) return;
1252 1491
1253 1492 $current_config = $this->config->get();
1254 - if ('timezone_string' == $option) {
1493 + if ('timezone_string' === $option) {
1255 1494 $timeZone = new DateTimeZone($new_value);
1256 1495 $dateTime = new DateTime("now");
1257 1496 $gmt_offset = $timeZone->getOffset($dateTime);
1258 1497 $current_config['gmt_offset'] = round($gmt_offset/3600, 1);
@@ -1372,9 +1611,9 @@
1372 1611 public function is_pagespeedninja_gzip_active() {
1373 1612 if (!class_exists('PagespeedNinja')) return false;
1374 1613
1375 1614 $options = get_option('pagespeedninja_config');
1376 - $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'];
1377 1616
1378 1617 return $gzip;
1379 1618 }
1380 1619
@@ -1381,9 +1620,9 @@
1381 1620 /**
1382 1621 * Output PageSpeed Ninja Gzip notice.
1383 1622 */
1384 1623 public function show_pagespeedninja_gzip_notice() {
1385 - 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>';
1386 1625 }
1387 1626
1388 1627 /**
1389 1628 * Check if Far Future Expiration is active and GZIP compression option is enabled.
@@ -1393,9 +1632,9 @@
1393 1632 public function is_farfutureexpiration_gzip_active() {
1394 1633 if (!class_exists('farFutureExpiration')) return false;
1395 1634
1396 1635 $options = get_option('far_future_expiration_settings');
1397 - $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1636 + $gzip = !empty($options) && $options['enable_gzip'];
1398 1637
1399 1638 return $gzip;
1400 1639 }
1401 1640
@@ -1402,9 +1641,9 @@
1402 1641 /**
1403 1642 * Output Far Future Expiration Gzip notice.
1404 1643 */
1405 1644 public function show_farfutureexpiration_gzip_notice() {
1406 - 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>';
1407 1646 }
1408 1647
1409 1648 /**
1410 1649 * This is a notice to show users that writing `advanced-cache.php` failed
@@ -1409,8 +1648,9 @@
1409 1648 /**
1410 1649 * This is a notice to show users that writing `advanced-cache.php` failed
1411 1650 */
1412 1651 public function show_admin_notice_advanced_cache() {
1652 + // translators: %s is advanced-cache.php file path
1413 1653 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1414 1654 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1415 1655 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1416 1656 }
@@ -1434,13 +1674,15 @@
1434 1674
1435 1675 $interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
1436 1676 $schedules['wpo_page_cache_preload_continue_interval'] = array(
1437 1677 'interval' => $interval,
1678 + // translators: %d is the number of minutes
1438 1679 'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
1439 1680 );
1440 1681
1441 1682 $schedules['wpo_use_cache_lifespan'] = array(
1442 1683 'interval' => $page_cache_length,
1684 + // translators: %s is the cache lifespan
1443 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'))
1444 1686 );
1445 1687
1446 1688 return $schedules;
@@ -1457,9 +1699,9 @@
1457 1699 }
1458 1700 }
1459 1701
1460 1702 /**
1461 - * May be regenerate cache config file, in case of migrations
1703 + * Maybe regenerate cache config file, in case of migrations
1462 1704 */
1463 1705 public function maybe_regenerate_cache_config_file() {
1464 1706 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1465 1707 if (!file_exists($config_file)) {
@@ -1478,9 +1720,9 @@
1478 1720 if (!is_dir($path)) return true;
1479 1721
1480 1722 if ($handle = opendir($path)) {
1481 1723 while (false !== ($entry = readdir($handle))) {
1482 - if ("." == $entry || ".." == $entry) {
1724 + if ("." === $entry || ".." === $entry) {
1483 1725 continue;
1484 1726 }
1485 1727
1486 1728 $full_path = $path . '/' . $entry;
@@ -1492,8 +1734,136 @@
1492 1734 closedir($handle);
1493 1735 return true;
1494 1736 }
1495 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;
1496 1866 }
1497 1867 }
1498 1868
1499 1869 endif;