Your IP : 216.73.217.78


Current Path : /home/seto/pribaltdom.com/wp-content/plugins/quick-cache/
Upload File :
Current File : /home/seto/pribaltdom.com/wp-content/plugins/quick-cache/quick-cache.inc.php

<?php
/**
 * Quick Cache Plugin
 *
 * @package quick_cache\plugin
 * @since 140422 First documented version.
 * @copyright WebSharks, Inc. <http://www.websharks-inc.com>
 * @license GNU General Public License, version 2
 */
namespace quick_cache
{
	if(!defined('WPINC')) // MUST have WordPress.
		exit('Do NOT access this file directly: '.basename(__FILE__));

	require_once dirname(__FILE__).'/includes/share.php';

	if(!class_exists('\\'.__NAMESPACE__.'\\plugin'))
	{
		/**
		 * Quick Cache Plugin
		 *
		 * @package quick_cache\plugin
		 * @since 140422 First documented version.
		 */
		class plugin extends share
		{
			/**
			 * Stub `__FILE__` location.
			 *
			 * @since 140422 First documented version.
			 *
			 * @var string Current `__FILE__` from the stub; NOT from this file.
			 *    Note that Quick Cache has a stub loader that checks for PHP v5.3 compat;
			 *    which is why we have this property. This is the stub `__FILE__`.
			 */
			public $file = '';

			/**
			 * An array of all default option values.
			 *
			 * @since 140422 First documented version.
			 *
			 * @var array Default options array; set by constructor.
			 */
			public $default_options = array();

			/**
			 * Configured option values.
			 *
			 * @since 140422 First documented version.
			 *
			 * @var array Options configured by site owner; set by constructor.
			 */
			public $options = array();

			/**
			 * General capability requirement.
			 *
			 * @since 140422 First documented version.
			 *
			 * @var string WordPress capability required to
			 *    administer QC in any environment; i.e. in multisite or otherwise.
			 */
			public $cap = '';

			/**
			 * Network capability requirement.
			 *
			 * @since 140422 First documented version.
			 *
			 * @var string WordPress capability required to
			 *    administer QC in a multisite network.
			 */
			public $network_cap = '';

			/**
			 * Uninstall capability requirement.
			 *
			 * @since 140829 Adding uninstall handler.
			 *
			 * @var string WordPress capability required to
			 *    completely uninstall/delete QC.
			 */
			public $uninstall_cap = '';

			/**
			 * Cache directory.
			 *
			 * @since 140605 Moving to a base directory.
			 *
			 * @var string Cache directory; relative to the configured base directory.
			 */
			public $cache_sub_dir = 'cache';

			/**
			 * Used by methods in this class to help optimize performance.
			 *
			 * @since 140725 Reducing auto-purge overhead.
			 *
			 * @var array An instance-based cache used by methods in this class.
			 */
			public $cache = array();

			/**
			 * Used by the plugin's uninstall handler.
			 *
			 * @since 140829 Adding uninstall handler.
			 *
			 * @var boolean If FALSE, run without any hooks.
			 */
			public $enable_hooks = TRUE;

			/**
			 * Quick Cache plugin constructor.
			 *
			 * @param boolean $enable_hooks Defaults to a TRUE value.
			 *    If FALSE, setup runs but without adding any hooks.
			 *
			 * @since 140422 First documented version.
			 */
			public function __construct($enable_hooks = TRUE)
			{
				parent::__construct(); // Shared constructor.

				/* -------------------------------------------------------------- */

				$this->enable_hooks = (boolean)$enable_hooks;
				$this->file         = preg_replace('/\.inc\.php$/', '.php', __FILE__);

				/* -------------------------------------------------------------- */

				if(!$this->enable_hooks) // Without hooks?
					return; // Stop here; construct without hooks.

				/* -------------------------------------------------------------- */

				add_action('after_setup_theme', array($this, 'setup'));
				register_activation_hook($this->file, array($this, 'activate'));
				register_deactivation_hook($this->file, array($this, 'deactivate'));
			}

			/**
			 * Setup the Quick Cache plugin.
			 *
			 * @since 140422 First documented version.
			 */
			public function setup()
			{
				if(isset($this->cache[__FUNCTION__]))
					return; // Already setup. Once only!
				$this->cache[__FUNCTION__] = -1;

				if($this->enable_hooks) // Hooks enabled?
					do_action('before__'.__METHOD__, get_defined_vars());

				/* -------------------------------------------------------------- */

				load_plugin_textdomain($this->text_domain);

				$this->default_options = array(
					/* Core/systematic plugin options. */

					'version'                          => $this->version,
					'crons_setup'                      => '0', // `0` or timestamp.

					/* Primary switch; enable? */

					'enable'                           => '0', // `0|1`.

					/* Related to debugging. */

					'debugging_enable'                 => '1',
					// `0|1|2` // 2 indicates greater debugging detail.

					/* Related to cache directory. */

					'base_dir'                         => 'cache/quick-cache', // Relative to `WP_CONTENT_DIR`.
					'cache_max_age'                    => '7 days', // `strtotime()` compatible.

					/* Related to automatic cache clearing. */

					'cache_clear_home_page_enable'     => '1', // `0|1`.
					'cache_clear_posts_page_enable'    => '1', // `0|1`.

					'cache_clear_author_page_enable'   => '1', // `0|1`.

					'cache_clear_term_category_enable' => '1', // `0|1`.
					'cache_clear_term_post_tag_enable' => '1', // `0|1`.
					'cache_clear_term_other_enable'    => '0', // `0|1`.

					/* Misc. cache behaviors. */

					'allow_browser_cache'              => '0', // `0|1`.
					'get_requests'                     => '0', // `0|1`.
					'feeds_enable'                     => '0', // `0|1`.
					'cache_404_requests'               => '0', // `0|1`.

					/* Related to uninstallation routines. */

					'uninstall_on_deletion'            => '0', // `0|1`.

				); // Default options are merged with those defined by the site owner.
				$options               = (is_array($options = get_option(__NAMESPACE__.'_options'))) ? $options : array();
				if(is_multisite() && is_array($site_options = get_site_option(__NAMESPACE__.'_options')))
					$options = array_merge($options, $site_options); // Multisite network options.

				if(!$options && get_option('ws_plugin__qcache_configured')
				   && is_array($old_options = get_option('ws_plugin__qcache_options')) && $old_options
				) // Before the rewrite. Only if QC was previously configured w/ options.
				{
					$this->options['version'] = '2.3.6'; // Old options.

					if(!isset($options['enable']) && isset($old_options['enabled']))
						$options['enable'] = (string)(integer)$old_options['enabled'];

					if(!isset($options['debugging_enable']) && isset($old_options['enable_debugging']))
						$options['debugging_enable'] = (string)(integer)$old_options['enable_debugging'];

					if(!isset($options['allow_browser_cache']) && isset($old_options['allow_browser_cache']))
						$options['allow_browser_cache'] = (string)(integer)$old_options['allow_browser_cache'];

					if(!isset($options['when_logged_in']) && isset($old_options['dont_cache_when_logged_in']))
						$options['when_logged_in'] = ((string)(integer)$old_options['dont_cache_when_logged_in']) ? '0' : '1';

					if(!isset($options['get_requests']) && isset($old_options['dont_cache_query_string_requests']))
						$options['get_requests'] = ((string)(integer)$old_options['dont_cache_query_string_requests']) ? '0' : '1';

					if(!isset($options['exclude_uris']) && isset($old_options['dont_cache_these_uris']))
						$options['exclude_uris'] = (string)$old_options['dont_cache_these_uris'];

					if(!isset($options['exclude_refs']) && isset($old_options['dont_cache_these_refs']))
						$options['exclude_refs'] = (string)$old_options['dont_cache_these_refs'];

					if(!isset($options['exclude_agents']) && isset($old_options['dont_cache_these_agents']))
						$options['exclude_agents'] = (string)$old_options['dont_cache_these_agents'];

					if(!isset($options['version_salt']) && isset($old_options['version_salt']))
						$options['version_salt'] = (string)$old_options['version_salt'];
				}
				$this->default_options = apply_filters(__METHOD__.'__default_options', $this->default_options, get_defined_vars());
				$this->options         = array_merge($this->default_options, $options); // This considers old options also.
				$this->options         = apply_filters(__METHOD__.'__options', $this->options, get_defined_vars());
				$this->options         = array_intersect_key($this->options, $this->default_options);

				$this->options['base_dir'] = trim($this->options['base_dir'], '\\/'." \t\n\r\0\x0B");
				if(!$this->options['base_dir']) // Security enhancement; NEVER allow this to be empty.
					$this->options['base_dir'] = $this->default_options['base_dir'];

				$this->cap           = apply_filters(__METHOD__.'__cap', 'activate_plugins');
				$this->network_cap   = apply_filters(__METHOD__.'__network_cap', 'manage_network_plugins');
				$this->uninstall_cap = apply_filters(__METHOD__.'__uninstall_cap', 'delete_plugins');

				/* -------------------------------------------------------------- */

				if(!$this->enable_hooks) // Without hooks?
					return; // Stop here; setup without hooks.

				/* -------------------------------------------------------------- */

				add_action('init', array($this, 'check_advanced_cache'));
				add_action('init', array($this, 'check_blog_paths'));
				add_action('wp_loaded', array($this, 'actions'));

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

				add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_styles'));
				add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));

				add_action('all_admin_notices', array($this, 'all_admin_notices'));
				add_action('all_admin_notices', array($this, 'all_admin_errors'));

				add_action('network_admin_menu', array($this, 'add_network_menu_pages'));
				add_action('admin_menu', array($this, 'add_menu_pages'));

				add_action('upgrader_process_complete', array($this, 'upgrader_process_complete'), 10, 2);
				add_action('safecss_save_pre', array($this, 'jetpack_custom_css'), 10, 1);

				add_action('switch_theme', array($this, 'auto_clear_cache'));
				add_action('wp_create_nav_menu', array($this, 'auto_clear_cache'));
				add_action('wp_update_nav_menu', array($this, 'auto_clear_cache'));
				add_action('wp_delete_nav_menu', array($this, 'auto_clear_cache'));

				add_action('save_post', array($this, 'auto_clear_post_cache'));
				add_action('delete_post', array($this, 'auto_clear_post_cache'));
				add_action('clean_post_cache', array($this, 'auto_clear_post_cache'));
				add_action('post_updated', array($this, 'auto_clear_author_page_cache'), 10, 3);
				add_action('transition_post_status', array($this, 'auto_clear_post_cache_transition'), 10, 3);

				add_action('added_term_relationship', array($this, 'auto_clear_post_terms_cache'), 10, 1);
				add_action('delete_term_relationships', array($this, 'auto_clear_post_terms_cache'), 10, 1);

				add_action('trackback_post', array($this, 'auto_clear_comment_post_cache'));
				add_action('pingback_post', array($this, 'auto_clear_comment_post_cache'));
				add_action('comment_post', array($this, 'auto_clear_comment_post_cache'));
				add_action('transition_comment_status', array($this, 'auto_clear_comment_transition'), 10, 3);

				add_action('create_term', array($this, 'auto_clear_cache'));
				add_action('edit_terms', array($this, 'auto_clear_cache'));
				add_action('delete_term', array($this, 'auto_clear_cache'));

				add_action('add_link', array($this, 'auto_clear_cache'));
				add_action('edit_link', array($this, 'auto_clear_cache'));
				add_action('delete_link', array($this, 'auto_clear_cache'));

				add_filter('enable_live_network_counts', array($this, 'update_blog_paths'));

				add_filter('plugin_action_links_'.plugin_basename($this->file), array($this, 'add_settings_link'));

				/* -------------------------------------------------------------- */

				add_filter('cron_schedules', array($this, 'extend_cron_schedules'));

				if((integer)$this->options['crons_setup'] < 1382523750)
				{
					wp_clear_scheduled_hook('_cron_'.__NAMESPACE__.'_cleanup');
					wp_schedule_event(time() + 60, 'daily', '_cron_'.__NAMESPACE__.'_cleanup');

					$this->options['crons_setup'] = (string)time();
					update_option(__NAMESPACE__.'_options', $this->options); // Blog-specific.
					if(is_multisite()) update_site_option(__NAMESPACE__.'_options', $this->options);
				}
				add_action('_cron_'.__NAMESPACE__.'_cleanup', array($this, 'purge_cache'));

				/* -------------------------------------------------------------- */

				do_action('after__'.__METHOD__, get_defined_vars());
				do_action(__METHOD__.'_complete', get_defined_vars());
			}

			/**
			 * WordPress database instance.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return \wpdb Reference for IDEs.
			 */
			public function wpdb() // Shortcut for other routines.
			{
				return $GLOBALS['wpdb'];
			}

			/**
			 * Plugin activation hook.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to {@link \register_activation_hook()}
			 */
			public function activate()
			{
				$this->setup(); // Setup routines.

				if(!$this->options['enable'])
					return; // Nothing to do.

				$this->add_wp_cache_to_wp_config();
				$this->add_advanced_cache();
				$this->update_blog_paths();
				$this->auto_clear_cache();
			}

			/**
			 * Check current plugin version that installed in WP.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `admin_init` hook.
			 */
			public function check_version()
			{
				$current_version = $prev_version = $this->options['version'];
				if(version_compare($current_version, $this->version, '>='))
					return; // Nothing to do; we've already upgraded them.

				$current_version = $this->options['version'] = $this->version;
				update_option(__NAMESPACE__.'_options', $this->options); // Updates version.
				if(is_multisite()) update_site_option(__NAMESPACE__.'_options', $this->options);

				require_once dirname(__FILE__).'/includes/version-specific-upgrade.php';
				new version_specific_upgrade($prev_version);

				if($this->options['enable']) // Recompile.
				{
					$this->add_wp_cache_to_wp_config();
					$this->add_advanced_cache();
					$this->update_blog_paths();
				}
				$this->wipe_cache(); // Always wipe the cache; no exceptions.

				$this->enqueue_notice(__('<strong>Quick Cache:</strong> detected a new version of itself. Recompiling w/ latest version... wiping the cache... all done :-)', $this->text_domain), '', TRUE);
			}

			/**
			 * Plugin deactivation hook.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to {@link \register_deactivation_hook()}
			 */
			public function deactivate()
			{
				$this->setup(); // Setup routines.

				$this->remove_wp_cache_from_wp_config();
				$this->remove_advanced_cache();
				$this->clear_cache();
			}

			/**
			 * Plugin uninstall hook.
			 *
			 * @since 140829 Adding uninstall handler.
			 *
			 * @attaches-to {@link \register_uninstall_hook()} ~ via {@link uninstall()}
			 */
			public function uninstall()
			{
				$this->setup(); // Setup routines.

				if(!defined('WP_UNINSTALL_PLUGIN'))
					return; // Disallow.

				if(empty($GLOBALS[__NAMESPACE__.'_uninstalling']))
					return; // Not uninstalling.

				if(!class_exists('\\'.__NAMESPACE__.'\\uninstall'))
					return; // Expecting the uninstall class.

				if(!current_user_can($this->uninstall_cap))
					return; // Extra layer of security.

				$this->remove_wp_cache_from_wp_config();
				$this->remove_advanced_cache();
				$this->wipe_cache();

				if(!$this->options['uninstall_on_deletion'])
					return; // Nothing to do here.

				$this->delete_advanced_cache();
				$this->remove_base_dir();

				delete_option(__NAMESPACE__.'_options');
				if(is_multisite()) // Delete network options too.
					delete_site_option(__NAMESPACE__.'_options');

				delete_option(__NAMESPACE__.'_notices');
				delete_option(__NAMESPACE__.'_errors');

				wp_clear_scheduled_hook('_cron_'.__NAMESPACE__.'_cleanup');
			}

			/**
			 * Current request is for a pro version preview?
			 *
			 * @since 140422 First documented version.
			 *
			 * @return boolean TRUE if the current request is for a pro preview.
			 */
			public function is_pro_preview()
			{
				static $is;
				if(isset($is)) return $is;

				if(!empty($_REQUEST[__NAMESPACE__.'_pro_preview']))
					return ($is = TRUE);

				return ($is = FALSE);
			}

			/**
			 * URL to a Quick Cache plugin file.
			 *
			 * @since 140422 First documented version.
			 *
			 * @param string $file Optional file path; relative to plugin directory.
			 * @param string $scheme Optional URL scheme; defaults to the current scheme.
			 *
			 * @return string URL to plugin directory; or to the specified `$file` if applicable.
			 */
			public function url($file = '', $scheme = '')
			{
				if(!isset(static::$static[__FUNCTION__]['plugin_dir']))
					static::$static[__FUNCTION__]['plugin_dir'] = rtrim(plugin_dir_url($this->file), '/');
				$plugin_dir =& static::$static[__FUNCTION__]['plugin_dir'];

				$url = $plugin_dir.(string)$file;

				if($scheme) // A specific URL scheme?
					$url = set_url_scheme($url, (string)$scheme);

				return apply_filters(__METHOD__, $url, get_defined_vars());
			}

			/**
			 * Plugin action handler.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `wp_loaded` hook.
			 */
			public function actions()
			{
				if(!empty($_REQUEST[__NAMESPACE__]))
					require_once dirname(__FILE__).'/includes/actions.php';
			}

			/**
			 * Adds CSS for administrative menu pages.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `admin_enqueue_scripts` hook.
			 */
			public function enqueue_admin_styles()
			{
				if(empty($_GET['page']) || strpos($_GET['page'], __NAMESPACE__) !== 0)
					return; // Nothing to do; NOT a plugin page in the administrative area.

				$deps = array(); // Plugin dependencies.

				wp_enqueue_style(__NAMESPACE__, $this->url('/client-s/css/menu-pages.min.css'), $deps, $this->version, 'all');
			}

			/**
			 * Adds JS for administrative menu pages.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `admin_enqueue_scripts` hook.
			 */
			public function enqueue_admin_scripts()
			{
				if(empty($_GET['page']) || strpos($_GET['page'], __NAMESPACE__) !== 0)
					return; // Nothing to do; NOT a plugin page in the administrative area.

				$deps = array('jquery'); // Plugin dependencies.

				wp_enqueue_script(__NAMESPACE__, $this->url('/client-s/js/menu-pages.min.js'), $deps, $this->version, TRUE);
			}

			/**
			 * Creates network admin menu pages.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `network_admin_menu` hook.
			 */
			public function add_network_menu_pages()
			{
				add_menu_page(__('Quick Cache', $this->text_domain), __('Quick Cache', $this->text_domain),
				              $this->network_cap, __NAMESPACE__, array($this, 'menu_page_options'),
				              $this->url('/client-s/images/menu-icon.png'));
			}

			/**
			 * Creates admin menu pages.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `admin_menu` hook.
			 */
			public function add_menu_pages()
			{
				if(is_multisite()) return; // Multisite networks MUST use network admin area.

				add_menu_page(__('Quick Cache', $this->text_domain), __('Quick Cache', $this->text_domain),
				              $this->cap, __NAMESPACE__, array($this, 'menu_page_options'),
				              $this->url('/client-s/images/menu-icon.png'));
			}

			/**
			 * Adds link(s) to Quick Cache row on the WP plugins page.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `plugin_action_links_'.plugin_basename($this->file)` filter.
			 *
			 * @param array $links An array of the existing links provided by WordPress.
			 *
			 * @return array Revised array of links.
			 */
			public function add_settings_link($links)
			{
				$links[] = '<a href="options-general.php?page='.urlencode(__NAMESPACE__).'">'.__('Settings', $this->text_domain).'</a>';
				$links[] = '<br/><a href="'.esc_attr(add_query_arg(urlencode_deep(array('page' => __NAMESPACE__, __NAMESPACE__.'_pro_preview' => '1')), self_admin_url('/admin.php'))).'">'.__('Preview Pro Features', $this->text_domain).'</a>';
				$links[] = '<a href="'.esc_attr('http://www.websharks-inc.com/product/'.str_replace('_', '-', __NAMESPACE__).'/').'" target="_blank">'.__('Upgrade', $this->text_domain).'</a>';

				return apply_filters(__METHOD__, $links, get_defined_vars());
			}

			/**
			 * Loads the admin menu page options.
			 *
			 * @since 140422 First documented version.
			 *
			 * @see add_network_menu_pages()
			 * @see add_menu_pages()
			 */
			public function menu_page_options()
			{
				require_once dirname(__FILE__).'/includes/menu-pages.php';
				$menu_pages = new menu_pages();
				$menu_pages->options();
			}

			/**
			 * Render admin notices; across all admin dashboard views.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `all_admin_notices` hook.
			 */
			public function all_admin_notices()
			{
				if(($notices = (is_array($notices = get_option(__NAMESPACE__.'_notices'))) ? $notices : array()))
				{
					$notices = $updated_notices = array_unique($notices); // De-dupe.

					foreach(array_keys($updated_notices) as $_key) if(strpos($_key, 'persistent-') !== 0)
						unset($updated_notices[$_key]); // Leave persistent notices; ditch others.
					unset($_key); // Housekeeping after updating notices.

					update_option(__NAMESPACE__.'_notices', $updated_notices);
				}
				if(current_user_can($this->cap)) foreach($notices as $_key => $_notice)
				{
					$_dismiss = ''; // Initialize empty string; e.g. reset value on each pass.
					if(strpos($_key, 'persistent-') === 0) // A dismissal link is needed in this case?
					{
						$_dismiss_css = 'display:inline-block; float:right; margin:0 0 0 15px; text-decoration:none; font-weight:bold;';
						$_dismiss     = add_query_arg(urlencode_deep(array(__NAMESPACE__ => array('dismiss_notice' => array('key' => $_key)), '_wpnonce' => wp_create_nonce())));
						$_dismiss     = '<a style="'.esc_attr($_dismiss_css).'" href="'.esc_attr($_dismiss).'">'.__('dismiss &times;', $this->text_domain).'</a>';
					}
					echo apply_filters(__METHOD__.'__notice', '<div class="updated"><p>'.$_notice.$_dismiss.'</p></div>', get_defined_vars());
				}
				unset($_key, $_notice, $_dismiss_css, $_dismiss); // Housekeeping.
			}

			/**
			 * Enqueue an administrative notice.
			 *
			 * @since 140605 Adding enqueue notice/error methods.
			 *
			 * @param string  $notice HTML markup containing the notice itself.
			 *
			 * @param string  $persistent_key Optional. A unique key which identifies a particular type of persistent notice.
			 *    This defaults to an empty string. If this is passed, the notice is persistent; i.e. it continues to be displayed until dismissed by the site owner.
			 *
			 * @param boolean $push_to_top Optional. Defaults to a `FALSE` value.
			 *    If `TRUE`, the notice is pushed to the top of the stack; i.e. displayed above any others.
			 */
			public function enqueue_notice($notice, $persistent_key = '', $push_to_top = FALSE)
			{
				$notice         = (string)$notice;
				$persistent_key = (string)$persistent_key;

				$notices = get_option(__NAMESPACE__.'_notices');
				if(!is_array($notices)) $notices = array();

				if($persistent_key) // A persistent notice?
				{
					if(strpos($persistent_key, 'persistent-') !== 0)
						$persistent_key = 'persistent-'.$persistent_key;

					if($push_to_top) // Push this notice to the top?
						$notices = array($persistent_key => $notice) + $notices;
					else $notices[$persistent_key] = $notice;
				}
				else if($push_to_top) // Push to the top?
					array_unshift($notices, $notice);

				else $notices[] = $notice; // Default behavior.

				update_option(__NAMESPACE__.'_notices', $notices);
			}

			/**
			 * Render admin errors; across all admin dashboard views.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `all_admin_notices` hook.
			 */
			public function all_admin_errors()
			{
				if(($errors = (is_array($errors = get_option(__NAMESPACE__.'_errors'))) ? $errors : array()))
				{
					$errors = $updated_errors = array_unique($errors); // De-dupe.

					foreach(array_keys($updated_errors) as $_key) if(strpos($_key, 'persistent-') !== 0)
						unset($updated_errors[$_key]); // Leave persistent errors; ditch others.
					unset($_key); // Housekeeping after updating notices.

					update_option(__NAMESPACE__.'_errors', $updated_errors);
				}
				if(current_user_can($this->cap)) foreach($errors as $_key => $_error)
				{
					$_dismiss = ''; // Initialize empty string; e.g. reset value on each pass.
					if(strpos($_key, 'persistent-') === 0) // A dismissal link is needed in this case?
					{
						$_dismiss_css = 'display:inline-block; float:right; margin:0 0 0 15px; text-decoration:none; font-weight:bold;';
						$_dismiss     = add_query_arg(urlencode_deep(array(__NAMESPACE__ => array('dismiss_error' => array('key' => $_key)), '_wpnonce' => wp_create_nonce())));
						$_dismiss     = '<a style="'.esc_attr($_dismiss_css).'" href="'.esc_attr($_dismiss).'">'.__('dismiss &times;', $this->text_domain).'</a>';
					}
					echo apply_filters(__METHOD__.'__error', '<div class="error"><p>'.$_error.$_dismiss.'</p></div>', get_defined_vars());
				}
				unset($_key, $_error, $_dismiss_css, $_dismiss); // Housekeeping.
			}

			/**
			 * Enqueue an administrative error.
			 *
			 * @since 140605 Adding enqueue notice/error methods.
			 *
			 * @param string  $error HTML markup containing the error itself.
			 *
			 * @param string  $persistent_key Optional. A unique key which identifies a particular type of persistent error.
			 *    This defaults to an empty string. If this is passed, the error is persistent; i.e. it continues to be displayed until dismissed by the site owner.
			 *
			 * @param boolean $push_to_top Optional. Defaults to a `FALSE` value.
			 *    If `TRUE`, the error is pushed to the top of the stack; i.e. displayed above any others.
			 */
			public function enqueue_error($error, $persistent_key = '', $push_to_top = FALSE)
			{
				$error          = (string)$error;
				$persistent_key = (string)$persistent_key;

				$errors = get_option(__NAMESPACE__.'_errors');
				if(!is_array($errors)) $errors = array();

				if($persistent_key) // A persistent notice?
				{
					if(strpos($persistent_key, 'persistent-') !== 0)
						$persistent_key = 'persistent-'.$persistent_key;

					if($push_to_top) // Push this notice to the top?
						$errors = array($persistent_key => $error) + $errors;
					else $errors[$persistent_key] = $error;
				}
				else if($push_to_top) // Push to the top?
					array_unshift($errors, $error);

				else $errors[] = $error; // Default behavior.

				update_option(__NAMESPACE__.'_errors', $errors);
			}

			/**
			 * Extends WP-Cron schedules.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `cron_schedules` filter.
			 *
			 * @param array $schedules An array of the current schedules.
			 *
			 * @return array Revised array of WP-Cron schedules.
			 */
			public function extend_cron_schedules($schedules)
			{
				$schedules['every15m'] = array('interval' => 900, 'display' => __('Every 15 Minutes', $this->text_domain));

				return apply_filters(__METHOD__, $schedules, get_defined_vars());
			}

			/**
			 * Wipes out all cache files in the cache directory.
			 *
			 * @since 140422 First documented version.
			 *
			 * @param boolean $manually Defaults to a `FALSE` value.
			 *    Pass as TRUE if the wipe is done manually by the site owner.
			 *
			 * @param string  $also_wipe_dir Defaults to an empty string.
			 *    By default (i.e. when this is empty) we only wipe {@link $cache_sub_dir} files.
			 *
			 *    WARNING: If this is passed, EVERYTHING inside this directory is deleted recursively;
			 *       in addition to deleting all of the {@link $cache_sub_dir} files.
			 *
			 *    SECURITY: This directory MUST be located inside the `/wp-content/` directory.
			 *    Also, it MUST be a sub-directory of `/wp-content/`, NOT the directory itself.
			 *    Also, it cannot be: `mu-plugins`, `themes`, or `plugins`.
			 *
			 * @return integer Total files wiped by this routine (if any).
			 *
			 * @throws \exception If a wipe failure occurs.
			 */
			public function wipe_cache($manually = FALSE, $also_wipe_dir = '')
			{
				$counter = 0; // Initialize.

				$also_wipe_dir = trim((string)$also_wipe_dir);

				@set_time_limit(1800); // @TODO When disabled, display a warning.

				if(is_dir($cache_dir = $this->cache_dir()))
					$counter += $this->delete_all_files_dirs_in($cache_dir);

				if($also_wipe_dir && is_dir($also_wipe_dir)) // Also wipe another directory?
					// This is called w/ version-specific upgrades. That's the only use at this time.
					$counter += $this->delete_all_files_dirs_in($also_wipe_dir);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Clears cache files for the current host|blog.
			 *
			 * @since 140422 First documented version.
			 *
			 * @param boolean $manually Defaults to a `FALSE` value.
			 *    Pass as TRUE if the clearing is done manually by the site owner.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clearing failure occurs.
			 */
			public function clear_cache($manually = FALSE)
			{
				$counter = 0; // Initialize.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				@set_time_limit(1800); // @TODO When disabled, display a warning.

				$regex = $this->build_host_cache_path_regex('', '.+');
				$counter += $this->clear_files_from_host_cache_dir($regex);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Purges expired cache files for the current host|blog.
			 *
			 * @since 140422 First documented version.
			 *
			 * @param boolean $manually Defaults to a `FALSE` value.
			 *    Pass as TRUE if the purging is done manually by the site owner.
			 *
			 * @return integer Total files purged by this routine (if any).
			 *
			 * @attaches-to `'_cron_'.__NAMESPACE__.'_cleanup'` via CRON job.
			 *
			 * @throws \exception If a purge failure occurs.
			 */
			public function purge_cache($manually = FALSE)
			{
				$counter = 0; // Initialize.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				@set_time_limit(1800); // @TODO When disabled, display a warning.

				$regex = $this->build_host_cache_path_regex('', '.+');
				$counter += $this->purge_files_from_host_cache_dir($regex);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically wipes out all cache files in the cache directory.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return integer Total files wiped by this routine (if any).
			 *
			 * @note Unlike many of the other `auto_` methods, this one is NOT currently attached to any hooks.
			 *    This is called upon whenever QC options are saved and/or restored though.
			 */
			public function auto_wipe_cache()
			{
				$counter = 0; // Initialize.

				if(isset($this->cache[__FUNCTION__]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				$counter += $this->wipe_cache();

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
				{
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/wipe.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected significant changes. Found %1$s in the cache; auto-wiping.', $this->text_domain),
					                              esc_html($this->i18n_files($counter))));
				}
				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears all cache files for the current blog.
			 *
			 * @attaches-to `switch_theme` hook.
			 *
			 * @attaches-to `wp_create_nav_menu` hook.
			 * @attaches-to `wp_update_nav_menu` hook.
			 * @attaches-to `wp_delete_nav_menu` hook.
			 *
			 * @attaches-to `create_term` hook.
			 * @attaches-to `edit_terms` hook.
			 * @attaches-to `delete_term` hook.
			 *
			 * @attaches-to `add_link` hook.
			 * @attaches-to `edit_link` hook.
			 * @attaches-to `delete_link` hook.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @note This is also called upon during plugin activation.
			 */
			public function auto_clear_cache()
			{
				$counter = 0; // Initialize.

				if(isset($this->cache[__FUNCTION__]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				$counter += $this->clear_cache();

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected important site changes. Found %1$s in the cache for this site; auto-clearing.', $this->text_domain),
					                              esc_html($this->i18n_files($counter))));

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for a particular post.
			 *
			 * @attaches-to `save_post` hook.
			 * @attaches-to `delete_post` hook.
			 * @attaches-to `clean_post_cache` hook.
			 *
			 * @since 140422 First documented version.
			 *
			 * @param integer $post_id A WordPress post ID.
			 *
			 * @param bool    $force Defaults to a `FALSE` value.
			 *    Pass as TRUE if clearing should be done for `draft`, `pending`,
			 *    `future`, or `trash` post statuses.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note This is also called upon by other routines which listen for
			 *    events that are indirectly associated with a post ID.
			 *
			 * @see auto_clear_comment_post_cache()
			 * @see auto_clear_post_cache_transition()
			 */
			public function auto_clear_post_cache($post_id, $force = FALSE)
			{
				$counter = 0; // Initialize.

				if(!($post_id = (integer)$post_id))
					return $counter; // Nothing to do.

				if(isset($this->cache[__FUNCTION__][$post_id][(integer)$force]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__][$post_id][(integer)$force] = -1;

				if(isset(static::$static['___allow_auto_clear_post_cache']) && static::$static['___allow_auto_clear_post_cache'] === FALSE)
				{
					static::$static['___allow_auto_clear_post_cache'] = TRUE; // Reset state.
					return $counter; // Nothing to do.
				}
				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
					return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				if(!($permalink = get_permalink($post_id)))
					return $counter; // Nothing we can do.

				if(!($post_status = get_post_status($post_id)))
					return $counter; // Nothing to do.

				if($post_status === 'auto-draft')
					return $counter; // Nothing to do.

				if($post_status === 'draft' && !$force)
					return $counter; // Nothing to do.

				if($post_status === 'pending' && !$force)
					return $counter; // Nothing to do.

				if($post_status === 'future' && !$force)
					return $counter; // Nothing to do.

				if($post_status === 'trash' && !$force)
					return $counter; // Nothing to do.

				if(($type = get_post_type($post_id)) && ($type = get_post_type_object($type)) && !empty($type->labels->singular_name))
					$type_singular_name = $type->labels->singular_name; // Singular name for the post type.
				else $type_singular_name = __('Post', $this->text_domain); // Default value.

				$regex = $this->build_host_cache_path_regex($permalink);
				$counter += $this->clear_files_from_host_cache_dir($regex);

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
				{
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache for %2$s ID: <code>%3$s</code>; auto-clearing.', $this->text_domain),
					                              esc_html($this->i18n_files($counter)), esc_html($type_singular_name), esc_html($post_id)));
				}
				$counter += $this->auto_clear_xml_feeds_cache('blog');
				$counter += $this->auto_clear_xml_feeds_cache('post-terms', $post_id);
				$counter += $this->auto_clear_xml_feeds_cache('post-authors', $post_id);

				$counter += $this->auto_clear_xml_sitemaps_cache();
				$counter += $this->auto_clear_home_page_cache();
				$counter += $this->auto_clear_posts_page_cache();
				$counter += $this->auto_clear_post_terms_cache($post_id, $force);

				// Also clear a possible custom post type archive view.
				$counter += $this->auto_clear_custom_post_type_archive_cache($post_id);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for a particular post when transitioning
			 *    from `publish` or `private` post status to `draft`, `future`, `private`, or `trash`.
			 *
			 * @attaches-to `transition_post_status` hook.
			 *
			 * @since 140605 First documented version.
			 *
			 * @param string   $new_status New post status.
			 * @param string   $old_status Old post status.
			 * @param \WP_Post $post Post object.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note This is also called upon by other routines which listen for
			 *    events that are indirectly associated with a post ID.
			 *
			 * @see auto_clear_post_cache()
			 */
			public function auto_clear_post_cache_transition($new_status, $old_status, \WP_Post $post)
			{
				$new_status = (string)$new_status;
				$old_status = (string)$old_status;

				$counter = 0; // Initialize.

				if(isset($this->cache[__FUNCTION__][$new_status][$old_status][$post->ID]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__][$new_status][$old_status][$post->ID] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if($old_status !== 'publish' && $old_status !== 'private')
					return $counter; // Nothing to do. We MUST be transitioning FROM one of these statuses.

				if($new_status === 'draft' || $new_status === 'future' || $new_status === 'private' || $new_status === 'trash')
					$counter = $this->auto_clear_post_cache($post->ID, TRUE);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files related to XML feeds.
			 *
			 * @since 140829 Working to improve compatibility with feeds.
			 *
			 * @param string  $type Type of feed(s) to auto-clear.
			 * @param integer $post_id A Post ID (when applicable).
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note Unlike many of the other `auto_` methods, this one is NOT currently
			 *    attached to any hooks. However, it is called upon by other routines attached to hooks.
			 */
			public function auto_clear_xml_feeds_cache($type, $post_id = 0)
			{
				$counter = 0; // Initialize.

				if(!($type = (string)$type))
					return $counter; // Nothing we can do.
				$post_id = (integer)$post_id; // Force integer.

				if(isset($this->cache[__FUNCTION__][$type][$post_id]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__][$type][$post_id] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!$this->options['feeds_enable'])
					return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				$variations = $variation_regex_frags = array(); // Initialize.
				require_once dirname(__FILE__).'/includes/utils-feed.php';
				$utils = new utils_feed(); // Feed utilities.

				switch($type) // Handle clearing based on the `$type`.
				{
					case 'blog': // The blog feed; i.e. `/feed/` on most WP installs.

						$variations = array_merge($variations, $utils->feed_link_variations());
						break; // Break switch handler.

					case 'blog-comments': // The blog comments feed; i.e. `/comments/feed/` on most WP installs.

						$variations = array_merge($variations, $utils->feed_link_variations('comments_'));
						break; // Break switch handler.

					case 'post-comments': // Feeds related to comments that a post has.

						if(!$post_id) break; // Nothing to do.
						if(!($post = get_post($post_id))) break;
						$variations = array_merge($variations, $utils->post_comments_feed_link_variations($post));
						break; // Break switch handler.

					case 'post-authors': // Feeds related to authors that a post has.

						if(!$post_id) break; // Nothing to do.
						if(!($post = get_post($post_id))) break;
						$variations = array_merge($variations, $utils->post_author_feed_link_variations($post));
						break; // Break switch handler.

					case 'post-terms': // Feeds related to terms that a post has.

						if(!$post_id) break; // Nothing to do.
						if(!($post = get_post($post_id))) break;
						$variations = array_merge($variations, $utils->post_term_feed_link_variations($post, TRUE));
						break; // Break switch handler.

					case 'custom-post-type': // Feeds related to a custom post type archive view.

						if(!$post_id) break; // Nothing to do.
						if(!($post = get_post($post_id))) break;
						$variations = array_merge($variations, $utils->post_type_archive_link_variations($post));
						break; // Break switch handler.

					// @TODO Possibly consider search-related feeds in the future.
					//    See: <http://codex.wordpress.org/WordPress_Feeds#Categories_and_Tags>
				}
				$variation_regex_frags = $utils->convert_variations_to_host_cache_path_regex_frags($variations);

				if(!$variation_regex_frags // Have regex pattern variations?
				   || !($variation_regex_frags = array_unique($variation_regex_frags))
				) return $counter; // Nothing to do here.

				$in_sets_of = apply_filters(__METHOD__.'__in_sets_of', 10, get_defined_vars());
				for($_i = 0; $_i < count($variation_regex_frags); $_i = $_i + $in_sets_of)
				{
					$_variation_regex_frags = array_slice($variation_regex_frags, $_i, $in_sets_of);
					$_regex                 = '/^\/(?:'.implode('|', $_variation_regex_frags).')\./i';
					$counter += $this->clear_files_from_host_cache_dir($_regex);
				}
				unset($_i, $_variation_regex_frags, $_regex); // Housekeeping.

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
				{
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache, for XML feeds of type: <code>%2$s</code>; auto-clearing.', $this->text_domain),
					                              esc_html($this->i18n_files($counter)), esc_html($type)));
				}
				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files related to XML sitemaps.
			 *
			 * @since 140725 Working to improve compatibility with sitemaps.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note Unlike many of the other `auto_` methods, this one is NOT currently
			 *    attached to any hooks. However, it is called upon by {@link auto_clear_post_cache()}.
			 *
			 * @see auto_clear_post_cache()
			 */
			public function auto_clear_xml_sitemaps_cache()
			{
				$counter = 0; // Initialize.

				if(isset($this->cache[__FUNCTION__]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				if(!($regex_frags = $this->build_host_cache_path_regex_frags_from_wc_uris('/sitemap*.xml', '')))
					return $counter; // There are no patterns to look for.

				$regex = $this->build_host_cache_path_regex('', '\/'.$regex_frags.'\.');
				$counter += $this->clear_files_from_host_cache_dir($regex);

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
				{
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache for XML sitemaps; auto-clearing.', $this->text_domain),
					                              esc_html($this->i18n_files($counter))));
				}
				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for the home page.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note Unlike many of the other `auto_` methods, this one is NOT currently
			 *    attached to any hooks. However, it is called upon by {@link auto_clear_post_cache()}.
			 *
			 * @see auto_clear_post_cache()
			 */
			public function auto_clear_home_page_cache()
			{
				$counter = 0; // Initialize.

				if(isset($this->cache[__FUNCTION__]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!$this->options['cache_clear_home_page_enable'])
					return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				$regex = $this->build_host_cache_path_regex(home_url('/'));
				$counter += $this->clear_files_from_host_cache_dir($regex);

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
				{
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache for the designated "Home Page"; auto-clearing.', $this->text_domain),
					                              esc_html($this->i18n_files($counter))));
				}
				$counter += $this->auto_clear_xml_feeds_cache('blog');

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for the posts page.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note Unlike many of the other `auto_` methods, this one is NOT currently
			 *    attached to any hooks. However, it is called upon by {@link auto_clear_post_cache()}.
			 *
			 * @see auto_clear_post_cache()
			 */
			public function auto_clear_posts_page_cache()
			{
				$counter = 0; // Initialize.

				if(isset($this->cache[__FUNCTION__]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!$this->options['cache_clear_posts_page_enable'])
					return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				$show_on_front  = get_option('show_on_front');
				$page_for_posts = get_option('page_for_posts');

				if(!in_array($show_on_front, array('posts', 'page'), TRUE))
					return $counter; // Nothing we can do in this case.

				if($show_on_front === 'page' && !$page_for_posts)
					return $counter; // Nothing we can do.

				if($show_on_front === 'posts') $posts_page = home_url('/');
				else if($show_on_front === 'page') $posts_page = get_permalink($page_for_posts);
				if(empty($posts_page)) return $counter; // Nothing we can do.

				$regex = $this->build_host_cache_path_regex($posts_page);
				$counter += $this->clear_files_from_host_cache_dir($regex);

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
				{
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache for the designated "Posts Page"; auto-clearing.', $this->text_domain),
					                              esc_html($this->i18n_files($counter))));
				}
				$counter += $this->auto_clear_xml_feeds_cache('blog');

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for a custom post type archive view.
			 *
			 * @since 140918 First documented version.
			 *
			 * @param integer $post_id A WordPress post ID.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note Unlike many of the other `auto_` methods, this one is NOT currently
			 *    attached to any hooks. However, it is called upon by {@link auto_clear_post_cache()}.
			 *
			 * @see auto_clear_post_cache()
			 */
			public function auto_clear_custom_post_type_archive_cache($post_id)
			{
				$counter = 0; // Initialize.

				if(!($post_id = (integer)$post_id))
					return $counter; // Nothing to do.

				if(isset($this->cache[__FUNCTION__][$post_id]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__][$post_id] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				if(!($post_type = get_post_type($post_id)))
					return $counter; // Nothing to do.

				if(!($all_custom_post_types = get_post_types(array('_builtin' => FALSE))))
					return $counter; // No custom post types.

				if(!in_array($post_type, array_keys($all_custom_post_types), TRUE))
					return $counter; // This is NOT a custom post type.

				if(!($custom_post_type = get_post_type_object($post_type)))
					return $counter; // Unable to retrieve post type.

				if(empty($custom_post_type->labels->name)
				   || !($custom_post_type_name = $custom_post_type->labels->name)
				) $custom_post_type_name = __('Untitled', $this->text_domain);

				if(!($custom_post_type_archive_link = get_post_type_archive_link($post_type)))
					return $counter; // Nothing to do; no link to work from in this case.

				$regex = $this->build_host_cache_path_regex($custom_post_type_archive_link);
				$counter += $this->clear_files_from_host_cache_dir($regex);

				if($counter && is_admin() /* && $this->options['change_notifications_enable'] */)
				{
					$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
					                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache for Custom Post Type: <code>%2$s</code>; auto-clearing.', $this->text_domain),
					                              esc_html($this->i18n_files($counter)), esc_html($custom_post_type_name)));
				}
				$counter += $this->auto_clear_xml_feeds_cache('custom-post-type', $post_id);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for the author page(s).
			 *
			 * @attaches-to `post_updated` hook.
			 *
			 * @since 140605 First documented version.
			 *
			 * @param integer  $post_id A WordPress post ID.
			 * @param \WP_Post $post_after WP_Post object following the update.
			 * @param \WP_Post $post_before WP_Post object before the update.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note If the author for the post is being changed, both the previous author
			 *       and current author pages are cleared, if the post status is applicable.
			 */
			public function auto_clear_author_page_cache($post_id, \WP_Post $post_after, \WP_Post $post_before)
			{
				$counter          = 0; // Initialize.
				$enqueued_notices = 0; // Initialize.
				$authors          = array(); // Initialize.
				$authors_to_clear = array(); // Initialize.

				if(!($post_id = (integer)$post_id))
					return $counter; // Nothing to do.

				if(isset($this->cache[__FUNCTION__][$post_id][$post_after->ID][$post_before->ID]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__][$post_id][$post_after->ID][$post_before->ID] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!$this->options['cache_clear_author_page_enable'])
					return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.
				/*
				 * If we're changing the post author AND
				 *    the previous post status was either 'published' or 'private'
				 * then clear the author page for both authors.
				 *
				 * Else if the old post status was 'published' or 'private' OR
				 *    the new post status is 'published' or 'private'
				 * then clear the author page for the current author.
				 *
				 * Else return the counter; post status does not warrant clearing author page cache.
				 */
				if($post_after->post_author !== $post_before->post_author &&
				   ($post_before->post_status === 'publish' || $post_before->post_status === 'private')
				) // Clear both authors in this case.
				{
					$authors[] = (integer)$post_before->post_author;
					$authors[] = (integer)$post_after->post_author;
				}
				else if(($post_before->post_status === 'publish' || $post_before->post_status === 'private') ||
				        ($post_after->post_status === 'publish' || $post_after->post_status === 'private')
				)
					$authors[] = (integer)$post_after->post_author;

				if(!$authors) // Have no authors to clear?
					return $counter; // Nothing to do.

				foreach($authors as $_author_id) // Get author posts URL and display name.
				{
					$authors_to_clear[$_author_id]['posts_url']    = get_author_posts_url($_author_id);
					$authors_to_clear[$_author_id]['display_name'] = get_the_author_meta('display_name', $_author_id);
				}
				unset($_author_id); // Housekeeping.

				foreach($authors_to_clear as $_author)
				{
					$_author_regex   = $this->build_host_cache_path_regex($_author['posts_url']);
					$_author_counter = $this->clear_files_from_host_cache_dir($_author_regex);
					$counter += $_author_counter; // Add to overall counter.

					if($_author_counter && $enqueued_notices < 100 && is_admin() /* && $this->options['change_notifications_enable'] */)
					{
						$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
						                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache for Author Page: <code>%2$s</code>; auto-clearing.', $this->text_domain),
						                              esc_html($this->i18n_files($_author_counter)), esc_html($_author['display_name'])));
						$enqueued_notices++; // Increment enqueued notices counter.
					}
				}
				unset($_author, $_author_regex, $_author_counter); // Housekeeping.

				$counter += $this->auto_clear_xml_feeds_cache('blog');
				$counter += $this->auto_clear_xml_feeds_cache('post-authors', $post_id);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for terms associated with a post.
			 *
			 * @attaches-to `added_term_relationship` hook.
			 * @attaches-to `delete_term_relationships` hook.
			 *
			 * @since 140605 First documented version.
			 *
			 * @param integer $post_id A WordPress post ID.
			 *
			 * @param bool    $force Defaults to a `FALSE` value.
			 *    Pass as TRUE if clearing should be done for `draft`, `pending`,
			 *    or `future` post statuses.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note In addition to the hooks this is attached to, it is also
			 *    called upon by {@link auto_clear_post_cache()}.
			 *
			 * @see auto_clear_post_cache()
			 */
			public function auto_clear_post_terms_cache($post_id, $force = FALSE)
			{
				$counter          = 0; // Initialize.
				$enqueued_notices = 0; // Initialize.

				if(!($post_id = (integer)$post_id))
					return $counter; // Nothing to do.

				if(isset($this->cache[__FUNCTION__][$post_id][(integer)$force]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__][$post_id][(integer)$force] = -1;

				if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
					return $counter; // Nothing to do.

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!$this->options['cache_clear_term_category_enable'] &&
				   !$this->options['cache_clear_term_post_tag_enable'] &&
				   !$this->options['cache_clear_term_other_enable']
				) return $counter; // Nothing to do.

				if(!is_dir($cache_dir = $this->cache_dir()))
					return $counter; // Nothing to do.

				$post_status = get_post_status($post_id); // Cache this.

				if($post_status === 'draft' && isset($GLOBALS['pagenow'], $_POST['publish'])
				   && is_admin() && $GLOBALS['pagenow'] === 'post.php' && current_user_can('publish_posts')
				   && strpos(wp_get_referer(), '/post-new.php') !== FALSE
				) $post_status = 'publish'; // A new post being published now.

				if($post_status === 'auto-draft')
					return $counter; // Nothing to do.

				if($post_status === 'draft' && !$force)
					return $counter; // Nothing to do.

				if($post_status === 'pending' && !$force)
					return $counter; // Nothing to do.

				if($post_status === 'future' && !$force)
					return $counter; // Nothing to do.
				/*
				 * Build an array of available taxonomies for this post (as taxonomy objects).
				 */
				$taxonomies = get_object_taxonomies(get_post($post_id), 'objects');

				if(!is_array($taxonomies)) // No taxonomies?
					return $counter; // Nothing to do.
				/*
				 * Build an array of terms associated with this post for each taxonomy.
				 * Also save taxonomy label information for Dashboard messaging later.
				 */
				$terms           = array();
				$taxonomy_labels = array();

				foreach($taxonomies as $_taxonomy)
				{
					if( // Check if this is a taxonomy/term that we should clear.
						($_taxonomy->name === 'category' && !$this->options['cache_clear_term_category_enable'])
						|| ($_taxonomy->name === 'post_tag' && !$this->options['cache_clear_term_post_tag_enable'])
						|| ($_taxonomy->name !== 'category' && $_taxonomy->name !== 'post_tag' && !$this->options['cache_clear_term_other_enable'])
					) continue; // Continue; nothing to do for this taxonomy.

					if(is_array($_terms = wp_get_post_terms($post_id, $_taxonomy->name)))
					{
						$terms = array_merge($terms, $_terms);

						// Improve Dashboard messaging by getting the Taxonomy label (e.g., "Tag" instead of "post_tag")
						// If we don't have a Singular Name for this taxonomy, use the taxonomy name itself
						if(empty($_taxonomy->labels->singular_name) || $_taxonomy->labels->singular_name === '')
							$taxonomy_labels[$_taxonomy->name] = $_taxonomy->name;
						else
							$taxonomy_labels[$_taxonomy->name] = $_taxonomy->labels->singular_name;
					}
				}
				unset($_taxonomy, $_terms);

				if(empty($terms)) // No taxonomy terms?
					return $counter; // Nothing to do.
				/*
				 * Build an array of terms with term names,
				 * permalinks, and associated taxonomy labels.
				 */
				$terms_to_clear = array();
				$_i             = 0;

				foreach($terms as $_term)
				{
					if(($_link = get_term_link($_term)))
					{
						$terms_to_clear[$_i]['permalink'] = $_link; // E.g., "http://jason.websharks-inc.net/category/uncategorized/"
						$terms_to_clear[$_i]['term_name'] = $_term->name; // E.g., "Uncategorized"
						if(!empty($taxonomy_labels[$_term->taxonomy])) // E.g., "Tag" or "Category"
							$terms_to_clear[$_i]['taxonomy_label'] = $taxonomy_labels[$_term->taxonomy];
						else
							$terms_to_clear[$_i]['taxonomy_label'] = $_term->taxonomy; // e.g., "post_tag" or "category"
					}
					$_i++; // Array index counter.
				}
				unset($_term, $_link, $_i);

				if(empty($terms_to_clear))
					return $counter; // Nothing to do.

				foreach($terms_to_clear as $_term)
				{
					$_term_regex   = $this->build_host_cache_path_regex($_term['permalink']);
					$_term_counter = $this->clear_files_from_host_cache_dir($_term_regex);
					$counter += $_term_counter; // Add to overall counter.

					if($_term_counter && $enqueued_notices < 100 && is_admin() /* && $this->options['change_notifications_enable'] */)
					{
						$this->enqueue_notice('<img src="'.esc_attr($this->url('/client-s/images/clear.png')).'" style="float:left; margin:0 10px 0 0; border:0;" />'.
						                      sprintf(__('<strong>Quick Cache:</strong> detected changes. Found %1$s in the cache for %2$s: <code>%3$s</code>; auto-clearing.', $this->text_domain),
						                              esc_html($this->i18n_files($_term_counter)), esc_html($_term['taxonomy_label']), esc_html($_term['term_name'])));
						$enqueued_notices++; // Increment enqueued notices counter.
					}
				}
				unset($_term, $_term_regex, $_term_counter); // Housekeeping.

				$counter += $this->auto_clear_xml_feeds_cache('post-terms', $post_id);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for a post associated with a particular comment.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `trackback_post` hook.
			 * @attaches-to `pingback_post` hook.
			 * @attaches-to `comment_post` hook.
			 *
			 * @param integer $comment_id A WordPress comment ID.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @see auto_clear_post_cache()
			 */
			public function auto_clear_comment_post_cache($comment_id)
			{
				$counter = 0; // Initialize.

				if(!($comment_id = (integer)$comment_id))
					return $counter; // Nothing to do.

				if(isset($this->cache[__FUNCTION__][$comment_id]))
					return $counter; // Already did this.
				$this->cache[__FUNCTION__][$comment_id] = -1;

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!is_object($comment = get_comment($comment_id)))
					return $counter; // Nothing we can do.

				if(empty($comment->comment_post_ID))
					return $counter; // Nothing we can do.

				if($comment->comment_approved === 'spam' || $comment->comment_approved === '0')
					// Don't allow next `auto_clear_post_cache()` call to clear post cache.
					// Also, don't allow spam to clear cache.
				{
					static::$static['___allow_auto_clear_post_cache'] = FALSE;
					return $counter; // Nothing to do here.
				}
				$counter += $this->auto_clear_xml_feeds_cache('blog-comments');
				$counter += $this->auto_clear_xml_feeds_cache('post-comments', $comment->comment_post_ID);
				$counter += $this->auto_clear_post_cache($comment->comment_post_ID);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears cache files for a post associated with a particular comment.
			 *
			 * @since 140711 First documented version.
			 *
			 * @attaches-to `transition_comment_status` hook.
			 *
			 * @param string   $new_status New comment status.
			 * @param string   $old_status Old comment status.
			 * @param \WP_Post $comment Comment object.
			 *
			 * @return integer Total files cleared by this routine (if any).
			 *
			 * @throws \exception If a clear failure occurs.
			 *
			 * @note This is also called upon by other routines which listen for
			 *    events that are indirectly associated with a comment ID.
			 *
			 * @see auto_clear_comment_post_cache()
			 */
			public function auto_clear_comment_transition($new_status, $old_status, $comment)
			{
				$counter = 0; // Initialize.

				if(!$this->options['enable'])
					return $counter; // Nothing to do.

				if(!is_object($comment))
					return $counter; // Nothing we can do.

				if(empty($comment->comment_post_ID))
					return $counter; // Nothing we can do.

				if(!($old_status === 'approved' || ($old_status === 'unapproved' && $new_status === 'approved')))
					// If excluded here, don't allow next `auto_clear_post_cache()` call to clear post cache.
				{
					static::$static['___allow_auto_clear_post_cache'] = FALSE;
					return $counter; // Nothing to do here.
				}
				$counter += $this->auto_clear_xml_feeds_cache('blog-comments');
				$counter += $this->auto_clear_xml_feeds_cache('post-comments', $comment->comment_post_ID);
				$counter += $this->auto_clear_post_cache($comment->comment_post_ID);

				return apply_filters(__METHOD__, $counter, get_defined_vars());
			}

			/**
			 * Automatically clears all cache files for current blog under various conditions;
			 *    used to check for conditions that don't have a hook that we can attach to.
			 *
			 * @since 140922 First documented version.
			 *
			 * @attaches-to `admin_init` hook.
			 *
			 * @see auto_clear_cache()
			 */
			public function maybe_auto_clear_cache()
			{
				$_pagenow = $GLOBALS['pagenow'];
				if(isset($this->cache[__FUNCTION__][$_pagenow]))
					return; // Already did this.
				$this->cache[__FUNCTION__][$_pagenow] = -1;

				// If Dashboard → Settings → General options are updated
				if($GLOBALS['pagenow'] === 'options-general.php' && !empty($_REQUEST['settings-updated']))
					$this->auto_clear_cache();

				// If Dashboard → Settings → Reading options are updated
				if($GLOBALS['pagenow'] === 'options-reading.php' && !empty($_REQUEST['settings-updated']))
					$this->auto_clear_cache();

				// If Dashboard → Settings → Discussion options are updated
				if($GLOBALS['pagenow'] === 'options-discussion.php' && !empty($_REQUEST['settings-updated']))
					$this->auto_clear_cache();

				// If Dashboard → Settings → Permalink options are updated
				if($GLOBALS['pagenow'] === 'options-permalink.php' && !empty($_REQUEST['settings-updated']))
					$this->auto_clear_cache();
			}

			/**
			 * Automatically clears all cache files for current blog when JetPack Custom CSS is saved.
			 *
			 * @since 140919 First documented version.
			 *
			 * @attaches-to `safecss_save_pre` hook.
			 *
			 * @param array $args Args passed in by hook.
			 *
			 * @see auto_clear_cache()
			 */
			public function jetpack_custom_css($args)
			{
				if(class_exists('\\Jetpack') && empty($args['is_preview']))
					$this->auto_clear_cache();
			}

			/**
			 * Automatically clears all cache files for current blog when WordPress core, or an active component, is upgraded.
			 *
			 * @since 141001 Clearing the cache on WP upgrades.
			 *
			 * @attaches-to `upgrader_process_complete` hook.
			 *
			 * @param \WP_Upgrader $upgrader_instance An instance of \WP_Upgrader.
			 *    Or, any class that extends \WP_Upgrader.
			 *
			 * @param array        $data Array of bulk item update data.
			 *
			 *    This array may include one or more of the following keys:
			 *
			 *       - `string` `$action` Type of action. Default 'update'.
			 *       - `string` `$type` Type of update process; e.g. 'plugin', 'theme', 'core'.
			 *       - `boolean` `$bulk` Whether the update process is a bulk update. Default true.
			 *       - `array` `$packages` Array of plugin, theme, or core packages to update.
			 *
			 * @see auto_clear_cache()
			 */
			public function upgrader_process_complete(\WP_Upgrader $upgrader_instance, array $data)
			{
				switch(!empty($data['type']) ? $data['type'] : '')
				{
					case 'plugin': // Plugin upgrade.

						/** @var $skin \Plugin_Upgrader_Skin * */
						$skin                    = $upgrader_instance->skin;
						$multi_plugin_update     = $single_plugin_update = FALSE;
						$upgrading_active_plugin = FALSE; // Initialize.

						if(!empty($data['bulk']) && !empty($data['plugins']) && is_array($data['plugins']))
							$multi_plugin_update = TRUE;

						else if(!empty($data['plugin']) && is_string($data['plugin']))
							$single_plugin_update = TRUE;

						if($multi_plugin_update)
						{
							foreach($data['plugins'] as $_plugin)
								if($_plugin && is_string($_plugin) && is_plugin_active($_plugin))
								{
									$upgrading_active_plugin = TRUE;
									break; // Got what we need here.
								}
							unset($_plugin); // Housekeeping.
						}
						else if($single_plugin_update && $skin->plugin_active == TRUE)
							$upgrading_active_plugin = TRUE;

						if($upgrading_active_plugin)
							$this->auto_clear_cache(); // Yes, clear the cache.

						break; // Break switch.

					case 'theme': // Theme upgrade.

						$current_active_theme          = wp_get_theme();
						$current_active_theme_parent   = $current_active_theme->parent();
						$multi_theme_update            = $single_theme_update = FALSE;
						$upgrading_active_parent_theme = $upgrading_active_theme = FALSE;

						if(!empty($data['bulk']) && !empty($data['themes']) && is_array($data['themes']))
							$multi_theme_update = TRUE;

						else if(!empty($data['theme']) && is_string($data['theme']))
							$single_theme_update = TRUE;

						if($multi_theme_update)
						{
							foreach($data['themes'] as $_theme)
							{
								if(!$_theme || !is_string($_theme) || !($_theme_obj = wp_get_theme($_theme)))
									continue; // Unable to acquire theme object instance.

								if($current_active_theme_parent && $current_active_theme_parent->get_stylesheet() === $_theme_obj->get_stylesheet())
								{
									$upgrading_active_parent_theme = TRUE;
									break; // Got what we needed here.
								}
								else if($current_active_theme->get_stylesheet() === $_theme_obj->get_stylesheet())
								{
									$upgrading_active_theme = TRUE;
									break; // Got what we needed here.
								}
							}
							unset($_theme, $_theme_obj); // Housekeeping.
						}
						else if($single_theme_update && ($_theme_obj = wp_get_theme($data['theme'])))
						{
							if($current_active_theme_parent && $current_active_theme_parent->get_stylesheet() === $_theme_obj->get_stylesheet())
								$upgrading_active_parent_theme = TRUE;

							else if($current_active_theme->get_stylesheet() === $_theme_obj->get_stylesheet())
								$upgrading_active_theme = TRUE;
						}
						unset($_theme_obj); // Housekeeping.

						if($upgrading_active_theme || $upgrading_active_parent_theme)
							$this->auto_clear_cache(); // Yes, clear the cache.

						break; // Break switch.

					case 'core': // Core upgrade.
					default: // Or any other sort of upgrade.

						$this->auto_clear_cache(); // Yes, clear the cache.

						break; // Break switch.
				}
			}

			/**
			 * This constructs an absolute server directory path (no trailing slashes);
			 *    which is always nested into {@link \WP_CONTENT_DIR} and the configured `base_dir` option value.
			 *
			 * @since 140605 Moving to a base directory structure.
			 *
			 * @param string $rel_dir_file A sub-directory or file; relative location please.
			 *
			 * @return string The full absolute server path to `$rel_dir_file`.
			 *
			 * @throws \exception If `base_dir` is empty when this method is called upon;
			 *    i.e. if you attempt to call upon this method before {@link setup()} runs.
			 */
			public function wp_content_base_dir_to($rel_dir_file)
			{
				$rel_dir_file = trim((string)$rel_dir_file, '\\/'." \t\n\r\0\x0B");

				if(empty($this->options) || !is_array($this->options) || empty($this->options['base_dir']))
					throw new \exception(__('Doing it wrong! Missing `base_dir` option value. MUST call this method after `setup()`.', $this->text_domain));

				$wp_content_base_dir_to = WP_CONTENT_DIR.'/'.$this->options['base_dir'];

				if(isset($rel_dir_file[0])) // Do we have this also?
					$wp_content_base_dir_to .= '/'.$rel_dir_file;

				return apply_filters(__METHOD__, $wp_content_base_dir_to, get_defined_vars());
			}

			/**
			 * This constructs a relative/base directory path (no leading/trailing slashes).
			 *    Always relative to {@link \WP_CONTENT_DIR}. Depends on the configured `base_dir` option value.
			 *
			 * @since 140605 Moving to a base directory structure.
			 *
			 * @param string $rel_dir_file A sub-directory or file; relative location please.
			 *
			 * @return string The relative/base directory path to `$rel_dir_file`.
			 *
			 * @throws \exception If `base_dir` is empty when this method is called upon;
			 *    i.e. if you attempt to call upon this method before {@link setup()} runs.
			 */
			public function base_path_to($rel_dir_file)
			{
				$rel_dir_file = trim((string)$rel_dir_file, '\\/'." \t\n\r\0\x0B");

				if(empty($this->options) || !is_array($this->options) || empty($this->options['base_dir']))
					throw new \exception(__('Doing it wrong! Missing `base_dir` option value. MUST call this method after `setup()`.', $this->text_domain));

				$base_path_to = $this->options['base_dir'];

				if(isset($rel_dir_file[0])) // Do we have this also?
					$base_path_to .= '/'.$rel_dir_file;

				return apply_filters(__METHOD__, $base_path_to, get_defined_vars());
			}

			/**
			 * Adds `define('WP_CACHE', TRUE);` to the `/wp-config.php` file.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return string The new contents of the updated `/wp-config.php` file;
			 *    else an empty string if unable to add the `WP_CACHE` constant.
			 */
			public function add_wp_cache_to_wp_config()
			{
				if(!$this->options['enable'])
					return ''; // Nothing to do.

				if(!($wp_config_file = $this->find_wp_config_file()))
					return ''; // Unable to find `/wp-config.php`.

				if(!is_readable($wp_config_file)) return ''; // Not possible.
				if(!($wp_config_file_contents = file_get_contents($wp_config_file)))
					return ''; // Failure; could not read file.

				if(preg_match('/define\s*\(\s*([\'"])WP_CACHE\\1\s*,\s*(?:\-?[1-9][0-9\.]*|TRUE|([\'"])(?:[^0\'"]|[^\'"]{2,})\\2)\s*\)\s*;/i', $wp_config_file_contents))
					return $wp_config_file_contents; // It's already in there; no need to modify this file.

				if(!($wp_config_file_contents = $this->remove_wp_cache_from_wp_config()))
					return ''; // Unable to remove previous value.

				if(!($wp_config_file_contents = preg_replace('/^\s*(\<\?php|\<\?)\s+/i', '${1}'."\n"."define('WP_CACHE', TRUE);"."\n", $wp_config_file_contents, 1)))
					return ''; // Failure; something went terribly wrong here.

				if(strpos($wp_config_file_contents, "define('WP_CACHE', TRUE);") === FALSE)
					return ''; // Failure; unable to add; unexpected PHP code.

				if(defined('DISALLOW_FILE_MODS') && DISALLOW_FILE_MODS)
					return ''; // We may NOT edit any files.

				if(!is_writable($wp_config_file)) return ''; // Not possible.
				if(!file_put_contents($wp_config_file, $wp_config_file_contents))
					return ''; // Failure; could not write changes.

				return apply_filters(__METHOD__, $wp_config_file_contents, get_defined_vars());
			}

			/**
			 * Removes `define('WP_CACHE', TRUE);` from the `/wp-config.php` file.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return string The new contents of the updated `/wp-config.php` file;
			 *    else an empty string if unable to remove the `WP_CACHE` constant.
			 */
			public function remove_wp_cache_from_wp_config()
			{
				if(!($wp_config_file = $this->find_wp_config_file()))
					return ''; // Unable to find `/wp-config.php`.

				if(!is_readable($wp_config_file)) return ''; // Not possible.
				if(!($wp_config_file_contents = file_get_contents($wp_config_file)))
					return ''; // Failure; could not read file.

				if(!preg_match('/([\'"])WP_CACHE\\1/i', $wp_config_file_contents))
					return $wp_config_file_contents; // Already gone.

				if(preg_match('/define\s*\(\s*([\'"])WP_CACHE\\1\s*,\s*(?:0|FALSE|NULL|([\'"])0?\\2)\s*\)\s*;/i', $wp_config_file_contents))
					return $wp_config_file_contents; // It's already disabled; no need to modify this file.

				if(!($wp_config_file_contents = preg_replace('/define\s*\(\s*([\'"])WP_CACHE\\1\s*,\s*(?:\-?[0-9\.]+|TRUE|FALSE|NULL|([\'"])[^\'"]*\\2)\s*\)\s*;/i', '', $wp_config_file_contents)))
					return ''; // Failure; something went terribly wrong here.

				if(preg_match('/([\'"])WP_CACHE\\1/i', $wp_config_file_contents))
					return ''; // Failure; perhaps the `/wp-config.php` file contains syntax we cannot remove safely.

				if(defined('DISALLOW_FILE_MODS') && DISALLOW_FILE_MODS)
					return ''; // We may NOT edit any files.

				if(!is_writable($wp_config_file)) return ''; // Not possible.
				if(!file_put_contents($wp_config_file, $wp_config_file_contents))
					return ''; // Failure; could not write changes.

				return apply_filters(__METHOD__, $wp_config_file_contents, get_defined_vars());
			}

			/**
			 * Checks to make sure the `qc-advanced-cache` file still exists;
			 *    and if it doesn't, the `advanced-cache.php` is regenerated automatically.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `init` hook.
			 *
			 * @note This runs so that remote deployments which completely wipe out an
			 *    existing set of website files (like the AWS Elastic Beanstalk does) will NOT cause Quick Cache
			 *    to stop functioning due to the lack of an `advanced-cache.php` file, which is generated by Quick Cache.
			 *
			 *    For instance, if you have a Git repo with all of your site files; when you push those files
			 *    to your website to deploy them, you most likely do NOT have the `advanced-cache.php` file.
			 *    Quick Cache creates this file on its own. Thus, if it's missing (and QC is active)
			 *    we simply regenerate the file automatically to keep Quick Cache running.
			 */
			public function check_advanced_cache()
			{
				if(!$this->options['enable'])
					return; // Nothing to do.

				if(!empty($_REQUEST[__NAMESPACE__]))
					return; // Skip on plugin actions.

				$cache_dir = $this->cache_dir(); // Current cache directory.

				if(!is_file($cache_dir.'/qc-advanced-cache'))
					$this->add_advanced_cache();
			}

			/**
			 * Creates and adds the `advanced-cache.php` file.
			 *
			 * @since 140422 First documented version.
			 *
			 * @note Many of the Quick Cache option values become PHP Constants in the `advanced-cache.php` file.
			 *    We take an option key (e.g. `version_salt`) and prefix it with `quick_cache_`.
			 *    Then we convert it to uppercase (e.g. `QUICK_CACHE_VERSION_SALT`) and wrap
			 *    it with double percent signs to form a replacement codes.
			 *    ex: `%%QUICK_CACHE_VERSION_SALT%%`
			 *
			 * @note There are a few special options considered by this routine which actually
			 *    get converted to regex patterns before they become replacement codes.
			 *
			 * @note In the case of a version salt, a PHP syntax is performed also.
			 *
			 * @return boolean|null `TRUE` on success. `FALSE` or `NULL` on failure.
			 *    A special `NULL` return value indicates success with a single failure
			 *    that is specifically related to the `qc-advanced-cache` file.
			 */
			public function add_advanced_cache()
			{
				if(!$this->remove_advanced_cache())
					return FALSE; // Still exists.

				$cache_dir               = $this->cache_dir();
				$advanced_cache_file     = WP_CONTENT_DIR.'/advanced-cache.php';
				$advanced_cache_template = dirname(__FILE__).'/includes/advanced-cache.tpl.php';

				if(is_file($advanced_cache_file) && !is_writable($advanced_cache_file))
					return FALSE; // Not possible to create.

				if(!is_file($advanced_cache_file) && !is_writable(dirname($advanced_cache_file)))
					return FALSE; // Not possible to create.

				if(!is_file($advanced_cache_template) || !is_readable($advanced_cache_template))
					return FALSE; // Template file is missing; or not readable.

				if(!($advanced_cache_contents = file_get_contents($advanced_cache_template)))
					return FALSE; // Template file is missing; or is not readable.

				$possible_advanced_cache_constant_key_values = array_merge(
					$this->options, // The following additional keys are dynamic.
					array('cache_dir' => $this->base_path_to($this->cache_sub_dir)
					));
				foreach($possible_advanced_cache_constant_key_values as $_option => $_value)
				{
					$_value = (string)$_value; // Force string.

					switch($_option) // Some values need tranformations.
					{
						default: // Default case handler.

							$_value = "'".$this->esc_sq($_value)."'";

							break; // Break switch handler.
					}
					$advanced_cache_contents = // Fill replacement codes.
						str_ireplace(array("'%%".__NAMESPACE__.'_'.$_option."%%'",
						                   "'%%".str_ireplace('_cache', '', __NAMESPACE__).'_'.$_option."%%'"),
						             $_value, $advanced_cache_contents);
				}
				unset($_option, $_value, $_values, $_response); // Housekeeping.

				if(strpos($this->file, WP_CONTENT_DIR) === 0)
					$plugin_file = "WP_CONTENT_DIR.'".$this->esc_sq(str_replace(WP_CONTENT_DIR, '', $this->file))."'";
				else $plugin_file = "'".$this->esc_sq($this->file)."'"; // Else use full absolute path.
				// Make it possible for the `advanced-cache.php` handler to find the plugin directory reliably.
				$advanced_cache_contents = str_ireplace("'%%".__NAMESPACE__."_PLUGIN_FILE%%'", $plugin_file, $advanced_cache_contents);

				// Ignore; this is created by Quick Cache; and we don't need to obey in this case.
				#if(defined('DISALLOW_FILE_MODS') && DISALLOW_FILE_MODS)
				#	return FALSE; // We may NOT edit any files.

				if(!file_put_contents($advanced_cache_file, $advanced_cache_contents))
					return FALSE; // Failure; could not write file.

				$cache_lock = $this->cache_lock(); // Lock cache.

				if(!is_dir($cache_dir)) mkdir($cache_dir, 0775, TRUE);

				if(is_writable($cache_dir) && !is_file($cache_dir.'/.htaccess'))
					file_put_contents($cache_dir.'/.htaccess', $this->htaccess_deny);

				if(!is_dir($cache_dir) || !is_writable($cache_dir) || !is_file($cache_dir.'/.htaccess') || !file_put_contents($cache_dir.'/qc-advanced-cache', time()))
				{
					$this->cache_unlock($cache_lock); // Unlock cache.
					return NULL; // Special return value (NULL) in this case.
				}
				$this->cache_unlock($cache_lock); // Unlock cache.

				return TRUE; // Success!
			}

			/**
			 * Removes the `advanced-cache.php` file.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return boolean `TRUE` on success. `FALSE` on failure.
			 *
			 * @note The `advanced-cache.php` file is NOT actually deleted by this routine.
			 *    Instead of deleting the file, we simply empty it out so that it's `0` bytes in size.
			 *
			 *    The reason for this is to preserve any file permissions set by the site owner.
			 *    If the site owner previously allowed this specific file to become writable, we don't want to
			 *    lose that permission by deleting the file; forcing the site owner to do it all over again later.
			 *
			 *    An example of where this is useful is when a site owner deactivates the QC plugin,
			 *    but later they decide that QC really is the most awesome plugin in the world and they turn it back on.
			 *
			 * @see delete_advanced_cache()
			 */
			public function remove_advanced_cache()
			{
				$advanced_cache_file = WP_CONTENT_DIR.'/advanced-cache.php';

				if(!is_file($advanced_cache_file)) return TRUE; // Already gone.

				if(is_readable($advanced_cache_file) && filesize($advanced_cache_file) === 0)
					return TRUE; // Already gone; i.e. it's empty already.

				if(!is_writable($advanced_cache_file)) return FALSE; // Not possible.

				// Ignore; this is created by Quick Cache; and we don't need to obey in this case.
				#if(defined('DISALLOW_FILE_MODS') && DISALLOW_FILE_MODS)
				#	return FALSE; // We may NOT edit any files.

				/* Empty the file only. This way permissions are NOT lost in cases where
					a site owner makes this specific file writable for Quick Cache. */
				if(file_put_contents($advanced_cache_file, '') !== 0)
					return FALSE; // Failure.

				return TRUE; // Removal success.
			}

			/**
			 * Deletes the `advanced-cache.php` file.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return boolean `TRUE` on success. `FALSE` on failure.
			 *
			 * @note The `advanced-cache.php` file is deleted by this routine.
			 *
			 * @see remove_advanced_cache()
			 */
			public function delete_advanced_cache()
			{
				$advanced_cache_file = WP_CONTENT_DIR.'/advanced-cache.php';

				if(!is_file($advanced_cache_file)) return TRUE; // Already gone.

				// Ignore; this is created by Quick Cache; and we don't need to obey in this case.
				#if(defined('DISALLOW_FILE_MODS') && DISALLOW_FILE_MODS)
				#	return FALSE; // We may NOT edit any files.

				if(!is_writable($advanced_cache_file) || !unlink($advanced_cache_file))
					return FALSE; // Not possible; or outright failure.

				return TRUE; // Deletion success.
			}

			/**
			 * Checks to make sure the `qc-blog-paths` file still exists;
			 *    and if it doesn't, the `qc-blog-paths` file is regenerated automatically.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `init` hook.
			 *
			 * @note This runs so that remote deployments which completely wipe out an
			 *    existing set of website files (like the AWS Elastic Beanstalk does) will NOT cause Quick Cache
			 *    to stop functioning due to the lack of a `qc-blog-paths` file, which is generated by Quick Cache.
			 *
			 *    For instance, if you have a Git repo with all of your site files; when you push those files
			 *    to your website to deploy them, you most likely do NOT have the `qc-blog-paths` file.
			 *    Quick Cache creates this file on its own. Thus, if it's missing (and QC is active)
			 *    we simply regenerate the file automatically to keep Quick Cache running.
			 */
			public function check_blog_paths()
			{
				if(!$this->options['enable'])
					return; // Nothing to do.

				if(!is_multisite()) return; // N/A.

				if(!empty($_REQUEST[__NAMESPACE__]))
					return; // Skip on plugin actions.

				$cache_dir = $this->cache_dir(); // Current cache directory.

				if(!is_file($cache_dir.'/qc-blog-paths'))
					$this->update_blog_paths();
			}

			/**
			 * Creates and/or updates the `qc-blog-paths` file.
			 *
			 * @since 140422 First documented version.
			 *
			 * @attaches-to `enable_live_network_counts` filter.
			 *
			 * @param mixed $enable_live_network_counts Optional, defaults to a `NULL` value.
			 *
			 * @return mixed The value of `$enable_live_network_counts` (passes through).
			 *
			 * @note While this routine is attached to a WP filter, we also call upon it directly at times.
			 */
			public function update_blog_paths($enable_live_network_counts = NULL)
			{
				$value = // This hook actually rides on a filter.
					$enable_live_network_counts; // Filter value.

				if(!$this->options['enable'])
					return $value; // Nothing to do.

				if(!is_multisite()) return $value; // N/A.

				$cache_dir  = $this->cache_dir(); // Cache dir.
				$cache_lock = $this->cache_lock(); // Lock.

				if(!is_dir($cache_dir)) mkdir($cache_dir, 0775, TRUE);

				if(is_writable($cache_dir) && !is_file($cache_dir.'/.htaccess'))
					file_put_contents($cache_dir.'/.htaccess', $this->htaccess_deny);

				if(is_dir($cache_dir) && is_writable($cache_dir))
				{
					$paths = // Collect child blog paths from the WordPress database.
						$this->wpdb()->get_col("SELECT `path` FROM `".esc_sql($this->wpdb()->blogs)."` WHERE `deleted` <= '0'");

					foreach($paths as &$_path) // Strip base; these need to match `$host_dir_token`.
						$_path = '/'.ltrim(preg_replace('/^'.preg_quote($this->host_base_token(), '/').'/', '', $_path), '/');
					unset($_path); // Housekeeping.

					file_put_contents($cache_dir.'/qc-blog-paths', serialize($paths));
				}
				$this->cache_unlock($cache_lock); // Unlock cache directory.

				return $value; // Pass through untouched (always).
			}

			/**
			 * Removes the entire base directory.
			 *
			 * @since 140422 First documented version.
			 *
			 * @return integer Total files removed by this routine (if any).
			 */
			public function remove_base_dir()
			{
				$counter = 0; // Initialize.

				@set_time_limit(1800); // @TODO When disabled, display a warning.

				return ($counter += $this->delete_all_files_dirs_in($this->wp_content_base_dir_to(''), TRUE));
			}
		}

		/**
		 * Used internally by other Quick Cache classes as an easy way to reference
		 *    the core {@link plugin} class instance for Quick Cache.
		 *
		 * @since 140422 First documented version.
		 *
		 * @return plugin Class instance.
		 */
		function plugin() // Easy reference.
		{
			return $GLOBALS[__NAMESPACE__];
		}

		/**
		 * A global reference to the Quick Cache plugin.
		 *
		 * @since 140422 First documented version.
		 *
		 * @var plugin Main plugin class.
		 */
		if(!isset($GLOBALS[__NAMESPACE__.'_autoload_plugin']) || $GLOBALS[__NAMESPACE__.'_autoload_plugin'])
			$GLOBALS[__NAMESPACE__] = new plugin(); // Load the Quick Cache plugin automatically.
		require_once dirname(__FILE__).'/includes/api-class.php'; // API class.
	}
	else if(empty($GLOBALS[__NAMESPACE__.'_uninstalling'])) add_action('all_admin_notices', function ()
	{
		echo '<div class="error">'.
		     '   <p>'. // Running multiple versions of this plugin at same time.
		     '      '.__('Please disable the LITE version of Quick Cache before you activate the PRO version.', str_replace('_', '-', __NAMESPACE__)).
		     '   </p>'.
		     '</div>';
	});
}