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 +842 -201 3.2.3 → 4.7.0 View file →
@@ -21,31 +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 specifc cache
27 - */
28 -if (!defined('WPO_CACHE_FILES_DIR')) define('WPO_CACHE_FILES_DIR', untrailingslashit(WP_CONTENT_DIR).'/cache/wpo-cache');
29 -
30 -if (!class_exists('WPO_Cache_Config')) require_once(dirname(__FILE__) . '/class-wpo-cache-config.php');
31 -if (!class_exists('WPO_Cache_Rules')) require_once(dirname(__FILE__) . '/class-wpo-cache-rules.php');
32 -
33 -if (!class_exists('WP_Optimize_Detect_Cache_Plugins')) require_once(dirname(__FILE__) . '/class-wpo-detect-cache-plugins.php');
34 -
35 -if (!class_exists('WP_Optimize_Page_Cache_Preloader')) require_once(dirname(__FILE__) . '/class-wpo-cache-preloader.php');
36 -if (!class_exists('WPO_Cache_Config')) require_once(dirname(__FILE__) . '/class-wpo-cache-config.php');
37 -if (!class_exists('WPO_Cache_Rules')) require_once(dirname(__FILE__) . '/class-wpo-cache-rules.php');
38 -
39 -if (!class_exists('Updraft_Abstract_Logger')) require_once(WPO_PLUGIN_MAIN_PATH.'includes/class-updraft-abstract-logger.php');
40 -if (!class_exists('Updraft_PHP_Logger')) require_once(WPO_PLUGIN_MAIN_PATH.'includes/class-updraft-php-logger.php');
41 -
42 25 require_once dirname(__FILE__) . '/file-based-page-cache-functions.php';
43 26
44 -if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
45 - require_once dirname(__FILE__) . '/php-5.3-functions.php';
46 -}
47 -
48 27 wpo_cache_load_extensions();
49 28
50 29 if (!class_exists('WPO_Page_Cache')) :
51 30
@@ -65,11 +44,18 @@
65 44 */
66 45 public $logger;
67 46
68 47 /**
48 + * File Logger for this class
49 + *
50 + * @var mixed
51 + */
52 + private $file_logger;
53 +
54 + /**
69 55 * Instance of this class
70 56 *
71 - * @var mixed
57 + * @var WPO_Page_Cache | null
72 58 */
73 59 public static $instance;
74 60
75 61 /**
@@ -101,17 +87,43 @@
101 87 */
102 88 private $_minimum_advanced_cache_file_version = '3.0.17';
103 89
104 90 /**
91 + * Flag to decide whether to purge or not
92 + *
93 + * @var bool
94 + */
95 + public $should_purge;
96 +
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 + /**
105 105 * Set everything up here
106 106 */
107 107 public function __construct() {
108 108 $this->config = WPO_Cache_Config::instance();
109 - $this->rules = WPO_Cache_Rules::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 +
116 + WPO_Cache_Rules::instance();
117 + WP_Optimize_Page_Cache_Preloader::instance();
118 +
110 119 $this->logger = new Updraft_PHP_Logger();
120 + $this->file_logger = new Updraft_File_Logger(WP_Optimize_Utils::get_log_file_path('cache'));
111 121
112 - add_action('activate_plugin', array($this, 'activate_deactivate_plugin'));
122 + add_action('activated_plugin', array($this, 'activate_deactivate_plugin'));
113 123 add_action('deactivate_plugin', array($this, 'activate_deactivate_plugin'));
124 + add_action('wpo_purge_old_cache', array($this, 'purge_old'));
125 + add_action('wpo_prune_cache_logs', array($this, 'prune_cache_logs'));
114 126
115 127 /**
116 128 * Regenerate config file on cache flush.
117 129 */
@@ -116,8 +128,10 @@
116 128 * Regenerate config file on cache flush.
117 129 */
118 130 add_action('wpo_cache_flush', array($this, 'update_cache_config'));
119 131 add_action('wpo_cache_flush', array($this, 'delete_cache_size_information'));
132 + add_action('wpo_cache_flush', array($this, 'update_cache_cleared_time'));
133 + add_action('update_option_permalink_structure', array($this, 'update_option_permalink_structure'), 10, 3);
120 134
121 135 // Add purge cache link to admin bar.
122 136 add_filter('wpo_cache_admin_bar_menu_items', array($this, 'admin_bar_purge_cache'), 20, 1);
123 137
@@ -125,24 +139,158 @@
125 139 add_action('wp_loaded', array($this, 'handle_purge_single_page_cache'));
126 140
127 141 add_action('admin_init', array($this, 'admin_init'));
128 142
143 + add_action('update_option_gmt_offset', array($this, 'update_gmt_offset_timezone_string_config'), 10, 3);
144 + add_action('update_option_timezone_string', array($this, 'update_gmt_offset_timezone_string_config'), 10, 3);
145 + add_action('update_option_date_format', array($this, 'update_option_date_format'), 10, 2);
146 + add_action('update_option_time_format', array($this, 'update_option_time_format'), 10, 2);
147 +
129 148 $this->check_compatibility_issues();
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');
130 159 }
131 160
132 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() {
189 + add_filter('cron_schedules', array($this, 'cron_schedules'));
190 + }
191 +
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 + /**
245 + * Activate cron job when the cache is enabled for deleting expired cache files
246 + */
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() {
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 + /**
133 282 * Do required actions on activate/deactivate any plugin.
134 283 */
135 284 public function activate_deactivate_plugin() {
136 285
286 + wp_clear_scheduled_hook('wpo_purge_old_cache');
287 +
137 288 $this->update_cache_config();
138 289
139 - /**
140 - * Filters whether activating / deactivating a plugin will purge the cache.
141 - */
142 - if (apply_filters('wpo_purge_page_cache_on_activate_deactivate_plugin', true)) {
143 - $this->purge();
144 - }
290 + $is_cache_purged = $this->purge();
291 +
292 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: plugin activation/deactivation");
145 293 }
146 294
147 295 /**
148 296 * Check if current user can purge cache.
@@ -149,10 +297,11 @@
149 297 *
150 298 * @return bool
151 299 */
152 300 public function can_purge_cache() {
153 - if (is_multisite()) return $this->is_enabled() && (current_user_can('manage_network_options') || WP_Optimize()->can_purge_the_cache());
154 - return $this->is_enabled() && (current_user_can('manage_options') || WP_Optimize()->can_purge_the_cache());
301 + if (!$this->is_enabled()) return false;
302 +
303 + return WP_Optimize_Utils::current_user_can_purge_cache();
155 304 }
156 305
157 306 /**
158 307 * Add Purge from cache in admin bar.
@@ -163,12 +312,13 @@
163 312 public function admin_bar_purge_cache($menu_items) {
164 313 global $pagenow;
165 314 if (!$this->can_purge_cache()) return $menu_items;
166 315
167 - $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();
168 317
169 318 $cache_size = $this->get_cache_size();
170 319 $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
320 + // translators: %d is the number of files in the cache
171 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>';
172 322
173 323 $menu_items[] = array(
174 324 'id' => 'wpo_cache_stats',
@@ -188,9 +338,9 @@
188 338 ),
189 339 'parent' => 'wpo_purge_cache',
190 340 );
191 341
192 - if (!is_admin() || 'post.php' == $pagenow) {
342 + if (!is_admin() || 'post.php' === $pagenow) {
193 343 $menu_items[] = array(
194 344 'id' => 'wpo_purge_this_page_cache',
195 345 'title' => __('Purge cache for this page', 'wp-optimize'),
196 346 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
@@ -212,22 +362,38 @@
212 362
213 363 if (!$this->can_purge_cache()) return;
214 364
215 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
216 368 if (isset($_GET['wpo_single_page_cache_purged'])) {
217 - $notice_function = $_GET['wpo_single_page_cache_purged'] ? 'notice_purge_single_page_cache_success' : 'notice_purge_single_page_cache_error';
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';
218 371 } else {
219 - $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';
220 374 }
375 + // phpcs:enable
221 376
222 - add_action('admin_notices', array($this, $notice_function));
377 + if (is_admin()) {
378 + $admin_notices_hook = is_network_admin() ? 'network_admin_notices' : 'admin_notices';
223 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 +
224 390 return;
225 391 }
226 392
227 393 if (!isset($_GET['_wpo_purge'])) return;
228 394
229 - 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')) {
230 396 $success = false;
231 397
232 398 if (is_admin()) {
233 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
@@ -232,55 +398,56 @@
232 398 if (is_admin()) {
233 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
234 400 if ($post > 0) {
235 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);
236 403 }
237 404 } else {
238 - $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__);
239 408 }
240 409
241 - // remove nonce from url and reload page.
242 - wp_redirect(add_query_arg('wpo_single_page_cache_purged', $success, remove_query_arg('_wpo_purge')));
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);
243 415 exit;
244 416
245 - } 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')) {
246 418 $success = self::purge();
419 + $this->maybe_set_preload_cron_job();
420 + if ($success) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
247 421
248 - // remove nonce from url and reload page.
249 - wp_redirect(add_query_arg('wpo_all_pages_cache_purged', $success, remove_query_arg('_wpo_purge')));
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);
250 427 exit;
251 428 }
252 429 }
253 430
254 431 /**
255 - * 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
256 438 */
257 - public function notice_purge_single_page_cache_success() {
258 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
259 - }
439 + private function maybe_set_preload_cron_job() {
440 + $wpo_page_cache_preloader = WP_Optimize_Page_Cache_Preloader::instance();
260 441
261 - /**
262 - * Show notification when page cache wasn't purged.
263 - */
264 - public function notice_purge_single_page_cache_error() {
265 - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
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 + }
266 447 }
267 -
448 +
268 449 /**
269 - * Show notification when all pages cache purged successfully.
270 - */
271 - public function notice_purge_all_pages_cache_success() {
272 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
273 - }
274 -
275 - /**
276 - * Show notification when all pages cache wasn't purged.
277 - */
278 - public function notice_purge_all_pages_cache_error() {
279 - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
280 - }
281 -
282 - /**
283 450 * Show notification in WordPress admin.
284 451 *
285 452 * @param string $message HTML (no further escaping is performed)
286 453 * @param string $type error, warning, success, or info
@@ -293,10 +460,10 @@
293 460 window.addEventListener('load', function() {
294 461 (function(wp) {
295 462 if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
296 463 wp.data.dispatch('core/notices').createNotice(
297 - '<?php echo $type; ?>',
298 - '<?php echo $message; ?>',
464 + '<?php echo esc_html($type); ?>',
465 + '<?php echo wp_kses_post($message); ?>',
299 466 {
300 467 isDismissible: true,
301 468 }
302 469 );
@@ -304,10 +471,10 @@
304 471 })(window.wp);
305 472 });
306 473 </script>
307 474 <?php else : ?>
308 - <div class="notice wpo-notice notice-<?php echo $type; ?> is-dismissible">
309 - <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>
310 477 </div>
311 478 <?php
312 479 endif;
313 480 }
@@ -319,8 +486,9 @@
319 486 *
320 487 * @return WP_Error|bool - true on success, error otherwise
321 488 */
322 489 public function enable($force_enable = false) {
490 + global $is_apache;
323 491 static $already_ran_enable = false;
324 492
325 493 if ($already_ran_enable) return $already_ran_enable;
326 494
@@ -329,16 +497,30 @@
329 497 $already_ran_enable = $folders_created;
330 498 return $already_ran_enable;
331 499 }
332 500
501 + if (!file_exists($this->config->get_config_file_path())) {
502 + $result = $this->update_cache_config();
503 + if (is_wp_error($result)) return $result;
504 + }
505 +
333 506 // if WPO_ADVANCED_CACHE isn't set, or environment doesn't contain the right constant, force regeneration
334 507 if (!defined('WPO_ADVANCED_CACHE') || !defined('WP_CACHE')) {
335 508 $force_enable = true;
336 509 }
337 510
511 + $this->maybe_regenerate_cache_config_file();
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 +
338 520 if (!$force_enable) {
339 - $already_ran_enable = true;
340 - return true;
521 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
522 + return $already_ran_enable;
341 523 }
342 524
343 525 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
344 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
@@ -344,11 +526,12 @@
344 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
345 527 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
346 528
347 529 if (!defined('WP_CLI') || !WP_CLI) {
348 - $message .= "\n\n".sprintf(__('1. Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
349 - $message .= "\n".__('2. Edit or create a file with the name advanced-cache.php', 'wp-optimize');
350 - $message .= "\n".__('3. Copy and paste the following lines into the file:', 'wp-optimize');
530 + // translators: %s is the path to advanced-cache.php folder
531 + $message .= "\n\n1. ".sprintf(__('Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
532 + $message .= "\n2. ".__('Edit or create a file with the name advanced-cache.php', 'wp-optimize');
533 + $message .= "\n3. ".__('Copy and paste the following lines into the file:', 'wp-optimize');
351 534 }
352 535
353 536 $already_ran_enable = new WP_Error("write_advanced_cache", $message);
354 537 return $already_ran_enable;
@@ -364,19 +547,21 @@
364 547 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
365 548 return $already_ran_enable;
366 549 }
367 550
368 - $already_ran_enable = true;
369 -
370 - return true;
551 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
552 + return $already_ran_enable;
371 553 }
372 554
373 555 /**
374 556 * Disables page cache
375 557 *
376 - * @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.
377 560 */
378 - public function disable() {
561 + public function disable($keep_option_enabled = false) {
562 + global $is_apache;
563 +
379 564 $ret = true;
380 565
381 566 $advanced_cache_file = $this->get_advanced_cache_filename();
382 567
@@ -390,9 +575,9 @@
390 575 $disabled_advanced_cache = true;
391 576 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
392 577 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
393 578 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
394 - if (file_exists($advanced_cache_file) && (!unlink($advanced_cache_file) && false === file_put_contents($advanced_cache_file, "<?php\n// WP-Optimize: page cache disabled"))) {
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"))) {
395 580 $disabled_advanced_cache = false;
396 581 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
397 582 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
398 583 }
@@ -403,14 +588,55 @@
403 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'));
404 589 }
405 590
406 591 // Delete cache to avoid stale cache on next activation
407 - $this->purge();
592 + $is_cache_purged = $this->purge();
593 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
408 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 +
409 615 return $ret;
410 616 }
411 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();
412 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 +
413 639 /**
414 640 * Purges the cache
415 641 *
416 642 * @return bool - true on success, false otherwise
@@ -415,8 +641,19 @@
415 641 *
416 642 * @return bool - true on success, false otherwise
417 643 */
418 644 public function purge() {
645 + /**
646 + * Filters whether activating / deactivating a plugin will purge the cache.
647 + */
648 + $this->should_purge = apply_filters('wpo_purge_page_cache_on_activate_deactivate_plugin', true);
649 + $purge_actions = array(
650 + 'deactivate_' . plugin_basename(WPO_PLUGIN_MAIN_PATH . 'wp-optimize.php'),
651 + 'deactivate_plugin',
652 + 'activated_plugin',
653 + );
654 + $is_deactivation_action = in_array(current_action(), $purge_actions, true);
655 + if ($is_deactivation_action && !$this->should_purge) return false;
419 656
420 657 if (!self::delete(WPO_CACHE_FILES_DIR)) {
421 658 $this->log("The request to the filesystem to delete the cache failed");
422 659 return false;
@@ -430,8 +667,62 @@
430 667 return true;
431 668 }
432 669
433 670 /**
671 + * Purge cache files older than cache life span
672 + *
673 + * @return array Log messages.
674 + */
675 + public function purge_old() {
676 + $log = array();
677 + $page_cache_length = $this->config->get_option('page_cache_length');
678 + if (0 === $page_cache_length) return $log;
679 + $expires = time() - $page_cache_length;
680 + $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
681 + // get all directories that are a direct child of current directory
682 + if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
683 + if ($handle = opendir($cache_folder)) {
684 + while (false !== ($d = readdir($handle))) {
685 + if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
686 + continue;
687 + }
688 +
689 + if ($this->is_front_page_cache($d)) {
690 + $modified_time = (int) filemtime("$cache_folder/$d");
691 + if ($modified_time <= $expires) {
692 + wp_delete_file("$cache_folder/$d");
693 + }
694 + continue;
695 + }
696 +
697 + $dir = $cache_folder.'/'.$d;
698 + if (!is_dir($dir)) continue;
699 +
700 + $stat = stat($dir);
701 + $modified_time = $stat['mtime'];
702 + $log[] = "checking if cache has expired - $d";
703 + if ($modified_time <= $expires) {
704 + $log[] = "deleting cache in $dir";
705 + wpo_delete_files($dir, true);
706 + if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
707 + }
708 + }
709 + closedir($handle);
710 + }
711 + }
712 + return $log;
713 + }
714 +
715 + /**
716 + * Finds out given cache file is of front page or not
717 + *
718 + * @return bool
719 + */
720 + private function is_front_page_cache($d) {
721 + return in_array($d, array('index.html', 'index.html.gz'));
722 + }
723 +
724 + /**
434 725 * Purges the cache
435 726 *
436 727 * @return bool - true on success, false otherwise
437 728 */
@@ -447,23 +738,39 @@
447 738 return true;
448 739 }
449 740
450 741 /**
451 - * Check if cache is enabled and working
742 + * Checks whether page caching is turned on in settings.
452 743 *
453 - * @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.
454 745 */
455 746 public function is_enabled() {
747 + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
748 + }
456 749
457 - if (!defined('WP_CACHE') || !WP_CACHE) {
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);
759 +
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");
458 762 return false;
459 763 }
460 764
461 - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
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");
462 767 return false;
463 768 }
464 769
465 - if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->get_cache_config_filename())) {
770 + $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
771 + if (!$is_cli_or_ajax && !file_exists($config_file)) {
772 + $this->file_log("Cache config file $config_file is not present");
466 773 return false;
467 774 }
468 775
469 776 return true;
@@ -471,23 +778,27 @@
471 778
472 779 /**
473 780 * Create the folder structure needed for cache to work
474 781 *
475 - * @return bool - true on success, false otherwise
782 + * @return bool | WP_Error - true on success, false otherwise
476 783 */
477 - private function create_folders() {
784 + public function create_folders() {
785 + $permissions_str = __('Please check your file permissions.', 'wp-optimize');
478 786
479 787 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
480 - return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s. Please check your file permissions.'), str_ireplace(ABSPATH, '', WPO_CACHE_DIR)));
788 + // translators: %s is a directory name
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);
481 790 }
482 791
483 792 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
484 - return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s. Please check your file permissions.'), str_ireplace(ABSPATH, '', WPO_CACHE_CONFIG_DIR)));
793 + // translators: %s is a directory name
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);
485 795 }
486 796
487 797 if (!is_dir(WPO_CACHE_FILES_DIR)) {
488 798 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
489 - return new WP_Error('create_folders', sprintf(__('The request to the filesystem failed: unable to create directory %s. Please check your file permissions.'), str_ireplace(ABSPATH, '', WPO_CACHE_FILES_DIR)));
799 + // translators: %s is a directory name
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);
490 801 } else {
491 802 wpo_disable_cache_directories_viewing();
492 803 }
493 804 }
@@ -504,23 +815,8 @@
504 815 return untrailingslashit(WP_CONTENT_DIR) . '/advanced-cache.php';
505 816 }
506 817
507 818 /**
508 - * Get advanced-cache.php file name with full path.
509 - *
510 - * @return string
511 - */
512 - public function get_cache_config_filename() {
513 - $url = parse_url(network_site_url());
514 -
515 - if (isset($url['port']) && '' != $url['port'] && 80 != $url['port']) {
516 - return 'config-'.$url['host'].'-port'.$url['port'].'.php';
517 - } else {
518 - return 'config-'.$url['host'].'.php';
519 - }
520 - }
521 -
522 - /**
523 819 * Writes advanced-cache.php
524 820 *
525 821 * @param boolean $update_required - Whether the update is required or not.
526 822 * @return bool
@@ -525,9 +821,9 @@
525 821 * @param boolean $update_required - Whether the update is required or not.
526 822 * @return bool
527 823 */
528 824 private function write_advanced_cache($update_required = false) {
529 - $config_file_basename = $this->get_cache_config_filename();
825 + $config_file_basename = $this->config->get_cache_config_filename();
530 826 $cache_file_basename = untrailingslashit(plugin_dir_path(__FILE__));
531 827 $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
532 828 $cache_path = '/wpo-cache';
533 829 $cache_files_path = '/cache/wpo-cache';
@@ -532,9 +828,10 @@
532 828 $cache_path = '/wpo-cache';
533 829 $cache_files_path = '/cache/wpo-cache';
534 830 $cache_extensions_path = WPO_CACHE_EXT_DIR;
535 831 $wpo_version = WPO_VERSION;
536 - $wpo_home_url = trailingslashit(home_url());
832 + $wpo_home_url = trailingslashit(network_home_url());
833 + $abspath = ABSPATH;
537 834
538 835 // CS does not like heredoc
539 836 // phpcs:disable
540 837 $this->advanced_cache_file_content = <<<EOF
@@ -541,9 +838,9 @@
541 838 <?php
542 839
543 840 if (!defined('ABSPATH')) die('No direct access allowed');
544 841
545 -// 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)
546 843
547 844 if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);
548 845
549 846 \$possible_plugin_locations = array(
@@ -592,10 +889,15 @@
592 889 if (empty(\$GLOBALS['wpo_cache_config'])) {
593 890 include_once(WPO_CACHE_CONFIG_DIR . '/$config_file_basename');
594 891 }
595 892
596 -if (empty(\$GLOBALS['wpo_cache_config']) || empty(\$GLOBALS['wpo_cache_config']['enable_page_caching'])) { return; }
893 +if (empty(\$GLOBALS['wpo_cache_config'])) {
894 + error_log('WP-Optimize: Caching failed because the configuration data could not be loaded from the config file.');
895 + return;
896 +}
597 897
898 +if (empty(\$GLOBALS['wpo_cache_config']['enable_page_caching'])) { return; }
899 +
598 900 if (false !== \$plugin_location) { include_once(\$plugin_location.'/file-based-page-cache.php'); }
599 901
600 902 EOF;
601 903
@@ -609,15 +911,15 @@
609 911 }
610 912
611 913 // check if we can't write the advanced cache file
612 914 // case 1: the directory is read-only and the file doesn't exist
613 - if (!is_file($advanced_cache_filename) && !is_writable(dirname($advanced_cache_filename))) {
915 + if (!is_file($advanced_cache_filename) && !wp_is_writable(dirname($advanced_cache_filename))) {
614 916 $this->advanced_cache_file_writing_error = true;
615 917 return false;
616 918 }
617 919
618 920 // case 2: the file already exists but it's read-only
619 - if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
921 + if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
620 922 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
621 923 $this->advanced_cache_file_writing_error = true;
622 924 return false;
623 925 } else {
@@ -648,11 +950,20 @@
648 950 }
649 951 }
650 952
651 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
652 955 $advanced_cache_current_version = $this->get_advanced_cache_version();
653 - if ($advanced_cache_current_version && version_compare($advanced_cache_current_version, $this->_minimum_advanced_cache_file_version, '>=')) return;
956 + $is_the_site_url_or_folder_changed = $this->is_the_site_url_or_folder_changed();
654 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 +
655 966 if (!$this->write_advanced_cache()) {
656 967 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
657 968 } else {
658 969 $this->update_cache_config();
@@ -659,8 +970,31 @@
659 970 }
660 971 }
661 972
662 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 + /**
663 997 * Show notification when advanced-cache.php could not be updated.
664 998 */
665 999 public function notice_advanced_cache_autoupdate_error() {
666 1000 $this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
@@ -705,8 +1039,13 @@
705 1039 if (!$config_path) {
706 1040 return false;
707 1041 }
708 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 +
709 1048 $config_file_string = file_get_contents($config_path);
710 1049
711 1050 // Config file is empty. Maybe couldn't read it?
712 1051 if (empty($config_file_string)) {
@@ -759,9 +1098,9 @@
759 1098 }
760 1099 $errors = 0;
761 1100
762 1101 // First check wp-config.php.
763 - if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
1102 + if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
764 1103 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
765 1104 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
766 1105 }
767 1106
@@ -767,9 +1106,9 @@
767 1106
768 1107 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
769 1108
770 1109 // Now check wp-content. We need to be able to create files of the same user as this file.
771 - if ((!file_exists($advanced_cache_file) || false === strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) && !is_writable($advanced_cache_file) && !is_writable(untrailingslashit(WP_CONTENT_DIR))) {
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))) {
772 1111 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
773 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'));
774 1113 $errors++;
775 1114 }
@@ -774,23 +1113,25 @@
774 1113 $errors++;
775 1114 }
776 1115
777 1116 if (file_exists(WPO_CACHE_FILES_DIR)) {
778 - if (!is_writable(WPO_CACHE_FILES_DIR)) {
1117 + if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
779 1118 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
1119 + // translators: %s is cache folder path
780 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));
781 1121 }
782 1122 }
783 1123
784 1124 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
785 - if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
1125 + if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
786 1126 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
787 1127 // If the config exists, only send a warning. Otherwise send an error.
788 1128 $type = 'warning';
789 - if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->get_cache_config_filename())) {
1129 + if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
790 1130 $type = 'error';
791 1131 $errors++;
792 1132 }
1133 + // translators: %s is cache config folder path
793 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);
794 1135 }
795 1136 }
796 1137
@@ -797,15 +1138,32 @@
797 1138 return !$errors;
798 1139 }
799 1140
800 1141 /**
1142 + * Update permalink structure in cache config
1143 + *
1144 + * @param string $old_value Old value of permalink_structure option
1145 + * @param string $value New value of permalink_structure option
1146 + * @param string $option Option name `permalink_structure`
1147 + */
1148 + public function update_option_permalink_structure($old_value, $value, $option) {
1149 + $current_config = $this->config->get();
1150 + if ($old_value !== $value) {
1151 + $current_config[$option] = $value;
1152 + $this->config->update($current_config, true);
1153 + }
1154 + }
1155 +
1156 + /**
801 1157 * Update cache config. Used to support 3d party plugins.
1158 + *
1159 + * @return bool | WP_Error
802 1160 */
803 1161 public function update_cache_config() {
804 1162 // get current cache settings.
805 1163 $current_config = $this->config->get();
806 1164 // and call update to change if need cookies and query variable names.
807 - $this->config->update($current_config, true);
1165 + return $this->config->update($current_config);
808 1166 }
809 1167
810 1168 /**
811 1169 * Delete information about cache size.
@@ -810,18 +1168,29 @@
810 1168 /**
811 1169 * Delete information about cache size.
812 1170 */
813 1171 public function delete_cache_size_information() {
1172 + //not removing delete_transient so it clear this key from option table
814 1173 delete_transient('wpo_get_cache_size');
1174 + delete_site_transient('wpo_get_cache_size');
815 1175 }
816 1176
817 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 + /**
818 1187 * Get current cache size.
819 1188 *
820 1189 * @return array
821 1190 */
822 1191 public function get_cache_size() {
823 - $cache_size = get_transient('wpo_get_cache_size');
1192 + $cache_size = get_site_transient('wpo_get_cache_size');
824 1193
825 1194 if (!empty($cache_size)) return $cache_size;
826 1195
827 1196 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
@@ -829,51 +1198,26 @@
829 1198 'size' => $infos['size'],
830 1199 'file_count' => $infos['file_count']
831 1200 );
832 1201
833 - 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);
834 1205
835 1206 return $cache_size;
836 1207 }
837 1208
838 1209 /**
839 - * Fetch directory informations.
1210 + * Fetch directory information.
840 1211 *
841 1212 * @param string $dir
842 1213 * @return array
843 1214 */
844 1215 private function get_dir_infos($dir) {
845 - $dir_size = 0;
846 - $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();
847 1218
848 - $handle = is_dir($dir) ? opendir($dir) : false;
849 -
850 - if (false === $handle) {
851 - return array('size' => 0, 'file_count' => 0);
852 - }
853 -
854 - $file = readdir($handle);
855 -
856 - while (false !== $file) {
857 -
858 - if ('.' != $file && '..' != $file) {
859 - $current_file = $dir.'/'.$file;
860 -
861 - if (is_dir($current_file)) {
862 - $sub_dir_infos = $this->get_dir_infos($current_file);
863 - $dir_size += $sub_dir_infos['size'];
864 - $file_count += $sub_dir_infos['file_count'];
865 - } elseif (is_file($current_file)) {
866 - $dir_size += filesize($current_file);
867 - $file_count++;
868 - }
869 - }
870 -
871 - $file = readdir($handle);
872 -
873 - }
874 -
875 - return array('size' => $dir_size, 'file_count' => $file_count);
1219 + return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
876 1220 }
877 1221
878 1222 /**
879 1223 * Returns the path to wp-config
@@ -905,11 +1249,9 @@
905 1249 * @param string $src
906 1250 * @return boolean
907 1251 */
908 1252 public static function delete($src) {
909 -
910 1253 return wpo_delete_files($src);
911 -
912 1254 }
913 1255
914 1256 /**
915 1257 * Delete cached files for specific url.
@@ -919,18 +1261,49 @@
919 1261 *
920 1262 * @return bool
921 1263 */
922 1264 public static function delete_cache_by_url($url, $recursive = false) {
923 - if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1265 + if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;
924 1266
925 1267 $path = self::get_full_path_from_url($url);
926 1268
927 1269 do_action('wpo_delete_cache_by_url', $url, $recursive);
928 1270
1271 + do_action('wpo_preload_url', $url);
1272 +
929 1273 return wpo_delete_files($path, $recursive);
930 1274 }
931 1275
932 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 + /**
933 1306 * Delete cached files for single post.
934 1307 *
935 1308 * @param integer $post_id The post ID
936 1309 *
@@ -936,11 +1309,26 @@
936 1309 *
937 1310 * @return bool
938 1311 */
939 1312 public static function delete_single_post_cache($post_id) {
940 -
941 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1313 + $is_cache_deleted = self::really_delete_single_post_cache($post_id);
942 1314
1315 + do_action('wpo_single_post_cache_deleted', $post_id);
1316 +
1317 + return $is_cache_deleted;
1318 + }
1319 +
1320 + /**
1321 + * Really delete cached files for single post.
1322 + *
1323 + * @param integer $post_id The post ID
1324 + *
1325 + * @return bool
1326 + */
1327 + public static function really_delete_single_post_cache($post_id) {
1328 +
1329 + if (!defined('WPO_CACHE_FILES_DIR')) return false;
1330 +
943 1331 $post_url = get_permalink($post_id);
944 1332
945 1333 $path = self::get_full_path_from_url($post_url);
946 1334
@@ -949,8 +1337,10 @@
949 1337 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
950 1338
951 1339 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
952 1340
1341 + do_action('wpo_preload_url', $post_url);
1342 +
953 1343 return wpo_delete_files($path, $recursive);
954 1344 }
955 1345
956 1346 /**
@@ -963,42 +1353,15 @@
963 1353 $homepage_url = get_home_url(get_current_blog_id());
964 1354
965 1355 $path = self::get_full_path_from_url($homepage_url);
966 1356
967 - do_action('wpo_delete_cache_by_url', $homepage_url, false);
1357 + $recursive = false;
968 1358
969 - wpo_delete_files($path, false);
970 - }
1359 + do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);
971 1360
972 - /**
973 - * Delete sitemap cahche.
974 - */
975 - public static function delete_sitemap_cache() {
976 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1361 + do_action('wpo_preload_url', $homepage_url);
977 1362
978 - $homepage_url = get_home_url(get_current_blog_id());
979 -
980 - $path = trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($homepage_url));
981 -
982 - if (!is_dir($path)) return;
983 -
984 - $handle = opendir($path);
985 -
986 - if (false !== $handle) {
987 - $file = readdir($handle);
988 -
989 - while (false !== $file) {
990 -
991 - if ('.' != $file && '..' != $file && is_dir($path . $file) && preg_match('/.*sitemap.*\.xml/i', $file)) {
992 - do_action('wpo_delete_cache_by_url', $path . $file, false);
993 - wpo_delete_files($path . $file, true);
994 - }
995 -
996 - $file = readdir($handle);
997 - }
998 - }
999 -
1000 - closedir($handle);
1363 + wpo_delete_files($path, $recursive);
1001 1364 }
1002 1365
1003 1366 /**
1004 1367 * Delete feed from cache.
@@ -1016,10 +1379,23 @@
1016 1379 }
1017 1380
1018 1381 /**
1019 1382 * Delete post feed from cache.
1383 + *
1384 + * @param int $post_id Post ID
1020 1385 */
1021 1386 public static function delete_post_feed_cache($post_id) {
1387 + self::really_delete_post_feed_cache($post_id);
1388 +
1389 + do_action('wpo_single_post_feed_cache_deleted', $post_id);
1390 + }
1391 +
1392 + /**
1393 + * Really delete post feed from cache.
1394 + *
1395 + * @param int $post_id Post ID
1396 + */
1397 + public static function really_delete_post_feed_cache($post_id) {
1022 1398 if (!defined('WPO_CACHE_FILES_DIR')) return;
1023 1399
1024 1400 $post_url = get_permalink($post_id);
1025 1401
@@ -1046,9 +1422,9 @@
1046 1422
1047 1423 // delete empty comments dir from the cache
1048 1424 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1049 1425 $path = self::get_full_path_from_url($comments_url);
1050 -
1426 +
1051 1427 if (wpo_is_empty_dir($path)) {
1052 1428 wpo_delete_files($path, true);
1053 1429 }
1054 1430 }
@@ -1058,9 +1434,9 @@
1058 1434 *
1059 1435 * @param string $url
1060 1436 * @return string
1061 1437 */
1062 - private static function get_full_path_from_url($url) {
1438 + public static function get_full_path_from_url($url) {
1063 1439 return trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($url));
1064 1440 }
1065 1441
1066 1442 /**
@@ -1069,10 +1445,11 @@
1069 1445 * @return void
1070 1446 */
1071 1447 public function admin_init() {
1072 1448 // Maybe update the advanced cache.
1073 - 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')) {
1074 1450 $this->maybe_update_advanced_cache();
1451 + $this->cron_activate();
1075 1452 }
1076 1453 }
1077 1454
1078 1455 /**
@@ -1083,10 +1460,8 @@
1083 1460 */
1084 1461 public function log($message) {
1085 1462 if (isset($this->logger)) {
1086 1463 $this->logger->log($message, 'error');
1087 - } else {
1088 - error_log($message);
1089 1464 }
1090 1465 }
1091 1466
1092 1467 /**
@@ -1091,9 +1466,9 @@
1091 1466
1092 1467 /**
1093 1468 * Returns an instance of the current class, creates one if it doesn't exist
1094 1469 *
1095 - * @return object
1470 + * @return self
1096 1471 */
1097 1472 public static function instance() {
1098 1473 if (empty(self::$instance)) {
1099 1474 self::$instance = new self();
@@ -1100,9 +1475,64 @@
1100 1475 }
1101 1476 return self::$instance;
1102 1477 }
1103 1478
1479 +
1104 1480 /**
1481 + * Update gmt_offset and timezone_string cache config value, used with hook `update_gmt_offset_timezone_string_config`.
1482 + *
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
1486 + *
1487 + * @return null
1488 + */
1489 + public function update_gmt_offset_timezone_string_config($old_value, $new_value, $option) {
1490 + if ('' === $new_value) return;
1491 +
1492 + $current_config = $this->config->get();
1493 + if ('timezone_string' === $option) {
1494 + $timeZone = new DateTimeZone($new_value);
1495 + $dateTime = new DateTime("now");
1496 + $gmt_offset = $timeZone->getOffset($dateTime);
1497 + $current_config['gmt_offset'] = round($gmt_offset/3600, 1);
1498 + $current_config['timezone_string'] = $new_value;
1499 + } elseif ('' !== $new_value) {
1500 + $current_config['gmt_offset'] = $new_value;
1501 + $current_config['timezone_string'] = '';
1502 + }
1503 + $this->config->update($current_config, true);
1504 + }
1505 +
1506 + /**
1507 + * Update `date_format` cache config value, used with hook `update_option_date_format`.
1508 + *
1509 + * @param string $old_value Old date format
1510 + * @param string $new_value New date format
1511 + *
1512 + * @return void
1513 + */
1514 + public function update_option_date_format($old_value, $new_value) {
1515 + $current_config = $this->config->get();
1516 + $current_config['date_format'] = $new_value;
1517 + $this->config->update($current_config, true);
1518 + }
1519 +
1520 + /**
1521 + * Update `time_format` cache config value, used with hook `update_option_time_format`.
1522 + *
1523 + * @param string $old_value Old time format
1524 + * @param string $new_value New time format
1525 + *
1526 + * @return void
1527 + */
1528 + public function update_option_time_format($old_value, $new_value) {
1529 + $current_config = $this->config->get();
1530 + $current_config['time_format'] = $new_value;
1531 + $this->config->update($current_config, true);
1532 + }
1533 +
1534 + /**
1105 1535 * Adds an error to the error store
1106 1536 *
1107 1537 * @param string $code - The error code
1108 1538 * @param string $message - The error's message
@@ -1181,9 +1611,9 @@
1181 1611 public function is_pagespeedninja_gzip_active() {
1182 1612 if (!class_exists('PagespeedNinja')) return false;
1183 1613
1184 1614 $options = get_option('pagespeedninja_config');
1185 - $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'];
1186 1616
1187 1617 return $gzip;
1188 1618 }
1189 1619
@@ -1190,9 +1620,9 @@
1190 1620 /**
1191 1621 * Output PageSpeed Ninja Gzip notice.
1192 1622 */
1193 1623 public function show_pagespeedninja_gzip_notice() {
1194 - echo '<div id="wp-optimize-pagespeedninja-gzip-notice" class="error wpo-notice"><p><b>'.__('WP-Optimize:', 'wp-optimize').'</b> '.__('Please disable the feature "Gzip compression" in PageSpeed Ninja to prevent conflicts.', 'wp-optimize').'</p></div>';
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>';
1195 1625 }
1196 1626
1197 1627 /**
1198 1628 * Check if Far Future Expiration is active and GZIP compression option is enabled.
@@ -1202,9 +1632,9 @@
1202 1632 public function is_farfutureexpiration_gzip_active() {
1203 1633 if (!class_exists('farFutureExpiration')) return false;
1204 1634
1205 1635 $options = get_option('far_future_expiration_settings');
1206 - $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1636 + $gzip = !empty($options) && $options['enable_gzip'];
1207 1637
1208 1638 return $gzip;
1209 1639 }
1210 1640
@@ -1211,9 +1641,9 @@
1211 1641 /**
1212 1642 * Output Far Future Expiration Gzip notice.
1213 1643 */
1214 1644 public function show_farfutureexpiration_gzip_notice() {
1215 - echo '<div id="wp-optimize-pagespeedninja-gzip-notice" class="error wpo-notice"><p><b>'.__('WP-Optimize:', 'wp-optimize').'</b> '.__('Please disable the feature "Gzip compression" in Far Future Expiration to prevent conflicts.', 'wp-optimize').'</p></div>';
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>';
1216 1646 }
1217 1647
1218 1648 /**
1219 1649 * This is a notice to show users that writing `advanced-cache.php` failed
@@ -1218,11 +1648,222 @@
1218 1648 /**
1219 1649 * This is a notice to show users that writing `advanced-cache.php` failed
1220 1650 */
1221 1651 public function show_admin_notice_advanced_cache() {
1652 + // translators: %s is advanced-cache.php file path
1222 1653 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1223 1654 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1224 1655 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1656 + }
1657 +
1658 + /**
1659 + * Scheduler public functions to update schedulers
1660 + *
1661 + * @param array $schedules An array of schedules being passed.
1662 + * @return array An array of schedules being returned.
1663 + */
1664 + public function cron_schedules($schedules) {
1665 + $page_cache_length = $this->config->get_option('page_cache_length');
1666 + // When the interval is less than `WP_CRON_LOCK_TIMEOUT` error messages are triggered
1667 + // So, instead of `0` (now) we are making it equal to the constant value.
1668 + // https://github.com/WordPress/WordPress/commit/84d19848666a81584e0007a6ab7f7ad3c990d71a
1669 + if (defined('WP_CRON_LOCK_TIMEOUT') && $page_cache_length < WP_CRON_LOCK_TIMEOUT) {
1670 + $page_cache_length = WP_CRON_LOCK_TIMEOUT;
1671 + }
1672 +
1673 + $schedules['wpo_purge_old_cache'] = array('interval' => false === $page_cache_length ? 86400 : $page_cache_length, 'display' => __('Every time after the cache has expired', 'wp-optimize'));
1674 +
1675 + $interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
1676 + $schedules['wpo_page_cache_preload_continue_interval'] = array(
1677 + 'interval' => $interval,
1678 + // translators: %d is the number of minutes
1679 + 'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
1680 + );
1681 +
1682 + $schedules['wpo_use_cache_lifespan'] = array(
1683 + 'interval' => $page_cache_length,
1684 + // translators: %s is the cache lifespan
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'))
1686 + );
1687 +
1688 + return $schedules;
1689 + }
1690 +
1691 + /**
1692 + * Update cache config file to reflect the webp images option
1693 + */
1694 + public function update_webp_images_option() {
1695 + if ($this->is_enabled()) {
1696 + $cache_settings = WPO_Cache_Config::instance()->get();
1697 + $cache_settings['use_webp_images'] = WP_Optimize()->get_options()->get_option('webp_conversion');
1698 + WPO_Cache_Config::instance()->update($cache_settings);
1699 + }
1700 + }
1701 +
1702 + /**
1703 + * Maybe regenerate cache config file, in case of migrations
1704 + */
1705 + public function maybe_regenerate_cache_config_file() {
1706 + $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1707 + if (!file_exists($config_file)) {
1708 + $config = $this->config->get();
1709 + $this->config->write($config);
1710 + }
1711 + }
1712 +
1713 + /**
1714 + * Checks if the given cache directory is empty
1715 + *
1716 + * @param string $path The path to the cache directory.
1717 + * @return bool Returns true if the cache directory is empty, false otherwise.
1718 + */
1719 + public static function is_cache_empty($path) {
1720 + if (!is_dir($path)) return true;
1721 +
1722 + if ($handle = opendir($path)) {
1723 + while (false !== ($entry = readdir($handle))) {
1724 + if ("." === $entry || ".." === $entry) {
1725 + continue;
1726 + }
1727 +
1728 + $full_path = $path . '/' . $entry;
1729 + if (is_file($full_path)) {
1730 + closedir($handle);
1731 + return false;
1732 + }
1733 + }
1734 + closedir($handle);
1735 + return true;
1736 + }
1737 + return true;
1738 + }
1739 +
1740 + /**
1741 + * Remove query params from request url
1742 + *
1743 + * @param string $url The request url.
1744 + * @return string Returns url without query params.
1745 + */
1746 + private static function remove_query_params($url) {
1747 + $parsed_url = wp_parse_url($url);
1748 + if (!$parsed_url) return '';
1749 + return $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'];
1750 + }
1751 +
1752 + /**
1753 + * Check configuration for auto preloading after cache deletion
1754 + *
1755 + * @return bool
1756 + */
1757 + public function should_auto_preload_purged_contents() {
1758 +
1759 + if (!$this->is_enabled()) return false; // as Page Cache is not enabled we should not auto preload purged content.
1760 +
1761 + $wpo_cache = WP_Optimize()->get_page_cache();
1762 + $wpo_cache_options = $wpo_cache->config->get();
1763 +
1764 + return $wpo_cache_options['auto_preload_purged_contents'];
1765 + }
1766 +
1767 + /**
1768 + * Logging of interesting messages related to Cache Purging
1769 + *
1770 + * @param string $message
1771 + * @param int $post_id
1772 + */
1773 + public function file_log($message, $post_id = 0) {
1774 + if (!empty($post_id)) {
1775 + if (false !== strpos($message, '{{URL}}')) {
1776 + $message = str_replace('{{URL}}', get_permalink($post_id), $message);
1777 + }
1778 + if (false !== strpos($message, '{{title}}')) {
1779 + $message = str_replace('{{title}}', '"' . get_the_title($post_id) . '"', $message);
1780 + }
1781 + }
1782 + $this->file_logger->info($message);
1783 + }
1784 +
1785 + /**
1786 + * Prunes the log file
1787 + */
1788 + public function prune_cache_logs() {
1789 + $this->file_log("Pruning the Cache log file");
1790 + $this->file_logger->prune_logs();
1791 + }
1792 +
1793 + /**
1794 + * Update .htaccess file to serve cached files directly.
1795 + *
1796 + * @param bool $enable
1797 + */
1798 + public function update_serve_cache_rules_htaccess_section($enable) {
1799 + $htaccess = new WP_Optimize_Htaccess();
1800 +
1801 + $section_title = 'WP-Optimize rules to serve cached files';
1802 +
1803 + if ($enable) {
1804 + if (!$htaccess->is_commented_section_exists($section_title)) {
1805 + $htaccess->update_commented_section($this->prepare_apache_cache_handle_section(), $section_title, true);
1806 + $htaccess->write_file();
1807 + }
1808 + } elseif ($htaccess->is_commented_section_exists($section_title)) {
1809 + $htaccess->remove_commented_section($section_title);
1810 + $htaccess->write_file();
1811 + }
1812 +
1813 + }
1814 +
1815 + /**
1816 + * Get the .htaccess section with rules to serve cached files
1817 + *
1818 + * @return array
1819 + */
1820 + private function prepare_apache_cache_handle_section() {
1821 +
1822 + $site_root = $this->get_site_root_path();
1823 +
1824 + return array(
1825 + array(
1826 + '<IfModule mod_rewrite.c>',
1827 + 'RewriteEngine On',
1828 + 'RewriteCond %{REQUEST_METHOD} GET|HEAD',
1829 + 'RewriteCond %{QUERY_STRING} =""',
1830 + 'RewriteCond %{HTTP:Cookie} =""',
1831 + 'RewriteCond %{REQUEST_URI} !^/(wp-(?:admin|login|register|comments-post|cron|json))/ [NC]',
1832 + 'RewriteCond %{HTTP_USER_AGENT} !(android|blackberry|ipad|iphone|ipod|windows.phone|iEMobile|opera.mini|opera.mobile|webos|symbian|windows.mobile|kindle|googlebot.mobile) [NC]',
1833 + 'RewriteCond %{DOCUMENT_ROOT}'.$site_root.'wp-content/cache/wpo-cache/%{HTTP_HOST}%{REQUEST_URI}index.html -f',
1834 + 'RewriteRule ^(.*)$ '.$site_root.'wp-content/cache/wpo-cache/%{HTTP_HOST}%{REQUEST_URI}index.html [L]',
1835 + array(
1836 + '<IfModule mod_setenvif.c>',
1837 + 'SetEnvIf Request_URI "/wp-content/cache/wpo-cache/" FROM_CACHE=1',
1838 + '</IfModule>',
1839 + ),
1840 + array(
1841 + '<IfModule mod_headers.c>',
1842 + 'Header always set X-WP-Optimize-Cache-Header "Loaded from WP-Optimize cache" env=FROM_CACHE',
1843 + '</IfModule>',
1844 + ),
1845 + '</IfModule>',
1846 + ),
1847 + );
1848 + }
1849 +
1850 + /**
1851 + * Get relative site root path, if site is placed in the subdirectory then it returns /subdirectory otherwise /
1852 + *
1853 + * @param string $default
1854 + *
1855 + * @return string
1856 + */
1857 + private function get_site_root_path($default = '/') {
1858 + $site_root = wp_parse_url(site_url());
1859 + if (isset($site_root['path'])) {
1860 + $site_root = trailingslashit($site_root['path']);
1861 + } else {
1862 + $site_root = $default;
1863 + }
1864 +
1865 + return $site_root;
1225 1866 }
1226 1867 }
1227 1868
1228 1869 endif;