# wp-optimize/4.7.0/cache/class-wpo-page-cache.php

WP-Optimize – Cache, Compress images, Minify &amp; Clean database to boost page speed &amp; performance, version 4.7.0. 1,870 lines.

- Page: https://pluginprobe.com/plugins/wp-optimize/4.7.0/code/cache/class-wpo-page-cache.php
- Raw: https://pluginprobe.com/plugins/wp-optimize/4.7.0/raw/cache/class-wpo-page-cache.php
- Modified: 2026-09-21T13:29:32+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/wp-optimize/4.7.0/code/cache/class-wpo-page-cache.php#L10-L20`.

```php
<?php
/**
 * Page caching functionality
 *
 * Acknowledgement: The page cache functionality was loosely based on the simple cache plugin - https://github.com/tlovett1/simple-cache
 */

if (!defined('ABSPATH')) die('No direct access allowed');

/**
 * Base cache directory, everything else goes under here
 */
if (!defined('WPO_CACHE_DIR')) define('WPO_CACHE_DIR', untrailingslashit(WP_CONTENT_DIR).'/wpo-cache');

/**
 * Extensions directory.
 */
if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', dirname(__FILE__).'/extensions');

/**
 * Directory that stores config and related files
 */
if (!defined('WPO_CACHE_CONFIG_DIR')) define('WPO_CACHE_CONFIG_DIR', WPO_CACHE_DIR.'/config');

require_once dirname(__FILE__) . '/file-based-page-cache-functions.php';

wpo_cache_load_extensions();

if (!class_exists('WPO_Page_Cache')) :

class WPO_Page_Cache {

	/**
	 * Cache config object
	 *
	 * @var mixed
	 */
	public $config;

	/**
	 * Logger for this class
	 *
	 * @var mixed
	 */
	public $logger;

	/**
	 * File Logger for this class
	 *
	 * @var mixed
	 */
	private $file_logger;

	/**
	 * Instance of this class
	 *
	 * @var WPO_Page_Cache | null
	 */
	public static $instance;

	/**
	 * Store last advanced cache file writing status
	 * If true then last writing finished with error
	 *
	 * @var bool
	 */
	public $advanced_cache_file_writing_error;

	/**
	 * Store errors
	 *
	 * @var array
	 */
	private $_errors = array();

	/**
	 * Last advanced cache file content
	 *
	 * @var string
	 */
	public $advanced_cache_file_content;

	/**
	 * Store the latest advanced-cache.php version required
	 *
	 * @var string
	 */
	private $_minimum_advanced_cache_file_version = '3.0.17';

	/**
	 * Flag to decide whether to purge or not
	 *
	 * @var bool
	 */
	public $should_purge;

	/**
	 * Flag to decide whether to purge or not
	 *
	 * @var bool
	 */
	private $files_to_ignore = array('index.php','.htaccess');

	/**
	 * Set everything up here
	 */
	public function __construct() {
		$this->config = WPO_Cache_Config::instance();
		if (empty($GLOBALS['wpo_cache_config'])) {
			$config_file_path = $this->config->get_config_file_path();
			if (file_exists($config_file_path)) {
				include_once($config_file_path);
			}
		}

		WPO_Cache_Rules::instance();
		WP_Optimize_Page_Cache_Preloader::instance();

		$this->logger = new Updraft_PHP_Logger();
		$this->file_logger = new Updraft_File_Logger(WP_Optimize_Utils::get_log_file_path('cache'));

		add_action('activated_plugin', array($this, 'activate_deactivate_plugin'));
		add_action('deactivate_plugin', array($this, 'activate_deactivate_plugin'));
		add_action('wpo_purge_old_cache', array($this, 'purge_old'));
		add_action('wpo_prune_cache_logs', array($this, 'prune_cache_logs'));

		/**
		 * Regenerate config file on cache flush.
		 */
		add_action('wpo_cache_flush', array($this, 'update_cache_config'));
		add_action('wpo_cache_flush', array($this, 'delete_cache_size_information'));
		add_action('wpo_cache_flush', array($this, 'update_cache_cleared_time'));
		add_action('update_option_permalink_structure', array($this, 'update_option_permalink_structure'), 10, 3);

		// Add purge cache link to admin bar.
		add_filter('wpo_cache_admin_bar_menu_items', array($this, 'admin_bar_purge_cache'), 20, 1);

		// Handle single page purge.
		add_action('wp_loaded', array($this, 'handle_purge_single_page_cache'));

		add_action('admin_init', array($this, 'admin_init'));

		add_action('update_option_gmt_offset', array($this, 'update_gmt_offset_timezone_string_config'), 10, 3);
		add_action('update_option_timezone_string', array($this, 'update_gmt_offset_timezone_string_config'), 10, 3);
		add_action('update_option_date_format', array($this, 'update_option_date_format'), 10, 2);
		add_action('update_option_time_format', array($this, 'update_option_time_format'), 10, 2);

		$this->check_compatibility_issues();

		$this->ensure_cron_schedules_filter_registered();

		add_action('wpo_save_images_settings', array($this, 'update_webp_images_option'));

		add_action('wpo_preload_url', array($this, 'maybe_preload_url'));

		// Setup filters for exceptions.
		add_filter('wpo_restricted_cache_page_type', 'wpo_restricted_cache_page_type');
		add_filter('wpo_url_in_conditional_tags_exceptions', 'wpo_url_in_conditional_tags_exceptions');
	}

	/**
	 * Ensures that the cron-related filter is registered.
	 *
	 * If `init` has already run, the filter is registered immediately.
	 * Otherwise, it schedules the registration to occur during `init`.
	 *
	 * @return void
	 */
	private function ensure_cron_schedules_filter_registered() {
		if (did_action('init')) {
			$this->register_cron_schedules_filter();
		} else {
			add_action('init', array($this, 'register_cron_schedules_filter'));
		}
	}

	/**
	 * Registers the custom cron schedule filter for WP-Cron.
	 *
	 * This method hooks the class's `cron_schedules()` method into WordPress's
	 * `cron_schedules` filter, allowing the plugin to define custom cron intervals.
	 *
	 * It is recommended to call this method on or after the `init` action to ensure
	 * that translations and other dependencies are properly loaded before the filter runs.
	 *
	 * @return void
	 */
	public function register_cron_schedules_filter() {
		add_filter('cron_schedules', array($this, 'cron_schedules'));
	}

	/**
	 * Determines whether the current page should be cached.
	 *
	 * This method checks cache rules to identify if caching is possible.
	 * If caching is not allowed, it adds appropriate HTTP headers and debug messages.
	 *
	 * @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
	 */
	public function should_cache_page() {

		if (!$this->is_enabled()) return false;

		return wpo_can_serve_from_cache();
	}

	/**
	 * Maybe add information about why the page is not cached to the headers and footer output
	 *
	 * @param array $no_cache_because
	 * @return void
	 */
	public function maybe_add_no_cache_because_info($no_cache_because) {
		// Add http header
		if (!wp_doing_cron() && !empty($no_cache_because)) {
			$no_cache_because_message = join(", ", $no_cache_because);
			wpo_cache_add_nocache_http_header_with_send_headers_action($no_cache_because_message);

			$not_cached_details = "";
			
			// Output the reason only when the user has turned on debugging
			if ((defined('WP_DEBUG') && WP_DEBUG) || !empty(TeamUpdraft\WP_Optimize\Includes\Fragments\fetch_superglobal('get', 'wpo_cache_debug'))) {
				$not_cached_details = "because: ".$no_cache_because_message;
			}
			wpo_cache_add_footer_output(sprintf("Page not served from cache %s", $not_cached_details));
		}
	}

	/**
	 * Handle cache settings update
	 *
	 * @param array $new_settings
	 * @return void
	 */
	public function cache_settings_updated($new_settings) {

		$this->unschedule_purge_old_cache();

		if ($new_settings['enable_page_caching']) {
			$this->schedule_purge_old_cache();
		}
	}

	/**
	 * Activate cron job when the cache is enabled for deleting expired cache files
	 */
	public function cron_activate() {
		if ($this->is_enabled()) {
			$this->schedule_purge_old_cache();
		} else {
			$this->unschedule_purge_old_cache();
		}
	}

	/**
	 * Schedules events to purge old cache
	 *
	 * @return void
	 */
	private function schedule_purge_old_cache() {
		$page_cache_length = $this->config->get_option('page_cache_length');

		if (!wp_next_scheduled('wpo_purge_old_cache')) {
			wp_schedule_event(time() + (false === $page_cache_length ? 86400 : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache');
		}
		if (!wp_next_scheduled('wpo_prune_cache_logs')) {
			wp_schedule_event(time(), 'weekly', 'wpo_prune_cache_logs');
		}
	}

	/**
	 * Unschedules events to purge old cache
	 *
	 * @return void
	 */
	private function unschedule_purge_old_cache() {
		wp_clear_scheduled_hook('wpo_purge_old_cache');
		wp_clear_scheduled_hook('wpo_prune_cache_logs');
	}

	/**
	 * Do required actions on activate/deactivate any plugin.
	 */
	public function activate_deactivate_plugin() {

		wp_clear_scheduled_hook('wpo_purge_old_cache');

		$this->update_cache_config();

		$is_cache_purged = $this->purge();

		if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: plugin activation/deactivation");
	}

	/**
	 * Check if current user can purge cache.
	 *
	 * @return bool
	 */
	public function can_purge_cache() {
		if (!$this->is_enabled()) return false;

		return WP_Optimize_Utils::current_user_can_purge_cache();
	}

	/**
	 * Add Purge from cache in admin bar.
	 *
	 * @param array        $menu_items
	 * @return array
	 */
	public function admin_bar_purge_cache($menu_items) {
		global $pagenow;
		if (!$this->can_purge_cache()) return $menu_items;

		$act_url = WP_Optimize_Utils::get_url_without_cache_purge_params();

		$cache_size = $this->get_cache_size();
		$cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>';
		// translators: %d is the number of files in the cache
		$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>';

		$menu_items[] = array(
			'id'    => 'wpo_cache_stats',
			'title' => $cache_size_info,
			'meta'  => array(
				'class' => 'wpo-cache-stats',
			),
			'parent' => 'wpo_purge_cache',
		);

		$menu_items[] = array(
			'id'    => 'wpo_purge_all_pages_cache',
			'title' => __('Purge cache for all pages', 'wp-optimize'),
			'href'  => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_all_pages_cache'), $act_url),
			'meta'  => array(
				'title' => __('Purge cache for all pages', 'wp-optimize'),
			),
			'parent' => 'wpo_purge_cache',
		);

		if (!is_admin() || 'post.php' === $pagenow) {
			$menu_items[] = array(
				'id'    => 'wpo_purge_this_page_cache',
				'title' => __('Purge cache for this page', 'wp-optimize'),
				'href'  => add_query_arg('_wpo_purge', wp_create_nonce('wpo_purge_single_page_cache'), $act_url),
				'meta'  => array(
					'title' => __('Purge cache for this page', 'wp-optimize'),
				),
				'parent' => 'wpo_purge_cache',
			);
		}

		return $menu_items;

	}

	/**
	 * Check if purge single page action sent and purge cache.
	 */
	public function handle_purge_single_page_cache() {

		if (!$this->can_purge_cache()) return;

		if (isset($_GET['wpo_single_page_cache_purged']) || isset($_GET['wpo_all_pages_cache_purged'])) {
			// phpcs:disable
			// We are not using $_GET values, just checks if the variable is set and then use hard coded string
			if (isset($_GET['wpo_single_page_cache_purged'])) {
				$message = $_GET['wpo_single_page_cache_purged'] ? __('Cache purged for this page', 'wp-optimize') : __('Error purging cache for this page', 'wp-optimize');
				$type = $_GET['wpo_single_page_cache_purged'] ? 'success' : 'error';
			} else {
				$message = $_GET['wpo_all_pages_cache_purged'] ? __('Cache purged for all pages', 'wp-optimize') : __('Error purging cache for all pages', 'wp-optimize');
				$type = $_GET['wpo_all_pages_cache_purged'] ? 'success' : 'error';
			}
			// phpcs:enable

			if (is_admin()) {
				$admin_notices_hook = is_network_admin() ? 'network_admin_notices' : 'admin_notices';

				add_action($admin_notices_hook, function() use ($message, $type) {
					$this->show_notice($message, $type);
				});
				
				add_action('admin_head', array('WP_Optimize_Utils', 'script_to_remove_cache_purge_params_from_url'));
			} else {
				printf('<script>window.onload = function() {alert("%s");}</script>', esc_js($message));
				WP_Optimize_Utils::script_to_remove_cache_purge_params_from_url();
			}

			return;
		}

		if (!isset($_GET['_wpo_purge'])) return;

		if (wp_verify_nonce(sanitize_key($_GET['_wpo_purge']), 'wpo_purge_single_page_cache')) {
			$success = false;

			if (is_admin()) {
				$post = isset($_GET['post']) ? (int) $_GET['post'] : 0;
				if ($post > 0) {
					$success = self::delete_single_post_cache($post);
					if ($success) $this->file_log("Cache for URL: {{URL}} has been purged, triggered by: " . __METHOD__, $post);
				}
			} else {
				$url = wpo_current_url();
				$success = self::delete_cache_by_url($url);
				if ($success) $this->file_log("Cache for URL: " . self::remove_query_params($url) . " has been purged, triggered by: " . __METHOD__);
			}

			// Remove all existing purge-related parameters and add the new one
			$redirect_url = WP_Optimize_Utils::get_url_without_cache_purge_params();
			$redirect_url = add_query_arg('wpo_single_page_cache_purged', $success, $redirect_url);

			wp_redirect($redirect_url);
			exit;

		} elseif (wp_verify_nonce(sanitize_key($_GET['_wpo_purge']), 'wpo_purge_all_pages_cache')) {
			$success = self::purge();
			$this->maybe_set_preload_cron_job();
			if ($success) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);

			// Remove all existing purge-related parameters and add the new one
			$redirect_url = WP_Optimize_Utils::get_url_without_cache_purge_params();
			$redirect_url = add_query_arg('wpo_all_pages_cache_purged', $success, $redirect_url);

			wp_redirect($redirect_url);
			exit;
		}
	}

	/**
	 * Sets a preload cron job to run after 30 seconds
	 *
	 * As preloading may take time for larger sites, we are opting for an immediate cron job instead
	 * of instant preloading to prevent the current request from slowing down the page load
	 *
	 * @return void
	 */
	private function maybe_set_preload_cron_job() {
		$wpo_page_cache_preloader = WP_Optimize_Page_Cache_Preloader::instance();

		if ($wpo_page_cache_preloader->is_scheduled_preload_enabled() || $this->should_auto_preload_purged_contents()) {
			if (!wp_next_scheduled('wpo_page_cache_run_preload')) {
				wp_schedule_single_event(time() + 30, 'wpo_page_cache_run_preload');
			}
		}
	}
	
	/**
	 * Show notification in WordPress admin.
	 *
	 * @param string $message HTML (no further escaping is performed)
	 * @param string $type    error, warning, success, or info
	 */
	public function show_notice($message, $type) {
		global $current_screen;
		
		if ($current_screen && is_callable(array($current_screen, 'is_block_editor')) && $current_screen->is_block_editor()) : ?>
			<script>
				window.addEventListener('load', function() {
					(function(wp) {
						if (window.wp && wp.hasOwnProperty('data') && 'function' == typeof wp.data.dispatch) {
							wp.data.dispatch('core/notices').createNotice(
								'<?php echo esc_html($type); ?>',
								'<?php echo wp_kses_post($message); ?>',
								{
									isDismissible: true,
								}
							);
						}
					})(window.wp);
				});
			</script>
		<?php else : ?>
			<div class="notice wpo-notice notice-<?php echo esc_attr($type); ?> is-dismissible">
				<p><?php echo wp_kses_post($message); ?></p>
			</div>
		<?php
		endif;
	}

	/**
	 * Enables page cache
	 *
	 * @param bool $force_enable - Force regenerating everything. E.g. we want to do that when saving the settings
	 *
	 * @return WP_Error|bool - true on success, error otherwise
	 */
	public function enable($force_enable = false) {
		global $is_apache;
		static $already_ran_enable = false;

		if ($already_ran_enable) return $already_ran_enable;

		$folders_created = $this->create_folders();
		if (is_wp_error($folders_created)) {
			$already_ran_enable = $folders_created;
			return $already_ran_enable;
		}

		if (!file_exists($this->config->get_config_file_path())) {
			$result = $this->update_cache_config();
			if (is_wp_error($result)) return $result;
		}

		// if WPO_ADVANCED_CACHE isn't set, or environment doesn't contain the right constant, force regeneration
		if (!defined('WPO_ADVANCED_CACHE') || !defined('WP_CACHE')) {
			$force_enable = true;
		}

		$this->maybe_regenerate_cache_config_file();

		// Enable `.htaccess` rules to serve cached files
		if ($is_apache && apply_filters('wpo_serve_cache_via_htaccess', false)) {
			wpo_disable_cache_directories_viewing();
			wpo_allow_access_to_index_cache_files();
			$this->update_serve_cache_rules_htaccess_section(true);
		}

		if (!$force_enable) {
			$already_ran_enable = $this->update_page_cache_enabled_state(true);
			return $already_ran_enable;
		}

		if (!$this->write_advanced_cache() && version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<')) {
			$message = sprintf("The request to write the file %s failed. ", htmlspecialchars($this->get_advanced_cache_filename()));
			$message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');

			if (!defined('WP_CLI') || !WP_CLI) {
				// translators: %s is the path to advanced-cache.php folder
				$message .= "\n\n1. ".sprintf(__('Please navigate, via FTP, to the folder - %s', 'wp-optimize'), htmlspecialchars(dirname($this->get_advanced_cache_filename())));
				$message .= "\n2. ".__('Edit or create a file with the name advanced-cache.php', 'wp-optimize');
				$message .= "\n3. ".__('Copy and paste the following lines into the file:', 'wp-optimize');
			}

			$already_ran_enable = new WP_Error("write_advanced_cache", $message);
			return $already_ran_enable;
		}

		if (!$this->write_wp_config(true)) {
			$already_ran_enable = new WP_Error("write_wp_config", "Could not turn on the WP_CACHE constant in wp-config.php. Check your permissions.");
			return $already_ran_enable;
		}

		if (!$this->verify_cache()) {
			$errors = $this->get_errors();
			$already_ran_enable = new WP_Error("verify_cache", "Could not verify if the cache was enabled: \n".implode("\n- ", $errors));
			return $already_ran_enable;
		}

		$already_ran_enable = $this->update_page_cache_enabled_state(true);
		return $already_ran_enable;
	}

	/**
	 * Disables page cache
	 *
	 * @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.
	 * @return bool|WP_Error - true on success or WP_Error on failure.
	 */
	public function disable($keep_option_enabled = false) {
		global $is_apache;

		$ret = true;

		$advanced_cache_file = $this->get_advanced_cache_filename();

		// 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.
		$disabled_wp_config = $this->write_wp_config(false);
		if (!$disabled_wp_config) {
			$this->log("Could not turn off the WP_CACHE constant in wp-config.php");
			$this->add_warning('error_disabling', __('Could not turn off the WP_CACHE constant in wp-config.php', 'wp-optimize'));
		}

		$disabled_advanced_cache = true;
		// First try to remove (so that it doesn't look to any other plugin like the file is already 'claimed')
		// We only touch advanched-cache.php and wp-config.php if it appears that we were in control of advanced-cache.php
		if (!file_exists($advanced_cache_file) || false !== strpos(file_get_contents($advanced_cache_file), 'WP-Optimize advanced-cache.php')) {
			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"))) {
				$disabled_advanced_cache = false;
				$this->log("The request to the filesystem to remove or empty advanced-cache.php failed");
				$this->add_warning('error_disabling', __('The request to the filesystem to remove or empty advanced-cache.php failed', 'wp-optimize'));
			}
		}

		// If both actions failed, the cache wasn't disabled. So we send an error. If only one succeeds, it will still be disabled.
		if (!$disabled_wp_config && !$disabled_advanced_cache) {
			$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'));
		}

		// Delete cache to avoid stale cache on next activation
		$is_cache_purged = $this->purge();
		if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);

		// Delete `.htaccess` rules to serve cached files
		if ($is_apache && apply_filters('wpo_serve_cache_via_htaccess', false)) {
			$this->update_serve_cache_rules_htaccess_section(false);
		}
		
		// Unschedule cron job to purge old cache
		wp_clear_scheduled_hook('wpo_purge_old_cache');

		if (!is_wp_error($ret)) {
			wp_clear_scheduled_hook('wpo_prune_cache_logs');

			if (!$keep_option_enabled) {
				$ret = $this->update_page_cache_enabled_state(false);
			}
		}

		if (!is_wp_error($ret)) {
			do_action('wpo_page_cache_disabled');
		}

		return $ret;
	}

	/**
	 * Update page cache enabled state.
	 * Only updates `enable_page_caching` if the option exists and the value has changed.
	 *
	 * @param bool $enabled Whether page caching should be marked as enabled.
	 *
	 * @return true|WP_Error true on success, WP_Error on failure.
	 */
	private function update_page_cache_enabled_state($enabled) {
		$config = WPO_Cache_Config::instance();
		$cache_config = $config->get();

		if (isset($cache_config['enable_page_caching']) && $cache_config['enable_page_caching'] === $enabled) {
			return true;
		}

		$cache_config['enable_page_caching'] = $enabled;

		return $config->update($cache_config, true);
	}

	/**
	 * Purges the cache
	 *
	 * @return bool - true on success, false otherwise
	 */
	public function purge() {
		/**
		 * Filters whether activating / deactivating a plugin will purge the cache.
		 */
		$this->should_purge = apply_filters('wpo_purge_page_cache_on_activate_deactivate_plugin', true);
		$purge_actions = array(
			'deactivate_' . plugin_basename(WPO_PLUGIN_MAIN_PATH . 'wp-optimize.php'),
			'deactivate_plugin',
			'activated_plugin',
		);
		$is_deactivation_action = in_array(current_action(), $purge_actions, true);
		if ($is_deactivation_action && !$this->should_purge) return false;

		if (!self::delete(WPO_CACHE_FILES_DIR)) {
			$this->log("The request to the filesystem to delete the cache failed");
			return false;
		}

		/**
		 * Fires after purging the cache
		 */
		do_action('wpo_cache_flush');

		return true;
	}

	/**
	 * Purge cache files older than cache life span
	 *
	 * @return array Log messages.
	 */
	public function purge_old() {
		$log = array();
		$page_cache_length = $this->config->get_option('page_cache_length');
		if (0 === $page_cache_length) return $log;
		$expires = time() - $page_cache_length;
		$cache_folder = WPO_CACHE_FILES_DIR . '/' . str_ireplace(array('http://', 'https://'), '', get_site_url());
		// get all directories that are a direct child of current directory
		if (is_dir($cache_folder) && wp_is_writable($cache_folder)) {
			if ($handle = opendir($cache_folder)) {
				while (false !== ($d = readdir($handle))) {
					if (0 === strcmp($d, '.') || 0 === strcmp($d, '..')) {
						continue;
					}
					
					if ($this->is_front_page_cache($d)) {
						$modified_time = (int) filemtime("$cache_folder/$d");
						if ($modified_time <= $expires) {
							wp_delete_file("$cache_folder/$d");
						}
						continue;
					}

					$dir = $cache_folder.'/'.$d;
					if (!is_dir($dir)) continue;

					$stat = stat($dir);
					$modified_time = $stat['mtime'];
					$log[] = "checking if cache has expired - $d";
					if ($modified_time <= $expires) {
						$log[] = "deleting cache in $dir";
						wpo_delete_files($dir, true);
						if (file_exists($dir)) rmdir($dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A
					}
				}
				closedir($handle);
			}
		}
		return $log;
	}

	/**
	 * Finds out given cache file is of front page or not
	 *
	 * @return bool
	 */
	private function is_front_page_cache($d) {
		return in_array($d, array('index.html', 'index.html.gz'));
	}

	/**
	 * Purges the cache
	 *
	 * @return bool - true on success, false otherwise
	 */
	public function clean_up() {

		$this->disable();

		if (!self::delete(WPO_CACHE_DIR, true)) {
			$this->log("The request to the filesystem to clean up the cache failed");
			return false;
		}

		return true;
	}

	/**
	 * Checks whether page caching is turned on in settings.
	 *
	 * @return bool true if the `enable_page_caching` option is set & environment is ready to cache, false otherwise.
	 */
	public function is_enabled() {
		return $this->config->get_option('enable_page_caching') && $this->is_environment_functional();
	}

	/**
	 * Checks whether the cache delivery chain is actually intact and able to serve cached pages.
	 *
	 * Verifies the WP_CACHE and WPO_ADVANCED_CACHE constants are defined and true, and that the cache config file exists on disk.
	 *
	 * @return bool True if the delivery chain is functional, false otherwise.
	 */
	private function is_environment_functional() {
		$is_cli_or_ajax = (defined('WP_CLI') && WP_CLI) || (defined('DOING_AJAX') && DOING_AJAX);

		if (!$is_cli_or_ajax && (!defined('WP_CACHE') || !WP_CACHE)) {
			$this->file_log("WP_CACHE constant is not present in wp-config.php");
			return false;
		}

		if (!$is_cli_or_ajax && (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE)) {
			$this->file_log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php");
			return false;
		}

		$config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
		if (!$is_cli_or_ajax && !file_exists($config_file)) {
			$this->file_log("Cache config file $config_file is not present");
			return false;
		}

		return true;
	}

	/**
	 * Create the folder structure needed for cache to work
	 *
	 * @return bool | WP_Error - true on success, false otherwise
	 */
	public function create_folders() {
		$permissions_str = __('Please check your file permissions.', 'wp-optimize');

		if (!is_dir(WPO_CACHE_DIR) && !wp_mkdir_p(WPO_CACHE_DIR)) {
			// translators: %s is a directory name
			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);
		}

		if (!is_dir(WPO_CACHE_CONFIG_DIR) && !wp_mkdir_p(WPO_CACHE_CONFIG_DIR)) {
			// translators: %s is a directory name
			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);
		}
		
		if (!is_dir(WPO_CACHE_FILES_DIR)) {
			if (!wp_mkdir_p(WPO_CACHE_FILES_DIR)) {
				// translators: %s is a directory name
				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);
			} else {
				wpo_disable_cache_directories_viewing();
			}
		}

		return true;
	}

	/**
	 * Get advanced-cache.php file name with full path.
	 *
	 * @return string
	 */
	public function get_advanced_cache_filename() {
		return untrailingslashit(WP_CONTENT_DIR) . '/advanced-cache.php';
	}

	/**
	 * Writes advanced-cache.php
	 *
	 * @param boolean $update_required - Whether the update is required or not.
	 * @return bool
	 */
	private function write_advanced_cache($update_required = false) {
		$config_file_basename = $this->config->get_cache_config_filename();
		$cache_file_basename = untrailingslashit(plugin_dir_path(__FILE__));
		$plugin_basename = basename(WPO_PLUGIN_MAIN_PATH);
		$cache_path = '/wpo-cache';
		$cache_files_path = '/cache/wpo-cache';
		$cache_extensions_path = WPO_CACHE_EXT_DIR;
		$wpo_version = WPO_VERSION;
		$wpo_home_url = trailingslashit(network_home_url());
		$abspath = ABSPATH;

		// CS does not like heredoc
		// phpcs:disable
		$this->advanced_cache_file_content = <<<EOF
<?php

if (!defined('ABSPATH')) die('No direct access allowed');

// 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)

if (!defined('WPO_ADVANCED_CACHE')) define('WPO_ADVANCED_CACHE', true);

\$possible_plugin_locations = array(
	defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR.'/$plugin_basename/cache' : false,
	defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR.'/plugins/$plugin_basename/cache' : false,
	dirname(__FILE__).'/plugins/$plugin_basename/cache',
	'$cache_file_basename',
);

\$plugin_location = false;

foreach (\$possible_plugin_locations as \$possible_location) {
	if (false !== \$possible_location && @file_exists(\$possible_location.'/file-based-page-cache.php')) {
		\$plugin_location = \$possible_location;
		break;
	}
}

if (false === \$plugin_location) {
	if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND')) define('WPO_PLUGIN_LOCATION_NOT_FOUND', true);
	\$protocol = \$_SERVER['REQUEST_SCHEME'];
	\$host = \$_SERVER['HTTP_HOST'];
	\$request_uri = \$_SERVER['REQUEST_URI'];
	if (strcasecmp('$wpo_home_url', \$protocol . '://' . \$host . \$request_uri) === 0) {
		error_log('WP-Optimize: No caching took place, because the plugin location could not be found');
	}
} else {
	if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND')) define('WPO_PLUGIN_LOCATION_NOT_FOUND', false);
}

if (is_admin()) { return; }

if (!defined('WPO_CACHE_DIR')) define('WPO_CACHE_DIR', WP_CONTENT_DIR.'$cache_path');
if (!defined('WPO_CACHE_CONFIG_DIR')) define('WPO_CACHE_CONFIG_DIR', WPO_CACHE_DIR.'/config');
if (!defined('WPO_CACHE_FILES_DIR')) define('WPO_CACHE_FILES_DIR', WP_CONTENT_DIR.'$cache_files_path');
if (false !== \$plugin_location) {
	if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', \$plugin_location.'/extensions');
} else {
	if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', '$cache_extensions_path');
}

if (!@file_exists(WPO_CACHE_CONFIG_DIR . '/$config_file_basename')) { return; }

\$GLOBALS['wpo_cache_config'] = @json_decode(file_get_contents(WPO_CACHE_CONFIG_DIR . '/$config_file_basename'), true);

if (empty(\$GLOBALS['wpo_cache_config'])) {
	include_once(WPO_CACHE_CONFIG_DIR . '/$config_file_basename');
}

if (empty(\$GLOBALS['wpo_cache_config'])) {
	error_log('WP-Optimize: Caching failed because the configuration data could not be loaded from the config file.');
	return;
}

if (empty(\$GLOBALS['wpo_cache_config']['enable_page_caching'])) { return; }

if (false !== \$plugin_location) { include_once(\$plugin_location.'/file-based-page-cache.php'); }

EOF;

		// phpcs:enable
		$advanced_cache_filename = $this->get_advanced_cache_filename();

		// If the file content is already up to date, success
		if (is_file($advanced_cache_filename) && file_get_contents($advanced_cache_filename) === $this->advanced_cache_file_content) {
			$this->advanced_cache_file_writing_error = false;
			return true;
		}

		// check if we can't write the advanced cache file
		// case 1: the directory is read-only and the file doesn't exist
		if (!is_file($advanced_cache_filename) && !wp_is_writable(dirname($advanced_cache_filename))) {
			$this->advanced_cache_file_writing_error = true;
			return false;
		}

		// case 2: the file already exists but it's read-only
		if (is_file($advanced_cache_filename) && !wp_is_writable($advanced_cache_filename)) {
			if (version_compare($this->get_advanced_cache_version(), $this->_minimum_advanced_cache_file_version, '<') || $update_required) {
				$this->advanced_cache_file_writing_error = true;
				return false;
			} else {
				$this->advanced_cache_file_writing_error = false;
				return true;
			}
		}

		if (!file_put_contents($this->get_advanced_cache_filename(), $this->advanced_cache_file_content)) {
			$this->advanced_cache_file_writing_error = true;
			return false;
		}

		$this->advanced_cache_file_writing_error = false;
		return true;
	}

	/**
	 * Update advanced cache version if needed.
	 */
	public function maybe_update_advanced_cache() {

		if (!$this->is_enabled()) return;

		if (!defined('WPO_PLUGIN_LOCATION_NOT_FOUND') || (defined('WPO_PLUGIN_LOCATION_NOT_FOUND') && true === WPO_PLUGIN_LOCATION_NOT_FOUND)) {
			if (!$this->write_advanced_cache(true)) {
				add_action('admin_notices', array($this, 'show_admin_notice_advanced_cache'));
			}
		}

		// from 3.0.17 we use more secure way to store cache config files and need update advanced-cache.php
		// if the site url or folder changed we also update advanced-cache.php
		$advanced_cache_current_version = $this->get_advanced_cache_version();
		$is_the_site_url_or_folder_changed = $this->is_the_site_url_or_folder_changed();

		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;

		// Purge the cache when the site url or folder changed.
		if ($is_the_site_url_or_folder_changed) {
			$is_cache_purged = $this->purge();
			if ($is_cache_purged) $this->file_log("Full Cache Purge triggered by: ". __METHOD__);
		}

		if (!$this->write_advanced_cache()) {
			add_action('admin_notices', array($this, 'notice_advanced_cache_autoupdate_error'));
		} else {
			$this->update_cache_config();
		}
	}

	/**
	 * Check if the site url or folder doesn't equal to values from advanced-cache.php
	 *
	 * @return bool
	 */
	private function is_the_site_url_or_folder_changed() {
		if (!is_file($this->get_advanced_cache_filename())) return false;

		$content = file_get_contents($this->get_advanced_cache_filename());
		
		if (false === $content) return false;

		if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\) (\(homeurl: (.+)\)) (\(abspath: (.+)\)) \(do not change/Ui', $content, $match)) {
			$wpo_home_url = trailingslashit(network_home_url());
			$abspath = ABSPATH;
			return ($wpo_home_url !== $match[3] || $abspath !== $match[5]);
		} elseif (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
			return true;
		}

		return false;
	}

	/**
	 * Show notification when advanced-cache.php could not be updated.
	 */
	public function notice_advanced_cache_autoupdate_error() {
		$this->show_notice(__('The file advanced-cache.php needs to be updated, but the automatic process failed.', 'wp-optimize').
		' <a href="'.admin_url('admin.php?page=wpo_cache').'">'.__('Please try to disable and then re-enable the WP-Optimize cache manually.', 'wp-optimize').'</a>', 'error');
	}

	/**
	 * Get WPO version number from advanced-cache.php file.
	 *
	 * @return bool|mixed
	 */
	public function get_advanced_cache_version() {
		if (!is_file($this->get_advanced_cache_filename())) return false;

		$version = false;
		$content = file_get_contents($this->get_advanced_cache_filename());

		if (preg_match('/WP\-Optimize advanced\-cache\.php \(written by version\: (.+)\)/Ui', $content, $match)) {
			$version = $match[1];
		}

		return $version;
	}

	/**
	 * Set WP_CACHE on or off in wp-config.php
	 *
	 * @param  boolean $status value of WP_CACHE.
	 * @return boolean true if the value was set, false otherwise
	 */
	private function write_wp_config($status = true) {
		// If we changed the value in wp-config, save it, in case we need to change it again in the same run.
		static $changed = false;

		if (defined('WP_CACHE') && WP_CACHE === $status && !$changed) {
			return true;
		}

		$config_path = $this->_get_wp_config();

		// Couldn't find wp-config.php.
		if (!$config_path) {
			return false;
		}

		// Bail early if wp-config.php is not writable, to avoid a PHP warning.
		if (!wp_is_writable($config_path)) {
			return false;
		}

		$config_file_string = file_get_contents($config_path);

		// Config file is empty. Maybe couldn't read it?
		if (empty($config_file_string)) {
			return false;
		}

		$config_file = preg_split("#(\n|\r\n)#", $config_file_string);
		$line_key    = false;

		foreach ($config_file as $key => $line) {
			if (!preg_match('/^\s*define\(\s*(\'|")([A-Z_]+)(\'|")(.*)/i', $line, $match)) {
				continue;
			}

			if ('WP_CACHE' === $match[2]) {
				$line_key = $key;
			}
		}

		if (false !== $line_key) {
			unset($config_file[$line_key]);
		}


		if ($status) {
			array_shift($config_file);
			array_unshift($config_file, '<?php', "define('WP_CACHE', true); // WP-Optimize Cache");
		}

		foreach ($config_file as $key => $line) {
			if ('' === $line) {
				unset($config_file[$key]);
			}
		}
		if (!file_put_contents($config_path, implode(PHP_EOL, $config_file))) {
			return false;
		}
		$changed = true;
		return true;
	}

	/**
	 * Verify we can write to the file system
	 *
	 * @return boolean
	 */
	private function verify_cache() {
		if (function_exists('clearstatcache')) {
			clearstatcache();
		}
		$errors = 0;

		// First check wp-config.php.
		if (!$this->_get_wp_config() && !wp_is_writable($this->_get_wp_config())) {
			$this->log("Unable to write to or find wp-config.php; please check file/folder permissions");
			$this->add_warning('verify_cache', __("Unable to write to or find wp-config.php; please check file/folder permissions.", 'wp-optimize'));
		}

		$advanced_cache_file = untrailingslashit(WP_CONTENT_DIR).'/advanced-cache.php';
		
		// Now check wp-content. We need to be able to create files of the same user as this file.
		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))) {
			$this->log("Unable to write the file advanced-cache.php inside the wp-content folder; please check file/folder permissions");
			$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'));
			$errors++;
		}

		if (file_exists(WPO_CACHE_FILES_DIR)) {
			if (!wp_is_writable(WPO_CACHE_FILES_DIR)) {
				$this->log("Unable to write inside the cache files folder; please check file/folder permissions");
				// translators: %s is cache folder path
				$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));
			}
		}
		
		if (file_exists(WPO_CACHE_CONFIG_DIR)) {
			if (!wp_is_writable(WPO_CACHE_CONFIG_DIR)) {
				$this->log("Unable to write inside the cache configuration folder; please check file/folder permissions");
				// If the config exists, only send a warning. Otherwise send an error.
				$type = 'warning';
				if (!file_exists(WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename())) {
					$type = 'error';
					$errors++;
				}
				// translators: %s is cache config folder path
				$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);
			}
		}

		return !$errors;
	}

	/**
	 * Update permalink structure in cache config
	 *
	 * @param string $old_value Old value of permalink_structure option
	 * @param string $value 	New value of permalink_structure option
	 * @param string $option 	Option name `permalink_structure`
	 */
	public function update_option_permalink_structure($old_value, $value, $option) {
		$current_config = $this->config->get();
		if ($old_value !== $value) {
			$current_config[$option] = $value;
			$this->config->update($current_config, true);
		}
	}

	/**
	 * Update cache config. Used to support 3d party plugins.
	 *
	 * @return bool | WP_Error
	 */
	public function update_cache_config() {
		// get current cache settings.
		$current_config = $this->config->get();
		// and call update to change if need cookies and query variable names.
		return $this->config->update($current_config);
	}

	/**
	 * Delete information about cache size.
	 */
	public function delete_cache_size_information() {
		//not removing delete_transient so it clear this key from option table
		delete_transient('wpo_get_cache_size');
		delete_site_transient('wpo_get_cache_size');
	}

	/**
	 * Update cache cleared time
	 */
	public function update_cache_cleared_time(): void {
		$config = $this->config->get();
		$config['last_cleared'] = time();
		$this->config->update($config);
	}

	/**
	 * Get current cache size.
	 *
	 * @return array
	 */
	public function get_cache_size() {
		$cache_size = get_site_transient('wpo_get_cache_size');

		if (!empty($cache_size)) return $cache_size;

		$infos = $this->get_dir_infos(WPO_CACHE_FILES_DIR);
		$cache_size = array(
			'size' => $infos['size'],
			'file_count' => $infos['file_count']
		);

		$expiration = apply_filters('wpo_cache_size_expiration', DAY_IN_SECONDS);
		$expiration = is_numeric($expiration) ? (int) $expiration : DAY_IN_SECONDS;
		set_site_transient('wpo_get_cache_size', $cache_size, $expiration);

		return $cache_size;
	}

	/**
	 * Fetch directory information.
	 *
	 * @param string $dir
	 * @return array
	 */
	private function get_dir_infos($dir) {
		$filtered = apply_filters('wpo_cache_size_files_to_ignore', $this->files_to_ignore);
		$this->files_to_ignore = is_array($filtered) ? $filtered : array();

		return WP_Optimize_Utils::get_folder_stats($dir, $this->files_to_ignore);
	}

	/**
	 * Returns the path to wp-config
	 *
	 * @return string|boolean wp-config.php path.
	 */
	private function _get_wp_config() {

		$config_path = false;

		foreach (get_included_files() as $filename) {
			if (preg_match('/(\\\\|\/)wp-config\.php$/i', $filename)) {
				$config_path = $filename;
				break;
			}
		}

		// WP-CLI doesn't include wp-config.php that's why we use function from WP-CLI to locate config file.
		if (!$config_path && is_callable('wpo_wp_cli_locate_wp_config')) {
			$config_path = wpo_wp_cli_locate_wp_config();
		}

		return $config_path;
	}

	/**
	 * Util to delete folders and/or files
	 *
	 * @param string $src
	 * @return boolean
	 */
	public static function delete($src) {
		return wpo_delete_files($src);
	}

	/**
	 * Delete cached files for specific url.
	 *
	 * @param string $url
	 * @param bool   $recursive If true child elements will deleted too
	 *
	 * @return bool
	 */
	public static function delete_cache_by_url($url, $recursive = false) {
		if (!defined('WPO_CACHE_FILES_DIR') || '' === $url) return false;

		$path = self::get_full_path_from_url($url);

		do_action('wpo_delete_cache_by_url', $url, $recursive);

		do_action('wpo_preload_url', $url);

		return wpo_delete_files($path, $recursive);
	}

	/**
	 * Adds the URL to the preload list and hooks the preload task to shutdown
	 *
	 * @param string $url
	 * @return void
	 */
	public function maybe_preload_url($url) {
		if (!$this->should_auto_preload_purged_contents()) return;

		static $urls_preloaded = array();

		// Remove the query string from the URL
		$url = preg_replace('/\?.*/', '', $url);

		if (!in_array($url, $urls_preloaded)) {
			$urls_preloaded[] = $url;

			$preloader = WP_Optimize_Page_Cache_Preloader::instance();
			$preloader->add_url_to_preload_list($url);
			$url_task_creator = array($preloader, 'create_tasks_for_auto_preload_urls');

			$preloader->maybe_dont_close_browser_connection();

			if (!has_action('shutdown', $url_task_creator)) {
				add_action('shutdown', $url_task_creator);
			}
		}
	}
	
	/**
	 * Delete cached files for single post.
	 *
	 * @param integer $post_id The post ID
	 *
	 * @return bool
	 */
	public static function delete_single_post_cache($post_id) {
		$is_cache_deleted = self::really_delete_single_post_cache($post_id);

		do_action('wpo_single_post_cache_deleted', $post_id);

		return $is_cache_deleted;
	}

	/**
	 * Really delete cached files for single post.
	 *
	 * @param integer $post_id The post ID
	 *
	 * @return bool
	 */
	public static function really_delete_single_post_cache($post_id) {

		if (!defined('WPO_CACHE_FILES_DIR')) return false;

		$post_url = get_permalink($post_id);
	
		$path = self::get_full_path_from_url($post_url);

		// for posts with pagination run purging cache recursively.
		$post = get_post($post_id);
		$recursive = preg_match('/\<\!--nextpage--\>/', $post->post_content) ? true : false;

		do_action('wpo_delete_cache_by_url', $post_url, $recursive);

		do_action('wpo_preload_url', $post_url);

		return wpo_delete_files($path, $recursive);
	}

	/**
	 * Delete cached home page files.
	 */
	public static function delete_homepage_cache() {
	
		if (!defined('WPO_CACHE_FILES_DIR')) return;

		$homepage_url = get_home_url(get_current_blog_id());

		$path = self::get_full_path_from_url($homepage_url);

		$recursive = false;

		do_action('wpo_delete_cache_by_url', $homepage_url, $recursive);

		do_action('wpo_preload_url', $homepage_url);

		wpo_delete_files($path, $recursive);
	}

	/**
	 * Delete feed from cache.
	 */
	public static function delete_feed_cache() {
		if (!defined('WPO_CACHE_FILES_DIR')) return;

		$homepage_url = get_home_url(get_current_blog_id());

		$path = self::get_full_path_from_url($homepage_url) . 'feed/';

		do_action('wpo_delete_cache_by_url', $path, true);

		wpo_delete_files($path, true);
	}

	/**
	 * Delete post feed from cache.
	 *
	 * @param int $post_id Post ID
	 */
	public static function delete_post_feed_cache($post_id) {
		self::really_delete_post_feed_cache($post_id);

		do_action('wpo_single_post_feed_cache_deleted', $post_id);
	}

	/**
	 * Really delete post feed from cache.
	 *
	 * @param int $post_id Post ID
	 */
	public static function really_delete_post_feed_cache($post_id) {
		if (!defined('WPO_CACHE_FILES_DIR')) return;

		$post_url = get_permalink($post_id);
	
		$path = self::get_full_path_from_url($post_url) . 'feed/';

		do_action('wpo_delete_cache_by_url', $path, true);

		wpo_delete_files($path, true);
	}

	/**
	 * Delete comments feed from cache.
	 */
	public static function delete_comments_feed() {
		if (!defined('WPO_CACHE_FILES_DIR')) return;

		$comments_feed_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/feed/';

		$path = self::get_full_path_from_url($comments_feed_url);

		do_action('wpo_delete_cache_by_url', $comments_feed_url, true);

		wpo_delete_files($path, true);

		// delete empty comments dir from the cache
		$comments_url = trailingslashit(get_home_url(get_current_blog_id())) . 'comments/';
		$path = self::get_full_path_from_url($comments_url);
		
		if (wpo_is_empty_dir($path)) {
			wpo_delete_files($path, true);
		}
	}

	/**
	 * Returns full path to the cache folder by url.
	 *
	 * @param string $url
	 * @return string
	 */
	public static function get_full_path_from_url($url) {
		return trailingslashit(WPO_CACHE_FILES_DIR) . trailingslashit(wpo_get_url_path($url));
	}

	/**
	 * Admin actions
	 *
	 * @return void
	 */
	public function admin_init() {
		// Maybe update the advanced cache.
		if ((!defined('DOING_AJAX') || !DOING_AJAX) && WP_Optimize()->current_user_can('update_plugins')) {
			$this->maybe_update_advanced_cache();
			$this->cron_activate();
		}
	}

	/**
	 * Logs error messages
	 *
	 * @param  string $message
	 * @return null|void
	 */
	public function log($message) {
		if (isset($this->logger)) {
			$this->logger->log($message, 'error');
		}
	}

	/**
	 * Returns an instance of the current class, creates one if it doesn't exist
	 *
	 * @return self
	 */
	public static function instance() {
		if (empty(self::$instance)) {
			self::$instance = new self();
		}
		return self::$instance;
	}


	/**
	 * Update gmt_offset and timezone_string cache config value, used with hook `update_gmt_offset_timezone_string_config`.
	 *
	 * @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)
	 * @param float | string  $new_value
	 * @param string    	  $option    gmt_offset|timezone_string
	 *
	 * @return null
	 */
	public function update_gmt_offset_timezone_string_config($old_value, $new_value, $option) {
		if ('' === $new_value) return;

		$current_config = $this->config->get();
		if ('timezone_string' === $option) {
			$timeZone = new DateTimeZone($new_value);
			$dateTime = new DateTime("now");
			$gmt_offset = $timeZone->getOffset($dateTime);
			$current_config['gmt_offset'] = round($gmt_offset/3600, 1);
			$current_config['timezone_string'] = $new_value;
		} elseif ('' !== $new_value) {
			$current_config['gmt_offset'] = $new_value;
			$current_config['timezone_string'] = '';
		}
		$this->config->update($current_config, true);
	}
	
	/**
	 * Update `date_format` cache config value, used with hook `update_option_date_format`.
	 *
	 * @param string $old_value Old date format
	 * @param string $new_value New date format
	 *
	 * @return void
	 */
	public function update_option_date_format($old_value, $new_value) {
		$current_config = $this->config->get();
		$current_config['date_format'] = $new_value;
		$this->config->update($current_config, true);
	}
	
	/**
	 * Update `time_format` cache config value, used with hook `update_option_time_format`.
	 *
	 * @param string $old_value Old time format
	 * @param string $new_value New time format
	 *
	 * @return void
	 */
	public function update_option_time_format($old_value, $new_value) {
		$current_config = $this->config->get();
		$current_config['time_format'] = $new_value;
		$this->config->update($current_config, true);
	}
 
	/**
	 * Adds an error to the error store
	 *
	 * @param string $code    - The error code
	 * @param string $message - The error's message
	 * @param string $type    - The error's type (error, warning)
	 * @return void
	 */
	public function add_error($code, $message, $type = 'error') {
		if (!isset($this->_errors[$type])) {
			$this->_errors[$type] = new WP_Error($code, $message);
		} else {
			$this->_errors[$type]->add($code, $message);
		}
	}

	/**
	 * Adds a warning to the error store
	 *
	 * @param string $code    - The error code
	 * @param string $message - The error's message
	 * @return void
	 */
	public function add_warning($code, $message) {
		$this->add_error($code, $message, 'warning');
	}

	/**
	 * Get all recorded errors
	 *
	 * @param string  $type              - The error type
	 * @param boolean $get_messages_only - Whether to get only the messages, or the full WP_Error object
	 * @return boolean|array|WP_Error
	 */
	public function get_errors($type = 'error', $get_messages_only = true) {
		if (!$this->has_errors($type)) return false;
		$errors = $this->_errors[$type];
		if ($get_messages_only) {
			return $errors->get_error_messages();
		}
		return $errors;
	}

	/**
	 * Check if any errors were recorded
	 *
	 * @param string $type - The error type
	 * @return boolean
	 */
	public function has_errors($type = 'error') {
		return isset($this->_errors[$type]) && !empty($this->_errors[$type]) && $this->_errors[$type]->has_errors();
	}

	/**
	 * Check if any warnings were recorded
	 *
	 * @return boolean
	 */
	public function has_warnings() {
		return $this->has_errors('warning');
	}

	/**
	 * Check the cache compatibility issues.
	 */
	public function check_compatibility_issues() {
		if (!$this->is_enabled()) return;

		if ($this->is_pagespeedninja_gzip_active()) add_action('admin_notices', array($this, 'show_pagespeedninja_gzip_notice'));
		if ($this->is_farfutureexpiration_gzip_active()) add_action('admin_notices', array($this, 'show_farfutureexpiration_gzip_notice'));
	}

	/**
	 * Check if PageSpeed Ninja is active and GZIP compression option is enabled.
	 *
	 * @return bool
	 */
	public function is_pagespeedninja_gzip_active() {
		if (!class_exists('PagespeedNinja')) return false;

		$options = get_option('pagespeedninja_config');
		$gzip = isset($options['psi_EnableGzipCompression']) && isset($options['html_gzip']) && (bool) $options['psi_EnableGzipCompression'] && (bool) $options['html_gzip'];

		return $gzip;
	}

	/**
	 * Output PageSpeed Ninja Gzip notice.
	 */
	public function show_pagespeedninja_gzip_notice() {
		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>';
	}

	/**
	 * Check if Far Future Expiration is active and GZIP compression option is enabled.
	 *
	 * @return bool
	 */
	public function is_farfutureexpiration_gzip_active() {
		if (!class_exists('farFutureExpiration')) return false;

		$options = get_option('far_future_expiration_settings');
		$gzip = !empty($options) && $options['enable_gzip'];

		return $gzip;
	}

	/**
	 * Output Far Future Expiration Gzip notice.
	 */
	public function show_farfutureexpiration_gzip_notice() {
		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>';
	}

	/**
	 * This is a notice to show users that writing `advanced-cache.php` failed
	 */
	public function show_admin_notice_advanced_cache() {
		// translators: %s is advanced-cache.php file path
		$message = sprintf(__('The request to write the file %s failed.', 'wp-optimize'), htmlspecialchars($this->get_advanced_cache_filename()));
		$message .= ' '.__('Please check file and directory permissions on the file paths up to this point, and your PHP error log.', 'wp-optimize');
		WP_Optimize()->include_template('notices/cache-notice.php', false, array('message' => $message));
	}

	/**
	 * Scheduler public functions to update schedulers
	 *
	 * @param  array $schedules An array of schedules being passed.
	 * @return array            An array of schedules being returned.
	 */
	public function cron_schedules($schedules) {
		$page_cache_length = $this->config->get_option('page_cache_length');
		// When the interval is less than `WP_CRON_LOCK_TIMEOUT` error messages are triggered
		// So, instead of `0` (now) we are making it equal to the constant value.
		// https://github.com/WordPress/WordPress/commit/84d19848666a81584e0007a6ab7f7ad3c990d71a
		if (defined('WP_CRON_LOCK_TIMEOUT') && $page_cache_length < WP_CRON_LOCK_TIMEOUT) {
			$page_cache_length = WP_CRON_LOCK_TIMEOUT;
		}

		$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'));

		$interval = WP_Optimize_Page_Cache_Preloader::instance()->get_continue_preload_cron_interval();
		$schedules['wpo_page_cache_preload_continue_interval'] = array(
			'interval' => $interval,
			// translators: %d is the number of minutes
			'display' => sprintf(__('%d minutes', 'wp-optimize'), round($interval / 60, 1))
		);

		$schedules['wpo_use_cache_lifespan'] = array(
			'interval' => $page_cache_length,
			// translators: %s is the cache lifespan
			'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'))
		);

		return $schedules;
	}

	/**
	 * Update cache config file to reflect the webp images option
	 */
	public function update_webp_images_option() {
		if ($this->is_enabled()) {
			$cache_settings = WPO_Cache_Config::instance()->get();
			$cache_settings['use_webp_images'] = WP_Optimize()->get_options()->get_option('webp_conversion');
			WPO_Cache_Config::instance()->update($cache_settings);
		}
	}

	/**
	 * Maybe regenerate cache config file, in case of migrations
	 */
	public function maybe_regenerate_cache_config_file() {
		$config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename();
		if (!file_exists($config_file)) {
			$config = $this->config->get();
			$this->config->write($config);
		}
	}
	
	/**
	 * Checks if the given cache directory is empty
	 *
	 * @param string $path The path to the cache directory.
	 * @return bool Returns true if the cache directory is empty, false otherwise.
	 */
	public static function is_cache_empty($path) {
		if (!is_dir($path)) return true;
		
		if ($handle = opendir($path)) {
			while (false !== ($entry = readdir($handle))) {
				if ("." === $entry || ".." === $entry) {
					continue;
				}
				
				$full_path = $path . '/' . $entry;
				if (is_file($full_path)) {
					closedir($handle);
					return false;
				}
			}
			closedir($handle);
			return true;
		}
		return true;
	}

	/**
	 * Remove query params from request url
	 *
	 * @param string $url The request url.
	 * @return string Returns url without query params.
	 */
	private static function remove_query_params($url) {
		$parsed_url = wp_parse_url($url);
		if (!$parsed_url) return '';
		return $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'];
	}

	/**
	 * Check configuration for auto preloading after cache deletion
	 *
	 * @return bool
	 */
	public function should_auto_preload_purged_contents() {

		if (!$this->is_enabled()) return false; // as Page Cache is not enabled we should not auto preload purged content.

		$wpo_cache = WP_Optimize()->get_page_cache();
		$wpo_cache_options = $wpo_cache->config->get();

		return $wpo_cache_options['auto_preload_purged_contents'];
	}

	/**
	 * Logging of interesting messages related to Cache Purging
	 *
	 * @param string $message
	 * @param int $post_id
	 */
	public function file_log($message, $post_id = 0) {
		if (!empty($post_id)) {
			if (false !== strpos($message, '{{URL}}')) {
				$message = str_replace('{{URL}}', get_permalink($post_id), $message);
			}
			if (false !== strpos($message, '{{title}}')) {
				$message = str_replace('{{title}}', '"' . get_the_title($post_id) . '"', $message);
			}
		}
		$this->file_logger->info($message);
	}

	/**
	 * Prunes the log file
	 */
	public function prune_cache_logs() {
		$this->file_log("Pruning the Cache log file");
		$this->file_logger->prune_logs();
	}

	/**
	 * Update .htaccess file to serve cached files directly.
	 *
	 * @param bool $enable
	 */
	public function update_serve_cache_rules_htaccess_section($enable) {
		$htaccess = new WP_Optimize_Htaccess();

		$section_title = 'WP-Optimize rules to serve cached files';

		if ($enable) {
			if (!$htaccess->is_commented_section_exists($section_title)) {
				$htaccess->update_commented_section($this->prepare_apache_cache_handle_section(), $section_title, true);
				$htaccess->write_file();
			}
		} elseif ($htaccess->is_commented_section_exists($section_title)) {
			$htaccess->remove_commented_section($section_title);
			$htaccess->write_file();
		}

	}

	/**
	 * Get the .htaccess section with rules to serve cached files
	 *
	 * @return array
	 */
	private function prepare_apache_cache_handle_section() {

		$site_root = $this->get_site_root_path();

		return array(
			array(
				'<IfModule mod_rewrite.c>',
				'RewriteEngine On',
				'RewriteCond %{REQUEST_METHOD} GET|HEAD',
				'RewriteCond %{QUERY_STRING} =""',
				'RewriteCond %{HTTP:Cookie} =""',
				'RewriteCond %{REQUEST_URI} !^/(wp-(?:admin|login|register|comments-post|cron|json))/ [NC]',
				'RewriteCond %{HTTP_USER_AGENT} !(android|blackberry|ipad|iphone|ipod|windows.phone|iEMobile|opera.mini|opera.mobile|webos|symbian|windows.mobile|kindle|googlebot.mobile) [NC]',
				'RewriteCond %{DOCUMENT_ROOT}'.$site_root.'wp-content/cache/wpo-cache/%{HTTP_HOST}%{REQUEST_URI}index.html -f',
				'RewriteRule ^(.*)$ '.$site_root.'wp-content/cache/wpo-cache/%{HTTP_HOST}%{REQUEST_URI}index.html [L]',
				array(
					'<IfModule mod_setenvif.c>',
					'SetEnvIf Request_URI "/wp-content/cache/wpo-cache/" FROM_CACHE=1',
					'</IfModule>',
				),
				array(
					'<IfModule mod_headers.c>',
					'Header always set X-WP-Optimize-Cache-Header "Loaded from WP-Optimize cache" env=FROM_CACHE',
					'</IfModule>',
				),
				'</IfModule>',
			),
		);
	}

	/**
	 * Get relative site root path, if site is placed in the subdirectory then it returns /subdirectory otherwise /
	 *
	 * @param string $default
	 *
	 * @return string
	 */
	private function get_site_root_path($default = '/') {
		$site_root = wp_parse_url(site_url());
		if (isset($site_root['path'])) {
			$site_root = trailingslashit($site_root['path']);
		} else {
			$site_root = $default;
		}

		return $site_root;
	}
}

endif;

```
