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 +718 -197 3.2.9 → 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,8 +497,13 @@
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 }
@@ -341,11 +509,18 @@
341 509 }
342 510
343 511 $this->maybe_regenerate_cache_config_file();
344 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 +
345 520 if (!$force_enable) {
346 - $already_ran_enable = true;
347 - return true;
521 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
522 + return $already_ran_enable;
348 523 }
349 524
350 525 if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
351 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
@@ -351,11 +526,12 @@
351 526 $message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
352 527 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
353 528
354 529 if (!defined('WP_CLI') || !WP_CLI) {
355 - $message .= "\n\n".sprintf(__('1. Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
356 - $message .= "\n".__('2. Edit or create a file with the name advanced-cache.php', 'wp-optimize');
357 - $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');
358 534 }
359 535
360 536 $already_ran_enable = new WP_Error("write_advanced_cache", $message);
361 537 return $already_ran_enable;
@@ -371,19 +547,21 @@
371 547 $already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
372 548 return $already_ran_enable;
373 549 }
374 550
375 - $already_ran_enable = true;
376 -
377 - return true;
551 + $already_ran_enable = $this->update_page_cache_enabled_state(true);
552 + return $already_ran_enable;
378 553 }
379 554
380 555 /**
381 556 * Disables page cache
382 557 *
383 - * @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.
384 560 */
385 - public function disable() {
561 + public function disable($keep_option_enabled = false) {
562 + global $is_apache;
563 +
386 564 $ret = true;
387 565
388 566 $advanced_cache_file = $this->get_advanced_cache_filename();
389 567
@@ -389,15 +567,8 @@
389 567
390 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.
391 569 $disabled_wp_config = $this->write_wp_config(false);
392 570 if (!$disabled_wp_config) {
393 - $plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
394 - $action = "deactivate_".$plugin_basename."/wp-optimize.php";
395 - if (current_action() === $action) {
396 - $cache_config = WPO_Cache_Config::instance()->config;
397 - $cache_config['enable_page_caching'] = false;
398 - WPO_Cache_Config::instance()->update($cache_config, true);
399 - }
400 571 $this->log("Could not turn off the WP_CACHE constant in wp-config.php");
401 572 $this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
402 573 }
403 574
@@ -404,9 +575,9 @@
404 575 $disabled_advanced_cache = true;
405 576 // First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
406 577 // We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
407 578 if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
408 - 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"))) {
409 580 $disabled_advanced_cache = false;
410 581 $this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
411 582 $this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
412 583 }
@@ -417,14 +588,55 @@
417 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'));
418 589 }
419 590
420 591 // Delete cache to avoid stale cache on next activation
421 - $this->purge();
592 + $is_cache_purged = $this->purge();
593 + if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
422 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 +
423 615 return $ret;
424 616 }
425 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();
426 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 +
427 639 /**
428 640 * Purges the cache
429 641 *
430 642 * @return bool - true on success, false otherwise
@@ -429,8 +641,19 @@
429 641 *
430 642 * @return bool - true on success, false otherwise
431 643 */
432 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;
433 656
434 657 if (!self::delete(WPO_CACHE_FILES_DIR)) {
435 658 $this->log("The request to the filesystem to delete the cache failed");
436 659 return false;
@@ -445,19 +668,22 @@
445 668 }
446 669
447 670 /**
448 671 * Purge cache files older than cache life span
672 + *
673 + * @return array Log messages.
449 674 */
450 675 public function purge_old() {
676 + $log = array();
451 677 $page_cache_length = $this->config->get_option('page_cache_length');
678 + if (0 === $page_cache_length) return $log;
452 679 $expires = time() - $page_cache_length;
453 - $log = array();
454 680 $cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
455 681 // get all directories that are a direct child of current directory
456 - if (is_dir($cache_folder) && is_writable($cache_folder)) {
682 + if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
457 683 if ($handle = opendir($cache_folder)) {
458 684 while (false !== ($d = readdir($handle))) {
459 - if (0 == strcmp($d, '.') || 0 == strcmp($d, '..')) {
685 + if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
460 686 continue;
461 687 }
462 688
463 689 if ($this->is_front_page_cache($d)) {
@@ -462,9 +688,9 @@
462 688
463 689 if ($this->is_front_page_cache($d)) {
464 690 $modified_time = (int) filemtime("$cache_folder/$d");
465 691 if ($modified_time <= $expires) {
466 - unlink("$cache_folder/$d");
692 + wp_delete_file("$cache_folder/$d");
467 693 }
468 694 continue;
469 695 }
470 696
@@ -476,9 +702,9 @@
476 702 $log[] = "checking if cache has expired - $d";
477 703 if ($modified_time <= $expires) {
478 704 $log[] = "deleting cache in $dir";
479 705 wpo_delete_files($dir, true);
480 - if (file_exists($dir)) rmdir($dir);
706 + if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
481 707 }
482 708 }
483 709 closedir($handle);
484 710 }
@@ -512,33 +738,39 @@
512 738 return true;
513 739 }
514 740
515 741 /**
516 - * Check if cache is enabled and working
742 + * Checks whether page caching is turned on in settings.
517 743 *
518 - * @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.
519 745 */
520 746 public function is_enabled() {
747 + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
748 + }
521 749
522 - if (!$this->config->get_option('enable_page_caching')) return false;
750 + /**
751 + * Checks whether the cache delivery chain is actually intact and able to serve cached pages.
752 + *
753 + * Verifies the WP_CACHE and WPO_ADVANCED_CACHE constants are defined and true, and that the cache config file exists on disk.
754 + *
755 + * @return bool True if the delivery chain is functional, false otherwise.
756 + */
757 + private function is_environment_functional() {
758 + $is_cli_or_ajax = (defined('WP_CLI') && WP_CLI) || (defined('DOING_AJAX') && DOING_AJAX);
523 759
524 - if (!defined('WP_CACHE') || !WP_CACHE) {
525 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
526 - $this->log("WP_CACHE constant is not present in wp-config.php");
527 - return false;
528 - }
760 + if (!$is_cli_or_ajax && (!defined('WP_CACHE') || !WP_CACHE)) {
761 + $this->file_log("WP_CACHE constant is not present in wp-config.php");
762 + return false;
529 763 }
530 764
531 - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) {
532 - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
533 - $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
534 - return false;
535 - }
765 + if (!$is_cli_or_ajax && (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE)) {
766 + $this->file_log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
767 + return false;
536 768 }
537 769
538 770 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
539 - if (!file_exists($config_file)) {
540 - $this->log("Cache config file $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");
541 773 return false;
542 774 }
543 775
544 776 return true;
@@ -546,23 +778,27 @@
546 778
547 779 /**
548 780 * Create the folder structure needed for cache to work
549 781 *
550 - * @return bool - true on success, false otherwise
782 + * @return bool | WP_Error - true on success, false otherwise
551 783 */
552 784 public function create_folders() {
785 + $permissions_str = __('Please check your file permissions.', 'wp-optimize');
553 786
554 787 if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
555 - 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);
556 790 }
557 791
558 792 if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
559 - 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);
560 795 }
561 796
562 797 if (!is_dir(WPO_CACHE_FILES_DIR)) {
563 798 if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
564 - 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);
565 801 } else {
566 802 wpo_disable_cache_directories_viewing();
567 803 }
568 804 }
@@ -592,9 +828,10 @@
592 828 $cache_path = '/wpo-cache';
593 829 $cache_files_path = '/cache/wpo-cache';
594 830 $cache_extensions_path = WPO_CACHE_EXT_DIR;
595 831 $wpo_version = WPO_VERSION;
596 - $wpo_home_url = trailingslashit(home_url());
832 + $wpo_home_url = trailingslashit(network_home_url());
833 + $abspath = ABSPATH;
597 834
598 835 // CS does not like heredoc
599 836 // phpcs:disable
600 837 $this->advanced_cache_file_content = <<<EOF
@@ -601,9 +838,9 @@
601 838 <?php
602 839
603 840 if (!defined('ABSPATH')) die('No direct access allowed');
604 841
605 -// 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)
606 843
607 844 if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);
608 845
609 846 \$possible_plugin_locations = array(
@@ -652,10 +889,15 @@
652 889 if (empty(\$GLOBALS['wpo_cache_config'])) {
653 890 include_once(WPO_CACHE_CONFIG_DIR . '/$config_file_basename');
654 891 }
655 892
656 -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 +}
657 897
898 +if (empty(\$GLOBALS['wpo_cache_config']['enable_page_caching'])) { return; }
899 +
658 900 if (false !== \$plugin_location) { include_once(\$plugin_location.'/file-based-page-cache.php'); }
659 901
660 902 EOF;
661 903
@@ -669,15 +911,15 @@
669 911 }
670 912
671 913 // check if we can't write the advanced cache file
672 914 // case 1: the directory is read-only and the file doesn't exist
673 - 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))) {
674 916 $this->advanced_cache_file_writing_error = true;
675 917 return false;
676 918 }
677 919
678 920 // case 2: the file already exists but it's read-only
679 - if (is_file($advanced_cache_filename) && !is_writable($advanced_cache_filename)) {
921 + if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
680 922 if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
681 923 $this->advanced_cache_file_writing_error = true;
682 924 return false;
683 925 } else {
@@ -708,11 +950,20 @@
708 950 }
709 951 }
710 952
711 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
712 955 $advanced_cache_current_version = $this->get_advanced_cache_version();
713 - 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();
714 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 +
715 966 if (!$this->write_advanced_cache()) {
716 967 add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
717 968 } else {
718 969 $this->update_cache_config();
@@ -719,8 +970,31 @@
719 970 }
720 971 }
721 972
722 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 + /**
723 997 * Show notification when advanced-cache.php could not be updated.
724 998 */
725 999 public function notice_advanced_cache_autoupdate_error() {
726 1000 $this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
@@ -765,8 +1039,13 @@
765 1039 if (!$config_path) {
766 1040 return false;
767 1041 }
768 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 +
769 1048 $config_file_string = file_get_contents($config_path);
770 1049
771 1050 // Config file is empty. Maybe couldn't read it?
772 1051 if (empty($config_file_string)) {
@@ -819,9 +1098,9 @@
819 1098 }
820 1099 $errors = 0;
821 1100
822 1101 // First check wp-config.php.
823 - if (!$this->_get_wp_config() && !is_writable($this->_get_wp_config())) {
1102 + if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
824 1103 $this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
825 1104 $this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
826 1105 }
827 1106
@@ -827,9 +1106,9 @@
827 1106
828 1107 $advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
829 1108
830 1109 // Now check wp-content. We need to be able to create files of the same user as this file.
831 - 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))) {
832 1111 $this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
833 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'));
834 1113 $errors++;
835 1114 }
@@ -834,16 +1113,17 @@
834 1113 $errors++;
835 1114 }
836 1115
837 1116 if (file_exists(WPO_CACHE_FILES_DIR)) {
838 - if (!is_writable(WPO_CACHE_FILES_DIR)) {
1117 + if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
839 1118 $this->log("Unable to write inside the cache files folder; please check file/folder permissions");
1119 + // translators: %s is cache folder path
840 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));
841 1121 }
842 1122 }
843 1123
844 1124 if (file_exists(WPO_CACHE_CONFIG_DIR)) {
845 - if (!is_writable(WPO_CACHE_CONFIG_DIR)) {
1125 + if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
846 1126 $this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
847 1127 // If the config exists, only send a warning. Otherwise send an error.
848 1128 $type = 'warning';
849 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
@@ -849,8 +1129,9 @@
849 1129 if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
850 1130 $type = 'error';
851 1131 $errors++;
852 1132 }
1133 + // translators: %s is cache config folder path
853 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);
854 1135 }
855 1136 }
856 1137
@@ -857,9 +1138,9 @@
857 1138 return !$errors;
858 1139 }
859 1140
860 1141 /**
861 - * Update permalink strucutre in cache config
1142 + * Update permalink structure in cache config
862 1143 *
863 1144 * @param string $old_value Old value of permalink_structure option
864 1145 * @param string $value New value of permalink_structure option
865 1146 * @param string $option Option name `permalink_structure`
@@ -865,9 +1146,9 @@
865 1146 * @param string $option Option name `permalink_structure`
866 1147 */
867 1148 public function update_option_permalink_structure($old_value, $value, $option) {
868 1149 $current_config = $this->config->get();
869 - if ($old_value != $value) {
1150 + if ($old_value !== $value) {
870 1151 $current_config[$option] = $value;
871 1152 $this->config->update($current_config, true);
872 1153 }
873 1154 }
@@ -873,14 +1154,16 @@
873 1154 }
874 1155
875 1156 /**
876 1157 * Update cache config. Used to support 3d party plugins.
1158 + *
1159 + * @return bool | WP_Error
877 1160 */
878 1161 public function update_cache_config() {
879 1162 // get current cache settings.
880 1163 $current_config = $this->config->get();
881 1164 // and call update to change if need cookies and query variable names.
882 - $this->config->update($current_config, true);
1165 + return $this->config->update($current_config);
883 1166 }
884 1167
885 1168 /**
886 1169 * Delete information about cache size.
@@ -885,18 +1168,29 @@
885 1168 /**
886 1169 * Delete information about cache size.
887 1170 */
888 1171 public function delete_cache_size_information() {
1172 + //not removing delete_transient so it clear this key from option table
889 1173 delete_transient('wpo_get_cache_size');
1174 + delete_site_transient('wpo_get_cache_size');
890 1175 }
891 1176
892 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 + /**
893 1187 * Get current cache size.
894 1188 *
895 1189 * @return array
896 1190 */
897 1191 public function get_cache_size() {
898 - $cache_size = get_transient('wpo_get_cache_size');
1192 + $cache_size = get_site_transient('wpo_get_cache_size');
899 1193
900 1194 if (!empty($cache_size)) return $cache_size;
901 1195
902 1196 $infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
@@ -904,51 +1198,26 @@
904 1198 'size' => $infos['size'],
905 1199 'file_count' => $infos['file_count']
906 1200 );
907 1201
908 - 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);
909 1205
910 1206 return $cache_size;
911 1207 }
912 1208
913 1209 /**
914 - * Fetch directory informations.
1210 + * Fetch directory information.
915 1211 *
916 1212 * @param string $dir
917 1213 * @return array
918 1214 */
919 1215 private function get_dir_infos($dir) {
920 - $dir_size = 0;
921 - $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();
922 1218
923 - $handle = is_dir($dir) ? opendir($dir) : false;
924 -
925 - if (false === $handle) {
926 - return array('size' => 0, 'file_count' => 0);
927 - }
928 -
929 - $file = readdir($handle);
930 -
931 - while (false !== $file) {
932 -
933 - if ('.' != $file && '..' != $file) {
934 - $current_file = $dir.'/'.$file;
935 -
936 - if (is_dir($current_file)) {
937 - $sub_dir_infos = $this->get_dir_infos($current_file);
938 - $dir_size += $sub_dir_infos['size'];
939 - $file_count += $sub_dir_infos['file_count'];
940 - } elseif (is_file($current_file)) {
941 - $dir_size += filesize($current_file);
942 - $file_count++;
943 - }
944 - }
945 -
946 - $file = readdir($handle);
947 -
948 - }
949 -
950 - return array('size' => $dir_size, 'file_count' => $file_count);
1219 + return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
951 1220 }
952 1221
953 1222 /**
954 1223 * Returns the path to wp-config
@@ -980,11 +1249,9 @@
980 1249 * @param string $src
981 1250 * @return boolean
982 1251 */
983 1252 public static function delete($src) {
984 -
985 1253 return wpo_delete_files($src);
986 -
987 1254 }
988 1255
989 1256 /**
990 1257 * Delete cached files for specific url.
@@ -994,18 +1261,49 @@
994 1261 *
995 1262 * @return bool
996 1263 */
997 1264 public static function delete_cache_by_url($url, $recursive = false) {
998 - if (!defined('WPO_CACHE_FILES_DIR') || '' == $url) return;
1265 + if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;
999 1266
1000 1267 $path = self::get_full_path_from_url($url);
1001 1268
1002 1269 do_action('wpo_delete_cache_by_url', $url, $recursive);
1003 1270
1271 + do_action('wpo_preload_url', $url);
1272 +
1004 1273 return wpo_delete_files($path, $recursive);
1005 1274 }
1006 1275
1007 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 + /**
1008 1306 * Delete cached files for single post.
1009 1307 *
1010 1308 * @param integer $post_id The post ID
1011 1309 *
@@ -1011,11 +1309,26 @@
1011 1309 *
1012 1310 * @return bool
1013 1311 */
1014 1312 public static function delete_single_post_cache($post_id) {
1015 -
1016 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1313 + $is_cache_deleted = self::really_delete_single_post_cache($post_id);
1017 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 +
1018 1331 $post_url = get_permalink($post_id);
1019 1332
1020 1333 $path = self::get_full_path_from_url($post_url);
1021 1334
@@ -1024,8 +1337,10 @@
1024 1337 $recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;
1025 1338
1026 1339 do_action('wpo_delete_cache_by_url', $post_url, $recursive);
1027 1340
1341 + do_action('wpo_preload_url', $post_url);
1342 +
1028 1343 return wpo_delete_files($path, $recursive);
1029 1344 }
1030 1345
1031 1346 /**
@@ -1038,42 +1353,15 @@
1038 1353 $homepage_url = get_home_url(get_current_blog_id());
1039 1354
1040 1355 $path = self::get_full_path_from_url($homepage_url);
1041 1356
1042 - do_action('wpo_delete_cache_by_url', $homepage_url, false);
1357 + $recursive = false;
1043 1358
1044 - wpo_delete_files($path, false);
1045 - }
1359 + do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);
1046 1360
1047 - /**
1048 - * Delete sitemap cahche.
1049 - */
1050 - public static function delete_sitemap_cache() {
1051 - if (!defined('WPO_CACHE_FILES_DIR')) return;
1361 + do_action('wpo_preload_url', $homepage_url);
1052 1362
1053 - $homepage_url = get_home_url(get_current_blog_id());
1054 -
1055 - $path = trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($homepage_url));
1056 -
1057 - if (!is_dir($path)) return;
1058 -
1059 - $handle = opendir($path);
1060 -
1061 - if (false !== $handle) {
1062 - $file = readdir($handle);
1063 -
1064 - while (false !== $file) {
1065 -
1066 - if ('.' != $file && '..' != $file && is_dir($path . $file) && preg_match('/.*sitemap.*\.xml/i', $file)) {
1067 - do_action('wpo_delete_cache_by_url', $path . $file, false);
1068 - wpo_delete_files($path . $file, true);
1069 - }
1070 -
1071 - $file = readdir($handle);
1072 - }
1073 - }
1074 -
1075 - closedir($handle);
1363 + wpo_delete_files($path, $recursive);
1076 1364 }
1077 1365
1078 1366 /**
1079 1367 * Delete feed from cache.
@@ -1091,10 +1379,23 @@
1091 1379 }
1092 1380
1093 1381 /**
1094 1382 * Delete post feed from cache.
1383 + *
1384 + * @param int $post_id Post ID
1095 1385 */
1096 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) {
1097 1398 if (!defined('WPO_CACHE_FILES_DIR')) return;
1098 1399
1099 1400 $post_url = get_permalink($post_id);
1100 1401
@@ -1121,9 +1422,9 @@
1121 1422
1122 1423 // delete empty comments dir from the cache
1123 1424 $comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
1124 1425 $path = self::get_full_path_from_url($comments_url);
1125 -
1426 +
1126 1427 if (wpo_is_empty_dir($path)) {
1127 1428 wpo_delete_files($path, true);
1128 1429 }
1129 1430 }
@@ -1133,9 +1434,9 @@
1133 1434 *
1134 1435 * @param string $url
1135 1436 * @return string
1136 1437 */
1137 - private static function get_full_path_from_url($url) {
1438 + public static function get_full_path_from_url($url) {
1138 1439 return trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($url));
1139 1440 }
1140 1441
1141 1442 /**
@@ -1144,9 +1445,9 @@
1144 1445 * @return void
1145 1446 */
1146 1447 public function admin_init() {
1147 1448 // Maybe update the advanced cache.
1148 - 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')) {
1149 1450 $this->maybe_update_advanced_cache();
1150 1451 $this->cron_activate();
1151 1452 }
1152 1453 }
@@ -1165,9 +1466,9 @@
1165 1466
1166 1467 /**
1167 1468 * Returns an instance of the current class, creates one if it doesn't exist
1168 1469 *
1169 - * @return object
1470 + * @return self
1170 1471 */
1171 1472 public static function instance() {
1172 1473 if (empty(self::$instance)) {
1173 1474 self::$instance = new self();
@@ -1174,9 +1475,64 @@
1174 1475 }
1175 1476 return self::$instance;
1176 1477 }
1177 1478
1479 +
1178 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 + /**
1179 1535 * Adds an error to the error store
1180 1536 *
1181 1537 * @param string $code - The error code
1182 1538 * @param string $message - The error's message
@@ -1255,9 +1611,9 @@
1255 1611 public function is_pagespeedninja_gzip_active() {
1256 1612 if (!class_exists('PagespeedNinja')) return false;
1257 1613
1258 1614 $options = get_option('pagespeedninja_config');
1259 - $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'];
1260 1616
1261 1617 return $gzip;
1262 1618 }
1263 1619
@@ -1264,9 +1620,9 @@
1264 1620 /**
1265 1621 * Output PageSpeed Ninja Gzip notice.
1266 1622 */
1267 1623 public function show_pagespeedninja_gzip_notice() {
1268 - 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>';
1269 1625 }
1270 1626
1271 1627 /**
1272 1628 * Check if Far Future Expiration is active and GZIP compression option is enabled.
@@ -1276,9 +1632,9 @@
1276 1632 public function is_farfutureexpiration_gzip_active() {
1277 1633 if (!class_exists('farFutureExpiration')) return false;
1278 1634
1279 1635 $options = get_option('far_future_expiration_settings');
1280 - $gzip = !empty($options) ? (bool) $options['enable_gzip'] : false;
1636 + $gzip = !empty($options) && $options['enable_gzip'];
1281 1637
1282 1638 return $gzip;
1283 1639 }
1284 1640
@@ -1285,9 +1641,9 @@
1285 1641 /**
1286 1642 * Output Far Future Expiration Gzip notice.
1287 1643 */
1288 1644 public function show_farfutureexpiration_gzip_notice() {
1289 - 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>';
1290 1646 }
1291 1647
1292 1648 /**
1293 1649 * This is a notice to show users that writing `advanced-cache.php` failed
@@ -1292,8 +1648,9 @@
1292 1648 /**
1293 1649 * This is a notice to show users that writing `advanced-cache.php` failed
1294 1650 */
1295 1651 public function show_admin_notice_advanced_cache() {
1652 + // translators: %s is advanced-cache.php file path
1296 1653 $message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
1297 1654 $message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
1298 1655 WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
1299 1656 }
@@ -1305,18 +1662,27 @@
1305 1662 * @return array An array of schedules being returned.
1306 1663 */
1307 1664 public function cron_schedules($schedules) {
1308 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 +
1309 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'));
1310 1674
1311 1675 $interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
1312 1676 $schedules['wpo_page_cache_preload_continue_interval'] = array(
1313 1677 'interval' => $interval,
1678 + // translators: %d is the number of minutes
1314 1679 'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
1315 1680 );
1316 1681
1317 1682 $schedules['wpo_use_cache_lifespan'] = array(
1318 - 'interval' => WPO_Cache_Config::instance()->get_option('page_cache_length'),
1683 + 'interval' => $page_cache_length,
1684 + // translators: %s is the cache lifespan
1319 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'))
1320 1686 );
1321 1687
1322 1688 return $schedules;
@@ -1333,9 +1699,9 @@
1333 1699 }
1334 1700 }
1335 1701
1336 1702 /**
1337 - * May be regenerate cache config file, in case of migrations
1703 + * Maybe regenerate cache config file, in case of migrations
1338 1704 */
1339 1705 public function maybe_regenerate_cache_config_file() {
1340 1706 $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
1341 1707 if (!file_exists($config_file)) {
@@ -1341,8 +1707,163 @@
1341 1707 if (!file_exists($config_file)) {
1342 1708 $config = $this->config->get();
1343 1709 $this->config->write($config);
1344 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;
1345 1866 }
1346 1867 }
1347 1868
1348 1869 endif;