File "class-cache.php"

Full Path: /home/theinspectionboy/public_html/suffolk/includes/class-cache.php
File size: 37.17 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * Class and methods for HTML page caching.
 *
 * @link https://ewww.io/swis/
 * @package SWIS_Performance
 */

namespace SWIS;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Enables caching of HTML pages.
 */
final class Cache extends Base {

	/**
	 * Determines if the page cache cleared hook should fire.
	 *
	 * @var bool
	 */
	public $fire_page_cache_cleared_hook = true;

	/**
	 * Setup all the hooks and settings.
	 */
	public function __construct() {
		if ( ! $this->get_option( 'cache' ) ) {
			return;
		}
		parent::__construct();
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		// Init hooks.
		add_action( 'init', array( '\SWIS\Cache_Engine', 'start' ) );
		add_action( 'init', array( $this, 'process_clear_cache_request' ) );

		// Clear cache hooks for public use.
		add_action( 'swis_clear_complete_cache', array( $this, 'clear_complete_cache' ) );
		add_action( 'swis_clear_site_cache', array( $this, 'clear_site_cache' ) );
		add_action( 'swis_clear_site_cache_by_blog_id', array( $this, 'clear_site_cache_by_blog_id' ) );
		add_action( 'swis_clear_page_cache_by_post_id', array( $this, 'clear_page_cache_by_post_id' ) );
		add_action( 'swis_clear_page_cache_by_url', array( $this, 'clear_page_cache_by_url' ) );

		// Core, theme, and plugin changes/updates.
		add_action( '_core_updated_successfully', array( $this, 'clear_complete_cache' ) );
		add_action( 'upgrader_process_complete', array( $this, 'on_upgrade' ), 10, 2 );
		add_action( 'switch_theme', array( $this, 'clear_site_cache' ) );
		add_action( 'permalink_structure_changed', array( $this, 'clear_site_cache' ) );
		add_action( 'activated_plugin', array( $this, 'on_plugin_activation_deactivation' ), 10, 2 );
		add_action( 'deactivated_plugin', array( $this, 'on_plugin_activation_deactivation' ), 10, 2 );
		// Post changes.
		add_action( 'save_post', array( $this, 'on_save_trash_post' ) );
		add_action( 'wp_trash_post', array( $this, 'on_save_trash_post' ) );
		add_action( 'pre_post_update', array( $this, 'on_pre_post_update' ), 10, 2 );
		// Comment changes.
		add_action( 'comment_post', array( $this, 'on_comment_post' ), 99, 2 );
		add_action( 'edit_comment', array( $this, 'on_edit_comment' ), 10, 2 );
		add_action( 'transition_comment_status', array( $this, 'on_transition_comment_status' ), 10, 3 );
		// Third-party hooks.
		add_action( 'autoptimize_action_cachepurged', array( $this, 'clear_complete_cache' ) );
		add_action( 'woocommerce_product_set_stock', array( $this, 'on_woocommerce_stock_update' ) );
		add_action( 'woocommerce_variation_set_stock', array( $this, 'on_woocommerce_stock_update' ) );
		add_action( 'woocommerce_product_set_stock_status', array( $this, 'on_woocommerce_stock_update' ) );
		add_action( 'woocommerce_variation_set_stock_status', array( $this, 'on_woocommerce_stock_update' ) );
		add_action( 'fl_builder_before_save_layout', array( $this, 'clear_complete_cache' ) );
		add_action( 'fl_builder_cache_cleared', array( $this, 'clear_complete_cache' ) );

		// Multisite hooks.
		add_action( 'wp_initialize_site', array( $this, 'install_later' ) );
		add_action( 'wp_uninitialize_site', array( $this, 'uninstall_later' ) );
		// Advanced cache hooks.
		add_action( 'permalink_structure_changed', array( $this, 'install_backend' ) );
		add_action( 'add_option_swis_performance', array( $this, 'install_backend' ) );
		add_action( 'update_option_swis_performance', array( $this, 'install_backend' ) );

		// Admin bar hook.
		add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_items' ), 90 );

		// Admin interface hooks.
		if ( is_admin() ) {
			add_action( 'admin_init', array( $this, 'admin_init' ), 9 );
			// Alert hooks.
			add_action( 'admin_notices', array( $this, 'requirements_check' ) );
			add_action( 'admin_notices', array( $this, 'cache_cleared_notice' ) );
			add_action( 'network_admin_notices', array( $this, 'cache_cleared_notice' ) );
		}
	}

	/**
	 * See if we should pre-empt the caching engine (possibly other caching plugin or server-based caching).
	 *
	 * @return bool True to prevent use of caching engine, false otherwise.
	 */
	function should_disable_caching() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if (
			isset( $_SERVER['cw_allowed_ip'] ) || // Cloudways.
			defined( 'IS_PRESSABLE' ) ||
			getenv( 'SPINUPWP_CACHE_PATH' ) ||
			defined( 'WPE_PLUGIN_VERSION' ) ||
			defined( 'FLYWHEEL_CONFIG_DIR' ) ||
			defined( 'KINSTAMU_VERSION' ) ||
			defined( 'O2SWITCH_VARNISH_PURGE_KEY' ) ||
			! empty( $_ENV['PANTHEON_ENVIRONMENT'] ) ||
			defined( 'WPCOMSH_VERSION' ) ||
			( defined( '\Savvii\CacheFlusherPlugin::NAME_FLUSH_NOW' ) && defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW' ) ) ||
			class_exists( 'VarnishPurger' )
		) {
			return true;
		}
		if ( defined( 'WP_CACHE' ) && WP_CACHE && is_file( WP_CONTENT_DIR . '/advanced-cache.php' ) && is_readable( WP_CONTENT_DIR . '/advanced-cache.php' ) ) {
			$contents = file_get_contents( WP_CONTENT_DIR . '/advanced-cache.php' );
			if ( false === strpos( $contents, 'SWIS_Performance' ) && ! empty( $contents ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Make sure plugin is setup.
	 */
	public function admin_init() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if ( get_option( 'swis_activation' ) ) {
			$this->on_activation( $this->is_plugin_active_for_network() );
		} elseif ( ! $this->should_disable_caching() && ( ! defined( 'WP_CACHE' ) || ! WP_CACHE ) ) {
			Disk_Cache::setup();
		}
	}

	/**
	 * Check if plugin is activated network-wide.
	 */
	public function is_plugin_active_for_network() {
		if ( ! function_exists( 'is_plugin_active_for_network' ) && is_multisite() ) {
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
		}
		if ( is_multisite() && is_plugin_active_for_network( plugin_basename( SWIS_PLUGIN_FILE ) ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Activation hook.
	 *
	 * @param bool $network_wide If the plugin is network activated.
	 * @return bool True if advanced-cache.php exists and WP_CACHE defined. False otherwise.
	 */
	public function on_activation( $network_wide ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if ( $this->should_disable_caching() ) {
			$this->set_option( 'cache', false );
			return true;
		}
		// Activation & setup routine.
		$this->each_site( $network_wide, array( $this, 'install_backend' ) );

		// Copy advanced cache file and define WP_CACHE.
		Disk_Cache::setup();
	}

	/**
	 * Upgrade hook (or core, theme, plugin, or translation updates).
	 *
	 * @param object $obj WP_Upgrader instance.
	 * @param array  $data Extra data related to the upgrade.
	 */
	public function on_upgrade( $obj, $data ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if ( empty( $data['action'] || 'update' !== $data['action'] ) ) {
			return;
		}
		// For theme updates.
		if ( ! empty( $data['type'] ) && 'theme' === $data['type'] && isset( $data['themes'] ) ) {
			$updated_themes = (array) $data['themes'];
			$sites_themes   = $this->each_site( is_multisite(), 'wp_get_theme' );

			// Check the themes for each site.
			foreach ( $sites_themes as $blog_id => $site_theme ) {
				// If the active or parent theme has been updated, clear the site cache.
				if ( in_array( $site_theme->stylesheet, $updated_themes, true ) || in_array( $site_theme->template, $updated_themes, true ) ) {
					$this->clear_site_cache_by_blog_id( $blog_id );
				}
			}
		}

		// If option enabled clear complete cache on any plugin update.
		if ( Cache_Engine::$settings['clear_complete_cache_on_changed_plugin'] ) {
			$this->clear_site_cache();
		}

		// Check for updated plugins.
		if ( ! empty( $data['type'] ) && 'plugin' === $data['type'] && isset( $data['plugins'] ) ) {
			$updated_plugins = (array) $data['plugins'];

			// If SWIS has been updated.
			if ( in_array( plugin_basename( SWIS_PLUGIN_FILE ), $updated_plugins, true ) ) {
				// Do update routine.
				$this->on_swis_update();
			} else {
				$network_plugins = ( is_multisite() ) ? array_flip( (array) get_site_option( 'active_sitewide_plugins', array() ) ) : array();

				// If a network-activated plugin has been updated, go nuclear!
				if ( ! empty( array_intersect( $updated_plugins, $network_plugins ) ) ) {
					$this->clear_complete_cache();
				} else {
					// Check each site otherwise.
					$sites_plugins = $this->each_site( is_multisite(), 'get_option', array( 'active_plugins', array() ) );
					foreach ( $sites_plugins as $blog_id => $site_plugins ) {
						if ( ! empty( array_intersect( $updated_plugins, (array) $site_plugins ) ) ) {
							$this->clear_site_cache_by_blog_id( $blog_id );
						}
					}
				}
			}
		}
	}

	/**
	 * SWIS Performance update actions for caching engine.
	 */
	public function on_swis_update() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$network_wide = $this->is_plugin_active_for_network();

		// Clean cache settings files.
		$this->each_site( $network_wide, '\SWIS\Disk_Cache::clean' );

		if ( $this->should_disable_caching() ) {
			$this->set_option( 'cache', false );
			return;
		}

		// Create cache settings files.
		$this->each_site( $network_wide, array( $this, 'install_backend' ) );

		Disk_Cache::setup();

		$this->clear_complete_cache();
	}

	/**
	 * Deactivation hook.
	 *
	 * @param bool $network_wide If the plugin is network activated.
	 */
	public function on_deactivation( $network_wide ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		// Deactivation & tear-down/cleanup routine.
		$this->each_site( $network_wide, '\SWIS\Disk_Cache::clean' );
		$this->each_site( $network_wide, array( $this, 'clear_site_cache' ) );
	}

	/**
	 * Install SWIS Cache on new site in multisite network.
	 *
	 * @param object $new_site WP_Site instance for new site.
	 */
	public function install_later( $new_site ) {
		if ( ! is_plugin_active_for_network( plugin_basename( SWIS_PLUGIN_FILE ) ) ) {
			return;
		}
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		// Switch to the new blog.
		switch_to_blog( (int) $new_site->blog_id );

		// Initialize settings, and create advanced cache settings file.
		$this->install_backend();

		restore_current_blog();
	}

	/**
	 * Create or update advanced cache settings.
	 */
	public function install_backend() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		// Runs settings through validation and creates the advanced cache settings file.
		Disk_Cache::create_settings_file( $this->get_settings() );
	}

	/**
	 * Uninstall SWIS Cache from deleted site in multisite network.
	 *
	 * @param object $old_site WP_Site instance for deleted site.
	 */
	public function uninstall_later( $old_site ) {
		if ( ! $this->is_plugin_active_for_network() ) {
			return;
		}
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		// Delete advanced cache settings file.
		Disk_Cache::clean();

		// Clear complete cache of deleted site.
		$this->clear_site_cache_by_blog_id( (int) $old_site->blog_id, false );
	}

	/**
	 * Run a function on each site.
	 *
	 * @param bool   $network Whether or not to perform the action on each site in network.
	 * @param string $callback The callback function to run.
	 * @param array  $callback_params The callback function parameters.
	 * @return array The returned value(s) from callback function.
	 */
	private function each_site( $network, $callback, $callback_params = array() ) {
		$callback_return = array();

		if ( $network ) {
			$blog_ids = $this->get_blog_ids();
			// Switch to each site in network.
			foreach ( $blog_ids as $blog_id ) {
				switch_to_blog( $blog_id );
				$callback_return[ $blog_id ] = call_user_func_array( $callback, $callback_params );
				restore_current_blog();
			}
		} else {
			$blog_id                     = 1;
			$callback_return[ $blog_id ] = (int) call_user_func_array( $callback, $callback_params );
		}

		return $callback_return;
	}

	/**
	 * Plugin activation and deactivation hooks.
	 */
	public function on_plugin_activation_deactivation() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		// If option enabled, clear complete cache on any plugin activation or deactivation.
		if ( Cache_Engine::$settings['clear_complete_cache_on_changed_plugin'] ) {
			$this->clear_site_cache();
		}
	}

	/**
	 * Get the SWIS Cache settings. If they don't exist, return defaults.
	 *
	 * @return array SWIS Cache options and defaults.
	 */
	public function get_settings() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$cache_settings   = $this->get_option( 'cache_settings', array() );
		$default_settings = array(
			'permalink_structure'                    => (string) $this->get_permalink_structure(),
			'expires'                                => 0,
			'clear_complete_cache_on_saved_post'     => 0,
			'clear_complete_cache_on_saved_comment'  => 0,
			'clear_complete_cache_on_changed_plugin' => 1,
			'webp'                                   => 0,
			'mobile'                                 => 0,
			'exclusions'                             => '',
			'excluded_cookies'                       => '',
			'excluded_query_strings'                 => '',
		);
		foreach ( $default_settings as $name => $value ) {
			if ( defined( 'SWIS_CACHE_' . strtoupper( $name ) ) ) {
				$cache_settings[ $name ] = constant( 'SWIS_CACHE_' . strtoupper( $name ) );
			}
		}
		return wp_parse_args( $cache_settings, $default_settings );
	}

	/**
	 * Validate the user-defined exclusions.
	 *
	 * @param array $user_exclusions A list of user-specified exclusions.
	 * @return array The validations exclusions list.
	 */
	function validate_user_exclusions( $user_exclusions ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$valid_exclusions = array( '/checkout/' );
		if ( ! empty( $user_exclusions ) ) {
			if ( is_string( $user_exclusions ) ) {
				$valid_exclusions = array( $user_exclusions );
			}
			if ( is_array( $user_exclusions ) ) {
				foreach ( $user_exclusions as $exclusion ) {
					if ( ! is_string( $exclusion ) ) {
						continue;
					}
					$valid_exclusions[] = $exclusion;
				}
			}
		}
		return $valid_exclusions;
	}

	/**
	 * Get blog IDs.
	 *
	 * @return array List of blog IDs.
	 */
	private function get_blog_ids() {
		$blog_ids = array( 1 );
		if ( is_multisite() ) {
			global $wpdb;
			$blog_ids = array_map( 'absint', $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) );
		}
		return $blog_ids;
	}

	/**
	 * Get blog path.
	 *
	 * @return string Blog path from site address URL, empty otherwise.
	 */
	public static function get_blog_path() {
		$site_url_path        = parse_url( home_url(), PHP_URL_PATH );
		$site_url_path        = rtrim( $site_url_path, '/' );
		$site_url_path_pieces = explode( '/', $site_url_path );

		// Get last piece in case installation is in a subdirectory.
		$blog_path = ( ! empty( end( $site_url_path_pieces ) ) ) ? '/' . end( $site_url_path_pieces ) . '/' : '';
		return $blog_path;
	}

	/**
	 * Get blog paths.
	 *
	 * @return array List of blog paths.
	 */
	public function get_blog_paths() {
		$blog_paths = array( '/' );
		if ( is_multisite() ) {
			global $wpdb;
			$blog_paths = $wpdb->get_col( "SELECT path FROM $wpdb->blogs" );
		}
		return $blog_paths;
	}

	/**
	 * Get the current permalink structure.
	 *
	 * @return string The permalink structure, as a string.
	 */
	private function get_permalink_structure() {
		// Get permalink structure.
		$permalink_structure = get_option( 'permalink_structure' );

		// Permalink structure is custom and has a trailing slash.
		if ( $permalink_structure && preg_match( '/\/$/', $permalink_structure ) ) {
			return 'has_trailing_slash';
		}

		// Permalink structure is custom and does not have a trailing slash.
		if ( $permalink_structure && ! preg_match( '/\/$/', $permalink_structure ) ) {
			return 'no_trailing_slash';
		}

		// Permalink structure is not custom, and sucks eggs.
		if ( empty( $permalink_structure ) ) {
			return 'plain';
		}
	}

	/**
	 * Get cache directory size.
	 *
	 * @return int The cache size in bytes
	 */
	public function get_cache_size() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$size = get_transient( $this->get_cache_size_transient_name() );
		if ( ! $size ) {
			$size = Disk_Cache::get_cache_size();
			set_transient( $this->get_cache_size_transient_name(), $size, MINUTE_IN_SECONDS * 15 );
		}
		return $size;
	}

	/**
	 * Get the cache size transient name.
	 *
	 * @return string The transient name for the given/current blog.
	 */
	private function get_cache_size_transient_name() {
		return 'swis_cache_size';
	}

	/**
	 * Get the cache cleared transient name used for the clear notice.
	 *
	 * @return string The transient name based on the user ID.
	 */
	private function get_cache_cleared_transient_name() {
		return 'swis_cache_cleared_' . get_current_user_id();
	}

	/**
	 * Add admin-bar links.
	 *
	 * @param object $wp_admin_bar The WP Admin Bar object, passed by reference.
	 */
	public function add_admin_bar_items( $wp_admin_bar ) {
		if ( ! $this->user_can_clear_cache() ) {
			return;
		}

		// Get clear complete cache button title.
		$title = ( is_multisite() && is_network_admin() ) ? esc_html__( 'Clear Network Cache', 'swis-performance' ) : esc_html__( 'Clear Site Cache', 'swis-performance' );

		if ( is_admin() ) {
			$wp_admin_bar->add_menu(
				array(
					'id'     => 'swis',
					'parent' => null,
					'title'  => '<a href="' . admin_url( 'options-general.php?page=swis-performance-options' ) . '"><span id="swis-slim-show-top"><span class="ab-icon"></span><span class="ab-label">' . __( 'SWIS', 'swis-performance' ) . '</span></span></a>',
				)
			);
		}
		// Add the clear cache button in admin bar.
		$wp_admin_bar->add_menu(
			array(
				'id'     => 'swis-clear-cache',
				'href'   => wp_nonce_url(
					add_query_arg(
						array(
							'_cache'  => 'swis-cache',
							'_action' => 'swis_cache_clear',
						)
					),
					'swis_cache_clear_nonce'
				),
				'parent' => 'swis',
				'title'  => '<span class="ab-item">' . $title . '</span>',
				'meta'   => array(
					'title' => $title,
				),
			)
		);

		// Add Clear URL Cache button in admin bar.
		if ( ! is_admin() ) {
			$wp_admin_bar->add_menu(
				array(
					'id'     => 'swis-clear-page-cache',
					'href'   => wp_nonce_url(
						add_query_arg(
							array(
								'_cache'  => 'swis-cache',
								'_action' => 'swis_cache_clearurl',
							)
						),
						'swis_cache_clear_nonce'
					),
					'parent' => 'swis',
					'title'  => '<span class="ab-item">' . esc_html__( 'Clear Page Cache', 'swis-performance' ) . '</span>',
					'meta'   => array(
						'title' => esc_html__( 'Clear Page Cache', 'swis-performance' ),
					),
				)
			);
		}
	}

	/**
	 * Check if a user can clear the cache.
	 *
	 * @return bool True if they can, false if they can't.
	 */
	private function user_can_clear_cache() {
		// Check user permissions.
		$permissions = apply_filters( 'swis_performance_admin_permissions', 'manage_options' );
		if ( apply_filters( 'user_can_clear_cache', current_user_can( $permissions ) ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Process a request to clear the cache.
	 */
	public function process_clear_cache_request() {
		// Check if this is a cache clear request.
		if (
			empty( $_GET['_cache'] ) || // The _cache arg is empty.
			empty( $_GET['_action'] ) || // The _action arg is empty.
			'swis-cache' !== $_GET['_cache'] || // The _cache arg isn't what it ought to be.
			( 'swis_cache_clear' !== $_GET['_action'] && 'swis_cache_clearurl' !== $_GET['_action'] ) // The _action param isn't one we recognize.
		) {
			return;
		}
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		// Verify nonce.
		if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'swis_cache_clear_nonce' ) ) {
			return;
		}

		// Check user has permissions to clear cache.
		if ( ! $this->user_can_clear_cache() ) {
			return;
		}

		if ( 'swis_cache_clearurl' === $_GET['_action'] ) {
			// Get clear URL without query string.
			$clear_url = $this->parse_url( home_url(), PHP_URL_SCHEME ) . '://' . Cache_Engine::$request_headers['Host'] . add_query_arg( null, null );
			$this->clear_page_cache_by_url( $clear_url );
		} elseif ( 'swis_cache_clear' === $_GET['_action'] ) {
			$this->each_site( ( is_multisite() && is_network_admin() ), array( $this, 'clear_site_cache' ) );
		}

		if ( is_admin() ) {
			set_transient( $this->get_cache_cleared_transient_name(), 1 );
		}

		wp_safe_redirect( remove_query_arg( array( '_cache', '_action', '_wpnonce' ) ) );
		exit();
	}

	/**
	 * Display notice after clearing the cache.
	 */
	public function cache_cleared_notice() {
		// Check user has permissions to clear cache.
		if ( ! $this->user_can_clear_cache() ) {
			return;
		}
		if ( get_transient( $this->get_cache_cleared_transient_name() ) ) {
			printf(
				'<div class="notice notice-success is-dismissible"><p>%s</p></div>',
				esc_html__( 'The cache has been cleared.', 'swis-performance' )
			);
			delete_transient( $this->get_cache_cleared_transient_name() );
		}
	}

	/**
	 * When any published post type is updated or sent to the trash.
	 *
	 * @param int $post_id The post ID number.
	 */
	public function on_save_trash_post( $post_id ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$post_status = get_post_status( $post_id );
		if ( 'publish' === $post_status ) {
			$this->clear_cache_on_post_save( $post_id );
		}
	}

	/**
	 * Clear the cache based on the pre_post_update hook.
	 *
	 * @param int   $post_id The post ID number.
	 * @param array $post_data The unslashed post data.
	 */
	public function on_pre_post_update( $post_id, $post_data ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		$old_post_status = get_post_status( $post_id );
		$new_post_status = $post_data['post_status'];

		// If any published post's status has changed.
		if ( 'publish' === $old_post_status && 'trash' !== $new_post_status ) {
			$this->clear_cache_on_post_save( $post_id );
		}
	}

	/**
	 * Clear the cache if a comment is posted.
	 *
	 * @param int        $comment_id The comment ID number.
	 * @param int|string $comment_approved 1 if the comment is approved, 0 if not, 'spam' if spam.
	 */
	public function on_comment_post( $comment_id, $comment_approved ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		// Check if comment is approved.
		if ( 1 === $comment_approved ) {
			$this->clear_cache_on_comment_save( $comment_id );
		}
	}


	/**
	 * Clear cache if a comment is edited.
	 *
	 * @param int   $comment_id The comment ID number.
	 * @param array $comment_data Data connected to the comment.
	 */
	public function on_edit_comment( $comment_id, $comment_data ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$comment_approved = (int) $comment_data['comment_approved'];
		if ( 1 === $comment_approved ) {
			$this->clear_cache_on_comment_save( $comment_id );
		}
	}

	/**
	 * Clear cache if comment status changes.
	 *
	 * @param string $new_status The new comment status.
	 * @param string $old_status The old comment status.
	 * @param object $comment WP_Comment object.
	 */
	public function on_transition_comment_status( $new_status, $old_status, $comment ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		// Check if status has been changed to or from 'approved'.
		if ( 'approved' === $old_status || 'approved' === $new_status ) {
			$this->clear_cache_on_comment_save( $comment );
		}
	}

	/**
	 * WooCommerce stock hooks
	 *
	 * @param int|object $product The product ID or a WC_Product instance.
	 */
	public function on_woocommerce_stock_update( $product ) {
		// Get the product ID.
		if ( is_int( $product ) ) {
			$product_id = $product;
		} elseif ( is_object( $product ) ) {
			$product_id = $product->get_id();
		} else {
			return;
		}
		$this->clear_cache_on_post_save( $product_id );
	}

	/**
	 * Clear the whole cache.
	 */
	public function clear_complete_cache() {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$network_wide = $this->is_plugin_active_for_network();

		$this->each_site( $network_wide, array( $this, 'clear_site_cache' ) );

		// Delete cache size transient(s).
		$this->each_site( $network_wide, 'delete_transient', array( $this->get_cache_size_transient_name() ) );
	}

	/**
	 * Clear the cache for a site.
	 */
	public function clear_site_cache() {
		$this->clear_site_cache_by_blog_id( get_current_blog_id() );
	}

	/**
	 * Clear cached pages that might have changed from any new or updated post.
	 *
	 * @param WP_Post $post The post instance.
	 */
	public function clear_associated_cache( $post ) {
		// Clear post type archives.
		$this->clear_post_type_archives_cache( $post->post_type );

		// Clear taxonomy archives.
		$this->clear_taxonomies_archives_cache_by_post_id( $post->ID );

		if ( 'post' === $post->post_type ) {
			$this->clear_author_archives_cache_by_user_id( $post->post_author );
			$this->clear_date_archives_cache_by_post_id( $post->ID );
		}
	}

	/**
	 * Clear post type archives page cache.
	 *
	 * @param string $post_type The post type to clear.
	 */
	public function clear_post_type_archives_cache( $post_type ) {
		$post_type_archives_url = get_post_type_archive_link( $post_type );

		// If an archive page exists for this post type, clear the archive page and its pagination page(s) cache.
		if ( ! empty( $post_type_archives_url ) ) {
			$this->clear_page_cache_by_url( $post_type_archives_url, 'pagination' );
		}
	}

	/**
	 * Clear taxonomy archives pages cache by post ID.
	 *
	 * @param int $post_id The post ID number.
	 */
	public function clear_taxonomies_archives_cache_by_post_id( $post_id ) {
		$taxonomies = get_taxonomies();

		foreach ( $taxonomies as $taxonomy ) {
			if ( wp_count_terms( $taxonomy ) > 0 ) {
				// Get terms attached to post.
				$term_ids = wp_get_post_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
				foreach ( $term_ids as $term_id ) {
					$term_archives_url = get_term_link( (int) $term_id, $taxonomy );
					// If the term archive URL exists and does not have a query string.
					if ( ! is_wp_error( $term_archives_url ) && false === strpos( $term_archives_url, '?' ) ) {
						// Clear taxonomy archives page and its pagination page(s) cache.
						$this->clear_page_cache_by_url( $term_archives_url, 'pagination' );
					}
				}
			}
		}
	}

	/**
	 * Clear author archives page cache by user ID.
	 *
	 * @param int $user_id The user ID of the author.
	 */
	public function clear_author_archives_cache_by_user_id( $user_id ) {
		// Get author archives URL.
		$author_username     = get_the_author_meta( 'user_login', $user_id );
		$author_base         = $GLOBALS['wp_rewrite']->author_base;
		$author_archives_url = home_url( '/' ) . $author_base . '/' . $author_username;

		// Clear author archives page and its pagination page(s) cache.
		$this->clear_page_cache_by_url( $author_archives_url, 'pagination' );
	}

	/**
	 * Clear date archives page cache.
	 *
	 * @param int $post_id The post ID number.
	 */
	public function clear_date_archives_cache_by_post_id( $post_id ) {
		// Get post dates.
		$post_date_day   = get_the_date( 'd', $post_id );
		$post_date_month = get_the_date( 'm', $post_id );
		$post_date_year  = get_the_date( 'Y', $post_id );

		// Get post date archive URLs.
		$date_archives_day_url   = get_day_link( $post_date_year, $post_date_month, $post_date_day );
		$date_archives_month_url = get_month_link( $post_date_year, $post_date_month );
		$date_archives_year_url  = get_year_link( $post_date_year );

		// Clear date archive pages and their pagination pages cache.
		$this->clear_page_cache_by_url( $date_archives_day_url, 'pagination' );
		$this->clear_page_cache_by_url( $date_archives_month_url, 'pagination' );
		$this->clear_page_cache_by_url( $date_archives_year_url, 'pagination' );
	}

	/**
	 * Clear page cache by post ID.
	 *
	 * @param int    $post_id The post ID number.
	 * @param string $clear_type Clear the `pagination` cache or all `subpages` cache instead of only the cached `page`.
	 */
	public function clear_page_cache_by_post_id( $post_id, $clear_type = 'page' ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if ( empty( $post_id ) ) {
			return;
		}
		if ( ! is_int( $post_id ) && is_numeric( $post_id ) ) {
			$post_id = (int) $post_id;
		}
		if ( ! is_int( $post_id ) ) {
			return;
		}
		$page_url = get_permalink( $post_id );

		// Clear page cache for post.
		if ( ! empty( $page_url ) && false === strpos( $page_url, '?' ) ) {
			$this->clear_page_cache_by_url( $page_url, $clear_type );
		}
	}

	/**
	 * Clear page cache by URL.
	 *
	 * @param string $clear_url URL of a page.
	 * @param string $clear_type Clear the `pagination` cache or all `subpages` cache instead of only the cached `page`.
	 */
	public function clear_page_cache_by_url( $clear_url, $clear_type = 'page' ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if ( ! is_string( $clear_url ) ) {
			return;
		}
		// Validate the given URL.
		if ( ! filter_var( $clear_url, FILTER_VALIDATE_URL ) ) {
			return;
		}

		Disk_Cache::clear_cache( $clear_url, $clear_type );
	}

	/**
	 * Clear cache by blog ID.
	 *
	 * @param int  $blog_id The blog ID number.
	 * @param bool $delete_cache_size_transient Whether or not the cache size transient should be deleted. Optional, defaults to true.
	 */
	public function clear_site_cache_by_blog_id( $blog_id, $delete_cache_size_transient = true ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		if ( ! is_int( $blog_id ) && is_numeric( $blog_id ) ) {
			$blog_id = (int) $blog_id;
		}
		if ( ! is_int( $blog_id ) ) {
			return;
		}

		// Check if the blog ID exists.
		if ( ! in_array( $blog_id, $this->get_blog_ids(), true ) ) {
			return;
		}

		// Make sure when we clear a site cache that we've made it the "current" site/blog, so that all subsequent actions are site-specific.
		if ( is_multisite() ) {
			switch_to_blog( $blog_id );
		}

		// Disable the hook that fires when a page/URL is cleared.
		$this->fire_page_cache_cleared_hook = false;

		// Get clear URL.
		$site_url = home_url();

		// Get all cache objects for the site.
		$site_objects = Disk_Cache::get_site_objects( $site_url );

		// Then clear the cache for each page.
		foreach ( $site_objects as $site_object ) {
			$this->clear_page_cache_by_url( trailingslashit( $site_url ) . $site_object, 'subpages' );
		}

		// Finally clear the home page cache.
		$this->clear_page_cache_by_url( $site_url );

		// Delete cache size transient.
		if ( $delete_cache_size_transient ) {
			delete_transient( $this->get_cache_size_transient_name() );
		}

		if ( is_multisite() ) {
			restore_current_blog();
		}
	}

	/**
	 * Clear cache when any post type is created or updated.
	 *
	 * @param int|object $post The post ID number or a WP_POST instance.
	 */
	public function clear_cache_on_post_save( $post ) {
		if ( ! is_object( $post ) ) {
			if ( is_int( $post ) ) {
				$post = get_post( $post );
			} else {
				return;
			}
		}

		// If setting enabled clear complete cache.
		if ( Cache_Engine::$settings['clear_complete_cache_on_saved_post'] ) {
			$this->clear_site_cache();
			// Otherwise, just clear the associated caches.
		} else {
			$this->clear_page_cache_by_post_id( $post->ID );
			// Clear associated cache.
			$this->clear_associated_cache( $post );
		}
	}

	/**
	 * Clear the cache when a comment has been posted or modified.
	 *
	 * @param int|object $comment The comment ID# or a WP_Comment instance.
	 */
	public function clear_cache_on_comment_save( $comment ) {
		if ( ! is_object( $comment ) ) {
			if ( is_int( $comment ) ) {
				$comment = get_comment( $comment );
			} else {
				return;
			}
		}

		if ( Cache_Engine::$settings['clear_complete_cache_on_saved_comment'] ) {
			$this->clear_site_cache();
		} else {
			$this->clear_page_cache_by_post_id( $comment->comment_post_ID );
		}
	}

	/**
	 * Check plugin requirements.
	 */
	public function requirements_check() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		// Check advanced-cache.php drop-in.
		if ( ! get_option( 'swis_activation' ) && ! is_file( WP_CONTENT_DIR . '/advanced-cache.php' ) ) {
			echo '<div class="notice notice-warning"><p>' .
				sprintf(
					/* translators: 1: SWIS Performance 2: advanced-cache.php 3: wp-content/plugins/swis-performance/assets/ 4: wp-content/ */
					esc_html__( '%1$s requires the %2$s drop-in. Please disable and then re-enable Page Caching to automatically copy this file or manually copy it from the %3$s directory to the %4$s directory.', 'swis-performance' ),
					'<strong>SWIS Performance</strong>',
					'<code>advanced-cache.php</code>',
					'<code>wp-content/plugins/swis-performance/assets/</code>',
					'<code>wp-content/</code>'
				) .
				'</p></div>';
		}

		// Warning if no custom permlink structure.
		if ( 'plain' === Cache_Engine::$settings['permalink_structure'] && current_user_can( 'manage_options' ) ) {
			echo '<div class="notice notice-warning"><p>' .
				sprintf(
					/* translators: 1: SWIS Performance 2: Permalink Settings */
					esc_html__( '%1$s requires custom permalinks (something besides the "Plain" permalink structure). Please change the permalink structure in the %2$s.', 'swis-performance' ),
					'<strong>SWIS Performance</strong>',
					'<a href="' . esc_url( admin_url( 'options-permalink.php' ) ) . '">' . esc_html__( 'Permalink Settings', 'swis-performance' ) . '</a>'
				) .
				'</p></div>';
		}

		// Permission check, can't do much without a writable cache directory.
		if ( file_exists( Disk_Cache::$cache_dir ) && ! is_writable( Disk_Cache::$cache_dir ) ) {
			echo '<div class="notice notice-warning"><p>' .
				sprintf(
					/* translators: 1: SWIS Performance 2: 755 3: wp-content/swis/ 4: file permissions */
					esc_html__( '%1$s requires write permissions (%2$s) in the %3$s directory. Please change the %4$s.', 'swis-performance' ),
					'<strong>SWIS Performance</strong>',
					'<code>755</code>',
					'<code>wp-content/swis/</code>',
					'<a href="https://wordpress.org/support/article/changing-file-permissions/" target="_blank">' . esc_html__( 'file permissions', 'swis-performance' ) . '</a>'
				) .
			'</p></div>';
		}

		// Check WP_CACHE constant.
		if ( ( ! defined( 'WP_CACHE' ) || ! WP_CACHE ) && ! get_option( 'swis_activation' ) ) {
			// Check also to see if permissions allow modifying wp-config.php.
			if ( file_exists( trailingslashit( ABSPATH ) . 'wp-config.php' ) && ! is_writable( trailingslashit( ABSPATH ) . 'wp-config.php' ) ) {
				echo '<div class="notice notice-warning"><p>' .
					sprintf(
						/* translators: 1: SWIS Performance 2: define( 'WP_CACHE', true ); 3: wp-config.php 4: file permissions */
						esc_html__( '%1$s could not set %2$s in the %3$s file. Please change the %4$s or add it manually to enable Page Caching.', 'swis-performance' ),
						'<strong>SWIS Performance</strong>',
						"<code>define( 'WP_CACHE', true );</code>",
						'<code>wp-config.php</code>',
						'<a href="https://wordpress.org/support/article/changing-file-permissions/" target="_blank">' . esc_html__( 'file permissions', 'swis-performance' ) . '</a>'
					) .
					'</p></div>';
			} else {
				echo '<div class="notice notice-warning"><p>' .
					sprintf(
						/* translators: 1: define( 'WP_CACHE', true ); 2: wp-config.php */
						esc_html__( '%1$s requires %2$s to be set for Page Caching. Please set this in the %3$s file.', 'swis-performance' ),
						'<strong>SWIS Performance</strong>',
						"<code>define( 'WP_CACHE', true );</code>",
						'<code>wp-config.php</code>'
					) .
					'</p></div>';
			}
		}
	}

	/**
	 * Validate a regex pattern.
	 *
	 * @param string $regex A (potential) regex pattern.
	 * @return string The regex pattern or an empty string if input is invalid.
	 */
	public function validate_regex( $regex ) {
		if ( ! empty( $regex ) ) {
			if ( ! preg_match( '/^\/.*\/$/', $regex ) ) {
				$regex = '/' . $regex . '/';
			}

			// If it returns false, that's an error condition for a bogus pattern.
			if ( @preg_match( $regex, null ) === false ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
				return '';
			}

			return sanitize_text_field( $regex );
		}
		return '';
	}

	/**
	 * Validate settings.
	 *
	 * @param array $settings The cache settings.
	 * @return array The validated settings.
	 */
	public function validate_settings( $settings ) {
		// Check if empty.
		if ( empty( $settings ) || ! is_array( $settings ) ) {
			return;
		}

		$system_settings = array(
			'permalink_structure' => (string) $this->get_permalink_structure(),
		);

		$validated_settings = array(
			'expires'                                => ! empty( $settings['expires'] ) ? (int) $settings['expires'] : 0,
			'clear_complete_cache_on_changed_plugin' => 1, /* Change this if we add it to the UI: (int) ( ! empty( $data['clear_complete_cache_on_changed_plugin'] ) ), */
			'clear_complete_cache_on_saved_post'     => (int) ( ! empty( $settings['clear_complete_cache_on_saved_post'] ) ),
			'clear_complete_cache_on_saved_comment'  => (int) ( ! empty( $settings['clear_complete_cache_on_saved_comment'] ) ),
			'webp'                                   => (int) ( ! empty( $settings['webp'] ) ),
			'mobile'                                 => (int) ( ! empty( $settings['mobile'] ) ),
			'exclusions'                             => ! empty( $settings['exclusions'] ) ? $this->validate_user_exclusions( $settings['exclusions'] ) : '',
			'excluded_cookies'                       => ! empty( $settings['excluded_cookies'] ) ? (string) $this->validate_regex( $settings['excluded_cookies'] ) : '',
			'excluded_query_strings'                 => ! empty( $settings['excluded_query_strings'] ) ? (string) $this->validate_regex( $settings['excluded_query_strings'] ) : '',
		);
		$validated_settings = wp_parse_args( $validated_settings, $system_settings );
		return $validated_settings;
	}
}