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 +741 -191 3.2.5 → 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 specifc 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 /**
@@ -89,18 +87,43 @@
89 87 */
90 88 private $_minimum_advanced_cache_file_version = '3.0.17';
91 89
92 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 + /**
93 105 * Set everything up here
94 106 */
95 107 public function __construct() {
96 108 $this->config = WPO_Cache_Config::instance();
97 - $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 +
98 119 $this->logger = new Updraft_PHP_Logger();
120 + $this->file_logger = new Updraft_File_Logger(WP_Optimize_Utils::get_log_file_path('cache'));
99 121
100 122 add_action('activated_plugin', array($this, 'activate_deactivate_plugin'));
101 123 add_action('deactivate_plugin', array($this, 'activate_deactivate_plugin'));
102 124 add_action('wpo_purge_old_cache', array($this, 'purge_old'));
125 + add_action('wpo_prune_cache_logs', array($this, 'prune_cache_logs'));
103 126
104 127 /**
105 128 * Regenerate config file on cache flush.
106 129 */
@@ -105,8 +128,9 @@
105 128 * Regenerate config file on cache flush.
106 129 */
107 130 add_action('wpo_cache_flush', array($this, 'update_cache_config'));
108 131 add_action('wpo_cache_flush', array($this, 'delete_cache_size_information'));
132 + add_action('wpo_cache_flush', array($this, 'update_cache_cleared_time'));
109 133 add_action('update_option_permalink_structure', array($this, 'update_option_permalink_structure'), 10, 3);
110 134
111 135 // Add purge cache link to admin bar.
112 136 add_filter('wpo_cache_admin_bar_menu_items', array($this, 'admin_bar_purge_cache'), 20, 1);
@@ -115,25 +139,147 @@
115 139 add_action('wp_loaded', array($this, 'handle_purge_single_page_cache'));
116 140
117 141 add_action('admin_init', array($this, 'admin_init'));
118 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 +
119 148 $this->check_compatibility_issues();
120 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() {
121 189 add_filter('cron_schedules', array($this, 'cron_schedules'));
122 - add_action('wpo_save_images_settings', array($this, 'update_webp_images_option'));
123 190 }
124 191
125 192 /**
126 - * Activate cron job for deleting expired cache files
193 + * Determines whether the current page should be cached.
194 + *
195 + * This method checks cache rules to identify if caching is possible.
196 + * If caching is not allowed, it adds appropriate HTTP headers and debug messages.
197 + *
198 + * @return bool|array True if the page should be cached, false otherwise or not empty array with the reasons why the page isn't cached
127 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 + */
128 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() {
129 261 $page_cache_length = $this->config->get_option('page_cache_length');
262 +
130 263 if (!wp_next_scheduled('wpo_purge_old_cache')) {
131 - wp_schedule_event(time() + (false === $page_cache_length ? '86400' : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
264 + wp_schedule_event(time() + (false === $page_cache_length ? 86400 : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
132 265 }
266 + if (!wp_next_scheduled('wpo_prune_cache_logs')) {
267 + wp_schedule_event(time(), 'weekly', 'wpo_prune_cache_logs');
268 + }
133 269 }
134 270
135 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 + /**
136 282 * Do required actions on activate/deactivate any plugin.
137 283 */
138 284 public function activate_deactivate_plugin() {
139 285
@@ -140,14 +286,11 @@
140 286 wp_clear_scheduled_hook('wpo_purge_old_cache');
141 287
142 288 $this->update_cache_config();
143 289
144 - /**
145 - * Filters whether activating / deactivating a plugin will purge the cache.
146 - */
147 - if (apply_filters('wpo_purge_page_cache_on_activate_deactivate_plugin', true)) {
148 - $this->purge();
149 - }
290 + $is_cache_purged = $this->purge();
291 +
292 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: plugin activation/deactivation");
150 293 }
151 294
152 295 /**
153 296 * Check if current user can purge cache.
@@ -154,10 +297,11 @@
154 297 *
155 298 * @return bool
156 299 */
157 300 public function can_purge_cache() {
158 - if (is_multisite()) return $this->is_enabled() && (current_user_can('manage_network_options') || WP_Optimize()->can_purge_the_cache());
159 - 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();
160 304 }
161 305
162 306 /**
163 307 * Add Purge from cache in admin bar.
@@ -168,12 +312,13 @@
168 312 public function admin_bar_purge_cache($menu_items) {
169 313 global $pagenow;
170 314 if (!$this->can_purge_cache()) return $menu_items;
171 315
172 - $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();
173 317
174 318 $cache_size = $this->get_cache_size();
175 319 $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
320 + // translators: %d is the number of files in the cache
176 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>';
177 322
178 323 $menu_items[] = array(
179 324 'id' => 'wpo_cache_stats',
@@ -193,9 +338,9 @@
193 338 ),
194 339 'parent' => 'wpo_purge_cache',
195 340 );
196 341
197 - if (!is_admin() || 'post.php' == $pagenow) {
342 + if (!is_admin() || 'post.php' === $pagenow) {
198 343 $menu_items[] = array(
199 344 'id' => 'wpo_purge_this_page_cache',
200 345 'title' => __('Purge cache for this page', 'wp-optimize'),
201 346 'href' => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
@@ -217,22 +362,38 @@
217 362
218 363 if (!$this->can_purge_cache()) return;
219 364
220 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
221 368 if (isset($_GET['wpo_single_page_cache_purged'])) {
222 - $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';
223 371 } else {
224 - $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';
225 374 }
375 + // phpcs:enable
226 376
227 - add_action('admin_notices', array($this, $notice_function));
377 + if (is_admin()) {
378 + $admin_notices_hook = is_network_admin() ? 'network_admin_notices' : 'admin_notices';
228 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 +
229 390 return;
230 391 }
231 392
232 393 if (!isset($_GET['_wpo_purge'])) return;
233 394
234 - 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')) {
235 396 $success = false;
236 397
237 398 if (is_admin()) {
238 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
@@ -237,55 +398,56 @@
237 398 if (is_admin()) {
238 399 $post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
239 400 if ($post > 0) {
240 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);
241 403 }
242 404 } else {
243 - $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__);
244 408 }
245 409
246 - // remove nonce from url and reload page.
247 - 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);
248 415 exit;
249 416
250 - } 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')) {
251 418 $success = self::purge();
419 + $this->maybe_set_preload_cron_job();
420 + if ($success) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
252 421
253 - // remove nonce from url and reload page.
254 - 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);
255 427 exit;
256 428 }
257 429 }
258 430
259 431 /**
260 - * 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
261 438 */
262 - public function notice_purge_single_page_cache_success() {
263 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
264 - }
439 + private function maybe_set_preload_cron_job() {
440 + $wpo_page_cache_preloader = WP_Optimize_Page_Cache_Preloader::instance();
265 441
266 - /**
267 - * Show notification when page cache wasn't purged.
268 - */
269 - public function notice_purge_single_page_cache_error() {
270 - $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 + }
271 447 }
272 -
448 +
273 449 /**
274 - * Show notification when all pages cache purged successfully.
275 - */
276 - public function notice_purge_all_pages_cache_success() {
277 - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success');
278 - }
279 -
280 - /**
281 - * Show notification when all pages cache wasn't purged.
282 - */
283 - public function notice_purge_all_pages_cache_error() {
284 - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error');
285 - }
286 -
287 - /**
288 450 * Show notification in WordPress admin.
289 451 *
290 452 * @param string $message HTML (no further escaping is performed)
291 453 * @param string $type error, warning, success, or info
@@ -298,10 +460,10 @@
298 460 window.addEventListener('load', function() {
299 461 (function(wp) {
300 462 if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
301 463 wp.data.dispatch('core/notices').createNotice(
302 - '<?php echo $type; ?>',
303 - '<?php echo $message; ?>',
464 + '<?php echo esc_html($type); ?>',
465 + '<?php echo wp_kses_post($message); ?>',
304 466 {
305 467 isDismissible: true,
306 468 }
307 469 );
@@ -309,10 +471,10 @@
309 471 })(window.wp);
310 472 });
311 473 </script>
312 474 <?php else : ?>
313 - <div class="notice wpo-notice notice-<?php echo $type; ?> is-dismissible">
314 - <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>
315 477 </div>
316 478 <?php
317 479 endif;
318 480 }
@@ -324,8 +486,9 @@
324 486 *
325 487 * @return WP_Error|bool - true on success, error otherwise
326 488 */
327 489 public function enable($force_enable = false) {
490 + global $is_apache;
328 491 static $already_ran_enable = false;
329 492
330 493 if ($already_ran_enable) return $already_ran_enable;
331 494
@@ -334,16 +497,30 @@
334 497 $already_ran_enable = $folders_created;
335 498 return $already_ran_enable;
336 499 }
337 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 +
338 506 // if WPO_ADVANCED_CACHE isn't set, or environment doesn't contain the right constant, force regeneration
339 507 if (!defined('WPO_ADVANCED_CACHE') || !defined('WP_CACHE')) {
340 508 $force_enable = true;
341 509 }
342 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 +
343 520 if (!$force_enable) {
344 - $already_ran_enable = true;
345 - return true;
521 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
522 + return $already_ran_enable;
346 523 }
347 524
348 525 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
349 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
@@ -349,11 +526,12 @@
349 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
350 527 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
351 528
352 529 if (!defined('WP_CLI') || !WP_CLI) {
353 - $message .= "\n\n".sprintf(__('1. Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
354 - $message .= "\n".__('2. Edit or create a file with the name advanced-cache.php', 'wp-optimize');
355 - $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');
356 534 }
357 535
358 536 $already_ran_enable = new WP_Error("write_advanced_cache", $message);
359 537 return $already_ran_enable;
@@ -369,19 +547,21 @@
369 547 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
370 548 return $already_ran_enable;
371 549 }
372 550
373 - $already_ran_enable = true;
374 -
375 - return true;
551 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
552 + return $already_ran_enable;
376 553 }
377 554
378 555 /**
379 556 * Disables page cache
380 557 *
381 - * @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.
382 560 */
383 - public function disable() {
561 + public function disable($keep_option_enabled = false) {
562 + global $is_apache;
563 +
384 564 $ret = true;
385 565
386 566 $advanced_cache_file = $this->get_advanced_cache_filename();
387 567
@@ -387,15 +567,8 @@
387 567
388 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.
389 569 $disabled_wp_config = $this->write_wp_config(false);
390 570 if (!$disabled_wp_config) {
391 - $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
392 - $action = "deactivate_".$plugin_basename."/wp-optimize.php";
393 - if (current_action() === $action) {
394 - $cache_config = WPO_Cache_Config::instance()->config;
395 - $cache_config['enable_page_caching'] = false;
396 - WPO_Cache_Config::instance()->update($cache_config, true);
397 - }
398 571 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
399 572 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
400 573 }
401 574
@@ -402,9 +575,9 @@
402 575 $disabled_advanced_cache = true;
403 576 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
404 577 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
405 578 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
406 - 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"))) {
407 580 $disabled_advanced_cache = false;
408 581 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
409 582 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
410 583 }
@@ -415,14 +588,55 @@
415 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'));
416 589 }
417 590
418 591 // Delete cache to avoid stale cache on next activation
419 - $this->purge();
592 + $is_cache_purged = $this->purge();
593 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
420 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 +
421 615 return $ret;
422 616 }
423 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();
424 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 +
425 639 /**
426 640 * Purges the cache
427 641 *
428 642 * @return bool - true on success, false otherwise
@@ -427,8 +641,19 @@
427 641 *
428 642 * @return bool - true on success, false otherwise
429 643 */
430 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;
431 656
432 657 if (!self::delete(WPO_CACHE_FILES_DIR)) {
433 658 $this->log("The request to the filesystem to delete the cache failed");
434 659 return false;
@@ -443,19 +668,22 @@
443 668 }
444 669
445 670 /**
446 671 * Purge cache files older than cache life span
672 + *
673 + * @return array Log messages.
447 674 */
448 675 public function purge_old() {
676 + $log = array();
449 677 $page_cache_length = $this->config->get_option('page_cache_length');
678 + if (0 === $page_cache_length) return $log;
450 679 $expires = time() - $page_cache_length;
451 - $log = array();
452 680 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
453 681 // get all directories that are a direct child of current directory
454 - if (is_dir($cache_folder) && is_writable($cache_folder)) {
682 + if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
455 683 if ($handle = opendir($cache_folder)) {
456 684 while (false !== ($d = readdir($handle))) {
457 - if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
685 + if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
458 686 continue;
459 687 }
460 688
461 689 if ($this->is_front_page_cache($d)) {
@@ -460,9 +688,9 @@
460 688
461 689 if ($this->is_front_page_cache($d)) {
462 690 $modified_time = (int) filemtime("$cache_folder/$d");
463 691 if ($modified_time <= $expires) {
464 - unlink("$cache_folder/$d");
692 + wp_delete_file("$cache_folder/$d");
465 693 }
466 694 continue;
467 695 }
468 696
@@ -474,9 +702,9 @@
474 702 $log[] = "checking if cache has expired - $d";
475 703 if ($modified_time <= $expires) {
476 704 $log[] = "deleting cache in $dir";
477 705 wpo_delete_files($dir, true);
478 - if (file_exists($dir)) rmdir($dir);
706 + if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
479 707 }
480 708 }
481 709 closedir($handle);
482 710 }
@@ -510,27 +738,39 @@
510 738 return true;
511 739 }
512 740
513 741 /**
514 - * Check if cache is enabled and working
742 + * Checks whether page caching is turned on in settings.
515 743 *
516 - * @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.
517 745 */
518 746 public function is_enabled() {
747 + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
748 + }
519 749
520 - if (!defined('WP_CACHE') || !WP_CACHE) {
521 - $this->log("WP_CACHE constant is not present in wp-config.php");
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");
522 762 return false;
523 763 }
524 764
525 - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
526 - $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
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");
527 767 return false;
528 768 }
529 769
530 770 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
531 - if (!file_exists($config_file)) {
532 - $this->log("$config_file is not present");
771 + if (!$is_cli_or_ajax && !file_exists($config_file)) {
772 + $this->file_log("Cache config file $config_file is not present");
533 773 return false;
534 774 }
535 775
536 776 return true;
@@ -538,23 +778,27 @@
538 778
539 779 /**
540 780 * Create the folder structure needed for cache to work
541 781 *
542 - * @return bool - true on success, false otherwise
782 + * @return bool | WP_Error - true on success, false otherwise
543 783 */
544 - private function create_folders() {
784 + public function create_folders() {
785 + $permissions_str = __('Please check your file permissions.', 'wp-optimize');
545 786
546 787 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
547 - 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);
548 790 }
549 791
550 792 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
551 - 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);
552 795 }
553 796
554 797 if (!is_dir(WPO_CACHE_FILES_DIR)) {
555 798 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
556 - 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);
557 801 } else {
558 802 wpo_disable_cache_directories_viewing();
559 803 }
560 804 }
@@ -584,9 +828,10 @@
584 828 $cache_path = '/wpo-cache';
585 829 $cache_files_path = '/cache/wpo-cache';
586 830 $cache_extensions_path = WPO_CACHE_EXT_DIR;
587 831 $wpo_version = WPO_VERSION;
588 - $wpo_home_url = trailingslashit(home_url());
832 + $wpo_home_url = trailingslashit(network_home_url());
833 + $abspath = ABSPATH;
589 834
590 835 // CS does not like heredoc
591 836 // phpcs:disable
592 837 $this->advanced_cache_file_content = <<<EOF
@@ -593,9 +838,9 @@
593 838 <?php
594 839
595 840 if (!defined('ABSPATH')) die('No direct access allowed');
596 841
597 -// 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)
598 843
599 844 if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);
600 845
601 846 \$possible_plugin_locations = array(
@@ -644,10 +889,15 @@
644 889 if (empty(\$GLOBALS['wpo_cache_config'])) {
645 890 include_once(WPO_CACHE_CONFIG_DIR . '/$config_file_basename');
646 891 }
647 892
648 -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 +}
649 897
898 +if (empty(\$GLOBALS['wpo_cache_config']['enable_page_caching'])) { return; }
899 +
650 900 if (false !== \$plugin_location) { include_once(\$plugin_location.'/file-based-page-cache.php'); }
651 901
652 902 EOF;
653 903
@@ -661,15 +911,15 @@
661 911 }
662 912
663 913 // check if we can't write the advanced cache file
664 914 // case 1: the directory is read-only and the file doesn't exist
665 - 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))) {
666 916 $this->advanced_cache_file_writing_error = true;
667 917 return false;
668 918 }
669 919
670 920 // case 2: the file already exists but it's read-only
671 - if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
921 + if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
672 922 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
673 923 $this->advanced_cache_file_writing_error = true;
674 924 return false;
675 925 } else {
@@ -700,11 +950,20 @@
700 950 }
701 951 }
702 952
703 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
704 955 $advanced_cache_current_version = $this->get_advanced_cache_version();
705 - 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();
706 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 +
707 966 if (!$this->write_advanced_cache()) {
708 967 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
709 968 } else {
710 969 $this->update_cache_config();
@@ -711,8 +970,31 @@
711 970 }
712 971 }
713 972
714 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 + /**
715 997 * Show notification when advanced-cache.php could not be updated.
716 998 */
717 999 public function notice_advanced_cache_autoupdate_error() {
718 1000 $this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
@@ -757,8 +1039,13 @@
757 1039 if (!$config_path) {
758 1040 return false;
759 1041 }
760 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 +
761 1048 $config_file_string = file_get_contents($config_path);
762 1049
763 1050 // Config file is empty. Maybe couldn't read it?
764 1051 if (empty($config_file_string)) {
@@ -811,9 +1098,9 @@
811 1098 }
812 1099 $errors = 0;
813 1100
814 1101 // First check wp-config.php.
815 - if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
1102 + if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
816 1103 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
817 1104 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
818 1105 }
819 1106
@@ -819,9 +1106,9 @@
819 1106
820 1107 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
821 1108
822 1109 // Now check wp-content. We need to be able to create files of the same user as this file.
823 - 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))) {
824 1111 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
825 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'));
826 1113 $errors++;
827 1114 }
@@ -826,16 +1113,17 @@
826 1113 $errors++;
827 1114 }
828 1115
829 1116 if (file_exists(WPO_CACHE_FILES_DIR)) {
830 - if (!is_writable(WPO_CACHE_FILES_DIR)) {
1117 + if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
831 1118 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
1119 + // translators: %s is cache folder path
832 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));
833 1121 }
834 1122 }
835 1123
836 1124 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
837 - if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
1125 + if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
838 1126 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
839 1127 // If the config exists, only send a warning. Otherwise send an error.
840 1128 $type = 'warning';
841 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
@@ -841,8 +1129,9 @@
841 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
842 1130 $type = 'error';
843 1131 $errors++;
844 1132 }
1133 + // translators: %s is cache config folder path
845 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);
846 1135 }
847 1136 }
848 1137
@@ -849,9 +1138,9 @@
849 1138 return !$errors;
850 1139 }
851 1140
852 1141 /**
853 - * Update permalink strucutre in cache config
1142 + * Update permalink structure in cache config
854 1143 *
855 1144 * @param string $old_value Old value of permalink_structure option
856 1145 * @param string $value New value of permalink_structure option
857 1146 * @param string $option Option name `permalink_structure`
@@ -857,9 +1146,9 @@
857 1146 * @param string $option Option name `permalink_structure`
858 1147 */
859 1148 public function update_option_permalink_structure($old_value, $value, $option) {
860 1149 $current_config = $this->config->get();
861 - if ($old_value != $value) {
1150 + if ($old_value !== $value) {
862 1151 $current_config[$option] = $value;
863 1152 $this->config->update($current_config, true);
864 1153 }
865 1154 }
@@ -865,14 +1154,16 @@
865 1154 }
866 1155
867 1156 /**
868 1157 * Update cache config. Used to support 3d party plugins.
1158 + *
1159 + * @return bool | WP_Error
869 1160 */
870 1161 public function update_cache_config() {
871 1162 // get current cache settings.
872 1163 $current_config = $this->config->get();
873 1164 // and call update to change if need cookies and query variable names.
874 - $this->config->update($current_config, true);
1165 + return $this->config->update($current_config);
875 1166 }
876 1167
877 1168 /**
878 1169 * Delete information about cache size.
@@ -877,18 +1168,29 @@
877 1168 /**
878 1169 * Delete information about cache size.
879 1170 */
880 1171 public function delete_cache_size_information() {
1172 + //not removing delete_transient so it clear this key from option table
881 1173 delete_transient('wpo_get_cache_size');
1174 + delete_site_transient('wpo_get_cache_size');
882 1175 }
883 1176
884 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 + /**
885 1187 * Get current cache size.
886 1188 *
887 1189 * @return array
888 1190 */
889 1191 public function get_cache_size() {
890 - $cache_size = get_transient('wpo_get_cache_size');
1192 + $cache_size = get_site_transient('wpo_get_cache_size');
891 1193
892 1194 if (!empty($cache_size)) return $cache_size;
893 1195
894 1196 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
@@ -896,51 +1198,26 @@
896 1198 'size' => $infos['size'],
897 1199 'file_count' => $infos['file_count']
898 1200 );
899 1201
900 - 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);
901 1205
902 1206 return $cache_size;
903 1207 }
904 1208
905 1209 /**
906 - * Fetch directory informations.
1210 + * Fetch directory information.
907 1211 *
908 1212 * @param string $dir
909 1213 * @return array
910 1214 */
911 1215 private function get_dir_infos($dir) {
912 - $dir_size = 0;
913 - $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();
914 1218
915 - $handle = is_dir($dir) ? opendir($dir) : false;
916 -
917 - if (false === $handle) {
918 - return array('size' => 0, 'file_count' => 0);
919 - }
920 -
921 - $file = readdir($handle);
922 -
923 - while (false !== $file) {
924 -
925 - if ('.' != $file && '..' != $file) {
926 - $current_file = $dir.'/'.$file;
927 -
928 - if (is_dir($current_file)) {
929 - $sub_dir_infos = $this->get_dir_infos($current_file);
930 - $dir_size += $sub_dir_infos['size'];
931 - $file_count += $sub_dir_infos['file_count'];
932 - } elseif (is_file($current_file)) {
933 - $dir_size += filesize($current_file);
934 - $file_count++;
935 - }
936 - }
937 -
938 - $file = readdir($handle);
939 -
940 - }
941 -
942 - return array('size' => $dir_size, 'file_count' => $file_count);
1219 + return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
943 1220 }
944 1221
945 1222 /**
946 1223 * Returns the path to wp-config
@@ -972,11 +1249,9 @@
972 1249 * @param string $src
973 1250 * @return boolean
974 1251 */
975 1252 public static function delete($src) {
976 -
977 1253 return wpo_delete_files($src);
978 -
979 1254 }
980 1255
981 1256 /**
982 1257 * Delete cached files for specific url.
@@ -986,18 +1261,49 @@
986 1261 *
987 1262 * @return bool
988 1263 */
989 1264 public static function delete_cache_by_url($url, $recursive = false) {
990 - if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1265 + if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;
991 1266
992 1267 $path = self::get_full_path_from_url($url);
993 1268
994 1269 do_action('wpo_delete_cache_by_url', $url, $recursive);
995 1270
1271 + do_action('wpo_preload_url', $url);
1272 +
996 1273 return wpo_delete_files($path, $recursive);
997 1274 }
998 1275
999 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 + /**
1000 1306 * Delete cached files for single post.
1001 1307 *
1002 1308 * @param integer $post_id The post ID
1003 1309 *
@@ -1003,11 +1309,26 @@
1003 1309 *
1004 1310 * @return bool
1005 1311 */
1006 1312 public static function delete_single_post_cache($post_id) {
1007 -
1008 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1313 + $is_cache_deleted = self::really_delete_single_post_cache($post_id);
1009 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 +
1010 1331 $post_url = get_permalink($post_id);
1011 1332
1012 1333 $path = self::get_full_path_from_url($post_url);
1013 1334
@@ -1016,8 +1337,10 @@
1016 1337 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1017 1338
1018 1339 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1019 1340
1341 + do_action('wpo_preload_url', $post_url);
1342 +
1020 1343 return wpo_delete_files($path, $recursive);
1021 1344 }
1022 1345
1023 1346 /**
@@ -1030,42 +1353,15 @@
1030 1353 $homepage_url = get_home_url(get_current_blog_id());
1031 1354
1032 1355 $path = self::get_full_path_from_url($homepage_url);
1033 1356
1034 - do_action('wpo_delete_cache_by_url', $homepage_url, false);
1357 + $recursive = false;
1035 1358
1036 - wpo_delete_files($path, false);
1037 - }
1359 + do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);
1038 1360
1039 - /**
1040 - * Delete sitemap cahche.
1041 - */
1042 - public static function delete_sitemap_cache() {
1043 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1361 + do_action('wpo_preload_url', $homepage_url);
1044 1362
1045 - $homepage_url = get_home_url(get_current_blog_id());
1046 -
1047 - $path = trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($homepage_url));
1048 -
1049 - if (!is_dir($path)) return;
1050 -
1051 - $handle = opendir($path);
1052 -
1053 - if (false !== $handle) {
1054 - $file = readdir($handle);
1055 -
1056 - while (false !== $file) {
1057 -
1058 - if ('.' != $file && '..' != $file && is_dir($path . $file) && preg_match('/.*sitemap.*\.xml/i', $file)) {
1059 - do_action('wpo_delete_cache_by_url', $path . $file, false);
1060 - wpo_delete_files($path . $file, true);
1061 - }
1062 -
1063 - $file = readdir($handle);
1064 - }
1065 - }
1066 -
1067 - closedir($handle);
1363 + wpo_delete_files($path, $recursive);
1068 1364 }
1069 1365
1070 1366 /**
1071 1367 * Delete feed from cache.
@@ -1083,10 +1379,23 @@
1083 1379 }
1084 1380
1085 1381 /**
1086 1382 * Delete post feed from cache.
1383 + *
1384 + * @param int $post_id Post ID
1087 1385 */
1088 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) {
1089 1398 if (!defined('WPO_CACHE_FILES_DIR')) return;
1090 1399
1091 1400 $post_url = get_permalink($post_id);
1092 1401
@@ -1113,9 +1422,9 @@
1113 1422
1114 1423 // delete empty comments dir from the cache
1115 1424 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1116 1425 $path = self::get_full_path_from_url($comments_url);
1117 -
1426 +
1118 1427 if (wpo_is_empty_dir($path)) {
1119 1428 wpo_delete_files($path, true);
1120 1429 }
1121 1430 }
@@ -1125,9 +1434,9 @@
1125 1434 *
1126 1435 * @param string $url
1127 1436 * @return string
1128 1437 */
1129 - private static function get_full_path_from_url($url) {
1438 + public static function get_full_path_from_url($url) {
1130 1439 return trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($url));
1131 1440 }
1132 1441
1133 1442 /**
@@ -1136,9 +1445,9 @@
1136 1445 * @return void
1137 1446 */
1138 1447 public function admin_init() {
1139 1448 // Maybe update the advanced cache.
1140 - 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')) {
1141 1450 $this->maybe_update_advanced_cache();
1142 1451 $this->cron_activate();
1143 1452 }
1144 1453 }
@@ -1151,10 +1460,8 @@
1151 1460 */
1152 1461 public function log($message) {
1153 1462 if (isset($this->logger)) {
1154 1463 $this->logger->log($message, 'error');
1155 - } else {
1156 - error_log($message);
1157 1464 }
1158 1465 }
1159 1466
1160 1467 /**
@@ -1159,9 +1466,9 @@
1159 1466
1160 1467 /**
1161 1468 * Returns an instance of the current class, creates one if it doesn't exist
1162 1469 *
1163 - * @return object
1470 + * @return self
1164 1471 */
1165 1472 public static function instance() {
1166 1473 if (empty(self::$instance)) {
1167 1474 self::$instance = new self();
@@ -1168,9 +1475,64 @@
1168 1475 }
1169 1476 return self::$instance;
1170 1477 }
1171 1478
1479 +
1172 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 + /**
1173 1535 * Adds an error to the error store
1174 1536 *
1175 1537 * @param string $code - The error code
1176 1538 * @param string $message - The error's message
@@ -1249,9 +1611,9 @@
1249 1611 public function is_pagespeedninja_gzip_active() {
1250 1612 if (!class_exists('PagespeedNinja')) return false;
1251 1613
1252 1614 $options = get_option('pagespeedninja_config');
1253 - $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'];
1254 1616
1255 1617 return $gzip;
1256 1618 }
1257 1619
@@ -1258,9 +1620,9 @@
1258 1620 /**
1259 1621 * Output PageSpeed Ninja Gzip notice.
1260 1622 */
1261 1623 public function show_pagespeedninja_gzip_notice() {
1262 - 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>';
1263 1625 }
1264 1626
1265 1627 /**
1266 1628 * Check if Far Future Expiration is active and GZIP compression option is enabled.
@@ -1270,9 +1632,9 @@
1270 1632 public function is_farfutureexpiration_gzip_active() {
1271 1633 if (!class_exists('farFutureExpiration')) return false;
1272 1634
1273 1635 $options = get_option('far_future_expiration_settings');
1274 - $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1636 + $gzip = !empty($options) && $options['enable_gzip'];
1275 1637
1276 1638 return $gzip;
1277 1639 }
1278 1640
@@ -1279,9 +1641,9 @@
1279 1641 /**
1280 1642 * Output Far Future Expiration Gzip notice.
1281 1643 */
1282 1644 public function show_farfutureexpiration_gzip_notice() {
1283 - 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>';
1284 1646 }
1285 1647
1286 1648 /**
1287 1649 * This is a notice to show users that writing `advanced-cache.php` failed
@@ -1286,8 +1648,9 @@
1286 1648 /**
1287 1649 * This is a notice to show users that writing `advanced-cache.php` failed
1288 1650 */
1289 1651 public function show_admin_notice_advanced_cache() {
1652 + // translators: %s is advanced-cache.php file path
1290 1653 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1291 1654 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1292 1655 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1293 1656 }
@@ -1299,9 +1662,30 @@
1299 1662 * @return array An array of schedules being returned.
1300 1663 */
1301 1664 public function cron_schedules($schedules) {
1302 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 +
1303 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 +
1304 1688 return $schedules;
1305 1689 }
1306 1690
1307 1691 /**
@@ -1312,8 +1696,174 @@
1312 1696 $cache_settings = WPO_Cache_Config::instance()->get();
1313 1697 $cache_settings['use_webp_images'] = WP_Optimize()->get_options()->get_option('webp_conversion');
1314 1698 WPO_Cache_Config::instance()->update($cache_settings);
1315 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;
1316 1866 }
1317 1867 }
1318 1868
1319 1869 endif;