diff --git a/quick-cache-pro/includes/menu-pages.php b/quick-cache-pro/includes/menu-pages.php index a2014945..fb77dde9 100644 --- a/quick-cache-pro/includes/menu-pages.php +++ b/quick-cache-pro/includes/menu-pages.php @@ -223,6 +223,13 @@ public function options() echo ' '."\n"; echo '

'."\n"; echo '
'."\n"; + echo '

'.__('Auto-Purge "RSS/RDF/ATOM Feeds" Too?', $this->plugin->text_domain).'

'."\n"; + echo '

'.__('If you enable Feed Caching (below), this can be quite handy. If enabled, when you update a Post/Page, approve a Comment, or make other changes where Quick Cache can detect that certain types of Feeds should be purged to keep your site up-to-date, then Quick Cache will do this for you automatically. For instance, the blog\'s master feed, the blog\'s master comments feed, feeds associated with comments on a Post/Page, term-related feeds (including mixed term-related feeds), author-related feeds, etc. Under various circumstances (i.e. as you work in the Dashboard) these can be purged automatically to keep your site up-to-date.', $this->plugin->text_domain).'

'."\n"; + echo '

'."\n"; + echo '
'."\n"; echo '

'.__('Auto-Purge "XML Sitemaps" Too?', $this->plugin->text_domain).'

'."\n"; echo '

'.__('If you\'re generating XML Sitemaps with a plugin like Google XML Sitemaps, you can tell Quick Cache to automatically purge the cache of any XML Sitemaps whenever it purges a Post/Page. Note; this does NOT purge the XML Sitemap itself of course, only the cache. The point being, to clear the cache and allow changes to a Post/Page to be reflected by a fresh copy of your XML Sitemap; sooner rather than later.', $this->plugin->text_domain).'

'."\n"; echo '

'."\n"; echo ' '."\n"; echo ' '."\n"; diff --git a/quick-cache-pro/includes/share.php b/quick-cache-pro/includes/share.php index c6d794ee..07b546a0 100644 --- a/quick-cache-pro/includes/share.php +++ b/quick-cache-pro/includes/share.php @@ -243,6 +243,9 @@ public function build_cache_path($url, $with_user_token = '', $with_version_salt if($url && strpos($url, '://') === FALSE) $url = '//'.ltrim($url, '/'); + if($url && strpos($url, '&') !== FALSE) + $url = str_replace('&', '&', $url); + if(!$url || !($url = parse_url($url))) return ''; // Invalid URL. @@ -902,6 +905,42 @@ public function esc_sq($string, $times = 1) return str_replace("'", str_repeat('\\', abs($times))."'", (string)$string); } + /** + * Normalizes directory/file separators. + * + * @since 14xxxx Implementing XML/RSS feed purging. + * + * @param string $dir_file Directory/file path. + * + * @param boolean $allow_trailing_slash Defaults to FALSE. + * If TRUE; and `$dir_file` contains a trailing slash; we'll leave it there. + * + * @return string Normalized directory/file path. + */ + public function n_dir_seps($dir_file, $allow_trailing_slash = FALSE) + { + $dir_file = (string)$dir_file; // Force string value. + if(!isset($dir_file[0])) return ''; // Catch empty string. + + if(strpos($dir_file, '://' !== FALSE)) // A possible stream wrapper? + { + if(preg_match('/^(?P[a-zA-Z0-9]+)\:\/\//', $dir_file, $stream_wrapper)) + $dir_file = preg_replace('/^(?P[a-zA-Z0-9]+)\:\/\//', '', $dir_file); + } + if(strpos($dir_file, ':' !== FALSE)) // Might have a Windows® drive letter? + { + if(preg_match('/^(?P[a-zA-Z])\:[\/\\\\]/', $dir_file)) // It has a Windows® drive letter? + $dir_file = preg_replace_callback('/^(?P[a-zA-Z])\:[\/\\\\]/', create_function('$m', 'return strtoupper($m[0]);'), $dir_file); + } + $dir_file = preg_replace('/\/+/', '/', str_replace(array(DIRECTORY_SEPARATOR, '\\', '/'), '/', $dir_file)); + $dir_file = ($allow_trailing_slash) ? $dir_file : rtrim($dir_file, '/'); // Strip trailing slashes. + + if(!empty($stream_wrapper[0])) // Stream wrapper (force lowercase). + $dir_file = strtolower($stream_wrapper[0]).$dir_file; + + return $dir_file; // Normalized now. + } + /** * Recursive directory iterator based on a regex pattern. * @@ -922,6 +961,66 @@ public function dir_regex_iteration($dir, $regex) return $regex_iterator; } + /** + * Deletes files from the cache directory (for the current host) that match a regex pattern. + * + * @since 14xxxx Implementing XML/RSS feed purging. + * + * @param string $regex A regex pattern; compares to each full file path. + * + * @return integer Total files deleted by this routine (if any). + * + * @throws \exception If unable to delete a file for any reason. + * + * @TODO @raamdev I think we could take the same concept introduced by this routine and use it to build others + * that deal with purging/clearing/wiping; thereby centralizing this sort of job, making QC DRYer. + * Also, this type of routine works to improve speed when running on a large MS network. + * + * Suggested class members to come in a future release of QC. + * - `purge_files_from_host_cache_dir()` + * - `clear_files_from_host_cache_dir()` + * + * Also, this class member (i.e. `delete_files_from_host_cache_dir()`) could be + * used by many of the existing auto-purge routines. Thereby requiring less code + * and speeding QC up overall; i.e. making it faster on large MS networks. + */ + public function delete_files_from_host_cache_dir($regex) + { + $counter = 0; // Initialize. + + if(!($regex = (string)$regex)) + return $counter; // Nothing to do. + + if(!is_dir($cache_dir = $this->cache_dir())) + return $counter; // Nothing to do. + + $host = $_SERVER['HTTP_HOST']; + $host_base_dir_tokens = $this->host_base_dir_tokens(); + $cache_dir = $this->n_dir_seps($cache_dir); + + foreach(array('http', 'https') as $_host_scheme) // Consider all possible schemes. + { + $_host_url = $_host_scheme.'://'.$host.$host_base_dir_tokens; // Base URL for this host (w/ MS support). + $_host_cache_path = $this->build_cache_path($_host_url, '', '', $this::CACHE_PATH_NO_PATH_INDEX | $this::CACHE_PATH_NO_QUV | $this::CACHE_PATH_NO_EXT); + $_host_cache_dir = $this->n_dir_seps($cache_dir.'/'.$_host_cache_path); + + /** @var $_dir_file \RecursiveDirectoryIterator For IDEs. */ + if($_host_cache_dir && is_dir($_host_cache_dir)) foreach($this->dir_regex_iteration($_host_cache_dir, $regex) as $_dir_file) + { + if(($_dir_file->isFile() || $_dir_file->isLink()) && ($_host_cache_dir !== $cache_dir || strpos($_dir_file->getSubpathname(), '/') !== FALSE)) + // Don't delete files in the immediate directory; e.g. `qc-advanced-cache` or `.htaccess`, etc. + // Actual `http|https/...` cache files are nested. Files in the immediate directory are for other purposes. + if(!unlink($_dir_file->getPathname())) // Throw exception if unable to delete. + throw new \exception(sprintf(__('Unable to delete file: `%1$s`.', $this->text_domain), $_dir_file->getPathname())); + else $counter++; // Increment counter for each file we purge. + } + unset($_dir_file); // Housekeeping. + } + unset($_host_scheme, $_host_url, $_host_cache_path, $_host_cache_dir); // Housekeeping. + + return $counter; // Total files deleted by this routine. + } + /* -------------------------------------------------------------------------------------- * Hook/filter API for Quick Cache. -------------------------------------------------------------------------------------- */ diff --git a/quick-cache-pro/quick-cache-pro.inc.php b/quick-cache-pro/quick-cache-pro.inc.php index 1993e2a4..5a5dc780 100644 --- a/quick-cache-pro/quick-cache-pro.inc.php +++ b/quick-cache-pro/quick-cache-pro.inc.php @@ -182,6 +182,7 @@ public function setup() 'admin_bar_enable' => '1', // `0|1`. 'cache_clear_s2clean_enable' => '0', // `0|1`. 'cache_clear_eval_code' => '', // PHP code. + 'cache_purge_xml_feeds_enable' => '1', // `0|1`. 'cache_purge_xml_sitemaps_enable' => '1', // `0|1`. 'cache_purge_xml_sitemap_patterns' => '/sitemap*.xml', // Empty string or line-delimited patterns. 'cache_purge_home_page_enable' => '1', // `0|1`. @@ -1395,7 +1396,6 @@ public function auto_purge_post_cache($id, $force = FALSE) static::$static['___allow_auto_purge_post_cache'] = TRUE; // Reset state. return $counter; // Nothing to do. } - if(!$this->options['enable']) return $counter; // Nothing to do. @@ -1455,10 +1455,14 @@ public function auto_purge_post_cache($id, $force = FALSE) } unset($_file); // Just a little housekeeping. - $counter += $this->auto_purge_xml_sitemaps_cache(); // If enabled and necessary. - $counter += $this->auto_purge_home_page_cache(); // If enabled and necessary. - $counter += $this->auto_purge_posts_page_cache(); // If enabled and necessary. - $counter += $this->auto_purge_post_terms_cache($id, $force); // If enabled and necessary. + $counter += $this->auto_purge_xml_feeds_cache('blog'); + $counter += $this->auto_purge_xml_feeds_cache('post-terms', $id); + $counter += $this->auto_purge_xml_feeds_cache('post-authors', $id); + + $counter += $this->auto_purge_xml_sitemaps_cache(); + $counter += $this->auto_purge_home_page_cache(); + $counter += $this->auto_purge_posts_page_cache(); + $counter += $this->auto_purge_post_terms_cache($id, $force); return apply_filters(__METHOD__, $counter, get_defined_vars()); } @@ -1507,6 +1511,266 @@ public function auto_purge_post_cache_transition($new_status, $old_status, \WP_P return apply_filters(__METHOD__, $counter, get_defined_vars()); } + /** + * Automatically purges cache files related to XML feeds. + * + * @since 14xxxx Working to improve compatibility with feeds. + * + * @param string $type Type of feed(s) to auto-purge. + * @param integer $post_id A Post ID (when applicable). + * + * @return integer Total files purged by this routine (if any). + * + * @throws \exception If a purge 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_purge_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(!$this->options['cache_purge_xml_feeds_enable']) + return $counter; // Nothing to do. + + if(!is_dir($cache_dir = $this->cache_dir())) + return $counter; // Nothing to do. + + $home_url = home_url('/'); // Need this below. + $default_feed = get_default_feed(); // Need this below. + $seo_friendly_permalinks = (boolean)get_option('permalink_structure'); + $_this = $this; // Reference needed by the closure below. + $feed_cache_path_regexs = array(); // Initialize array of feed cache paths. + $build_cache_path_regex = function ($feed_link, $wildcard_regex = NULL) use ($_this) + { + if(!is_string($feed_link) || !$feed_link) + return ''; // Nothing to do here. + + $cache_path_flags = $_this::CACHE_PATH_NO_SCHEME | $_this::CACHE_PATH_NO_EXT + | $_this::CACHE_PATH_NO_USER | $_this::CACHE_PATH_NO_VSALT; + if($wildcard_regex) $cache_path_flags |= $_this::CACHE_PATH_ALLOW_WILDCARDS; + + $cache_path_regex = preg_quote($_this->build_cache_path($feed_link, '', '', $cache_path_flags), '/'); + + if($wildcard_regex) // Replace wildcards with regex. + $cache_path_regex = preg_replace('/\\\\\*/', $wildcard_regex, $cache_path_regex); + + return $cache_path_regex; + }; + switch($type) // Handle purging based on the `$type`. + { + case 'blog': // The blog feed; i.e. `/feed/` on most WP installs. + + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link($default_feed)); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('rdf')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('rss')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('rss2')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('atom')); + + // It is not necessary to cover query string variations for these when `$seo_friendly_permalinks = TRUE`, + // because `redirect_canonical()` will force SEO-friendly links in the end anyway. + + break; // Break switch handler. + + case 'blog-comments': // The blog comments feed; i.e. `/comments/feed/` on most WP installs. + + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('comments_'.$default_feed)); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('comments_rdf')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('comments_rss')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('comments_rss2')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_feed_link('comments_atom')); + + // It is not necessary to cover query string variations for these when `$seo_friendly_permalinks = TRUE`, + // because `redirect_canonical()` will force SEO-friendly links in the end anyway. + + break; // Break switch handler. + + // @TODO Possibly consider search-related feeds in the future. + // See: + // e.g. case 'blog-searches': + + case 'post-comments': // Feeds related to comments that a post has. + + if(!$post_id) break; // Nothing to do here. + if(!($post = get_post($post_id))) break; + + $feed_cache_path_regexs[] = $build_cache_path_regex(get_post_comments_feed_link($post->ID, $default_feed)); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_post_comments_feed_link($post->ID, 'rdf')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_post_comments_feed_link($post->ID, 'rss')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_post_comments_feed_link($post->ID, 'rss2')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_post_comments_feed_link($post->ID, 'atom')); + + // It is not necessary to cover query string variations for these when `$seo_friendly_permalinks = TRUE`, + // because `redirect_canonical()` will force SEO-friendly links in the end anyway. + + break; // Break switch handler. + + case 'post-authors': // Feeds related to authors that a post has. + + if(!$post_id) break; // nothing to do here. + if(!($post = get_post($post_id))) break; + + $feed_cache_path_regexs[] = $build_cache_path_regex(get_author_feed_link($post->post_author, $default_feed)); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_author_feed_link($post->post_author, 'rdf')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_author_feed_link($post->post_author, 'rss')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_author_feed_link($post->post_author, 'rss2')); + $feed_cache_path_regexs[] = $build_cache_path_regex(get_author_feed_link($post->post_author, 'atom')); + + if($seo_friendly_permalinks) // The above uses SEO-friendly permalinks? + // Here we cover query string variations that can be left behind after `redirect_canonical()` does its thing. + // In the case of author-related feeds, most of the URL is converted to SEO-friendly format. + // Everything except `?author=` which is what we deal with below. + { + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $post->post_author)), $home_url.'feed/'.urlencode($default_feed).'/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $post->post_author)), $home_url.'feed/rdf/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $post->post_author)), $home_url.'feed/rss/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $post->post_author)), $home_url.'feed/rss2/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $post->post_author)), $home_url.'feed/atom/')); + + if(($_post_author = get_userdata($post->post_author)) && !empty($_post_author->user_nicename)) // By author nicename. + { + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $_post_author->user_nicename)), $home_url.'feed/'.urlencode($default_feed).'/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $_post_author->user_nicename)), $home_url.'feed/rdf/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $_post_author->user_nicename)), $home_url.'feed/rss/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $_post_author->user_nicename)), $home_url.'feed/rss2/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array('author' => $_post_author->user_nicename)), $home_url.'feed/atom/')); + } + unset($_post_author); // Housekeeping. + } + break; // Break switch handler. + + case 'post-terms': // Feeds related to terms that a post has. + + if(!$post_id) break; // Nothing to do here. + if(!($post = get_post($post_id))) break; + + $post_terms = array(); // Initialize array of all post terms. + + if(!is_array($_post_taxonomies = get_object_taxonomies($post, 'objects')) || !$_post_taxonomies) + break; // Nothing to do here; post has no terms. + + foreach($_post_taxonomies as $_post_taxonomy) // Collect terms for each taxonomy. + if(is_array($_post_taxonomy_terms = wp_get_post_terms($post->ID, $_post_taxonomy->name)) && $_post_taxonomy_terms) + $post_terms = array_merge($post_terms, $_post_taxonomy_terms); + + $post_term_cache_path_variations = function ($post_term_feed_link, $post_term) use ($build_cache_path_regex) + { + $post_term_feed_link = (string)$post_term_feed_link; // Force string. + $variations = array(); // Initialize the array of variations. + if($post_term_feed_link) $variations[] = $build_cache_path_regex($post_term_feed_link); + + /* NOTE: We CANNOT reliably include permalink variations here that use query string vars. + This is because Quick Cache hashes query string variables via MD5 checksums. + For this reason, we deal with SEO-friendly permalink variations only here. */ + + if($post_term_feed_link && strpos($post_term_feed_link, '?') === FALSE + && is_object($post_term) && !empty($post_term->term_id) && !empty($post_term->slug) + )// Create variations that deal with SEO-friendly permalink variations. + { + // Quick example: `(?:123|slug)`; to consider both. + $_term_id_or_slug = '(?:'.preg_quote($post_term->term_id, '/'). + '|'.preg_quote(preg_replace('/[^a-z0-9\/.]/i', '-', $post_term->slug), '/').')'; + + // Quick example: `http://www.example.com/tax/term/feed`; + // with a wildcard this becomes: `http://www.example.com/tax/*/feed` + $_wildcarded = preg_replace('/\/[^\/]+\/feed([\/?#]|$)/', '/*/feed'.'${1}', $post_term_feed_link); + + // Quick example: `http://www.example.com/tax/*/feed`; + // becomes: `www\.example\.com\/tax\/.*?(?=[\/\-]?(?:123|slug)[\/\-]).*?\/feed` + // ... this covers variations that use: `/tax/term,term/feed/` + // ... also covers variations that use: `/tax/term/tax/term/feed/` + $variations[] = $build_cache_path_regex($_wildcarded, '.*?(?=[\/\-]?'.$_term_id_or_slug.'[\/\-]).*?'); + // NOTE: This may also pick up false-positives. Not much we can do about this. + // For instance, if another feed has the same word/slug in what is actually a longer/different term. + // Or, if another feed has the same word/slug in what is actually the name of a taxonomy. + + unset($_term_id_or_slug, $_wildcarded); // Housekeeping. + } + return $variations; // Zero or more variations. + }; + foreach($post_terms as $_post_term) // See: + { + $_post_term_feed_link = get_term_feed_link($_post_term->term_id, $_post_term->taxonomy, $default_feed); + $feed_cache_path_regexs = array_merge($feed_cache_path_regexs, $post_term_cache_path_variations($_post_term_feed_link, $_post_term)); + + $_post_term_feed_link = get_term_feed_link($_post_term->term_id, $_post_term->taxonomy, 'rdf'); + $feed_cache_path_regexs = array_merge($feed_cache_path_regexs, $post_term_cache_path_variations($_post_term_feed_link, $_post_term)); + + $_post_term_feed_link = get_term_feed_link($_post_term->term_id, $_post_term->taxonomy, 'rss'); + $feed_cache_path_regexs = array_merge($feed_cache_path_regexs, $post_term_cache_path_variations($_post_term_feed_link, $_post_term)); + + $_post_term_feed_link = get_term_feed_link($_post_term->term_id, $_post_term->taxonomy, 'rss2'); + $feed_cache_path_regexs = array_merge($feed_cache_path_regexs, $post_term_cache_path_variations($_post_term_feed_link, $_post_term)); + + $_post_term_feed_link = get_term_feed_link($_post_term->term_id, $_post_term->taxonomy, 'atom'); + $feed_cache_path_regexs = array_merge($feed_cache_path_regexs, $post_term_cache_path_variations($_post_term_feed_link, $_post_term)); + + if($seo_friendly_permalinks && ($_post_term_taxonomy = get_taxonomy($_post_term->taxonomy))/* The above uses SEO-friendly permalinks? */) + // Here we cover query string variations that can be left behind after `redirect_canonical()` does its thing. + // In the case of term-related feeds, most of the URL is converted to SEO-friendly format. + // Everything except `?[tax query var]=` which is what we deal with below. + { + if($_post_term_taxonomy->name === 'category') + $_post_term_taxonomy_query_var = 'cat'; // Special query var. + else $_post_term_taxonomy_query_var = $_post_term_taxonomy->query_var; + + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->term_id)), $home_url.'feed/'.urlencode($default_feed).'/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->term_id)), $home_url.'feed/rdf/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->term_id)), $home_url.'feed/rss/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->term_id)), $home_url.'feed/rss2/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->term_id)), $home_url.'feed/atom/')); + + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->slug)), $home_url.'feed/'.urlencode($default_feed).'/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->slug)), $home_url.'feed/rdf/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->slug)), $home_url.'feed/rss/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->slug)), $home_url.'feed/rss2/')); + $feed_cache_path_regexs[] = $build_cache_path_regex(add_query_arg(urlencode_deep(array($_post_term_taxonomy_query_var => $_post_term->slug)), $home_url.'feed/atom/')); + } + unset($_post_term_taxonomy, $_post_term_taxonomy_query_var); // Housekeeping. + } + unset($_post_taxonomies, $_post_taxonomy, $_post_taxonomy_terms, $_post_term, $_post_term_feed_link); + + break; // Break switch handler. + } + foreach($feed_cache_path_regexs as $_key => $_feed_cache_path_regex) + if(!is_string($_feed_cache_path_regex) || !$_feed_cache_path_regex) unset($feed_cache_path_regexs[$_key]); + unset($_key, $_feed_cache_path_regex); // Housekeeping. + + if(!$feed_cache_path_regexs || !($feed_cache_path_regexs = array_unique($feed_cache_path_regexs))) + return $counter; // Nothing to do here. + + $in_sets_of = apply_filters(__METHOD__.'__in_sets_of', 10, get_defined_vars()); + + for($_i = 0; $_i < count($feed_cache_path_regexs); $_i = $_i + $in_sets_of) + // This prevents the regex from hitting a backtrack limit in some environments. + { + $_feed_cache_path_regexs = array_slice($feed_cache_path_regexs, $_i, $in_sets_of); + $_regex = '/^'.preg_quote($cache_dir, '/').'\/[^\/]+\/(?:'.implode('|', $_feed_cache_path_regexs).')\./'; + $counter += $this->delete_files_from_host_cache_dir($_regex); + } + unset($_i, $_feed_cache_path_regexs, $_regex); // Housekeeping. + + if($counter && $this->options['change_notifications_enable'] && is_admin()) + $this->enqueue_notice(''. + sprintf(__('Quick Cache: detected changes. Found XML feeds of type %1$s (auto-purging).', $this->text_domain), esc_html($type))); + + return apply_filters(__METHOD__, $counter, get_defined_vars()); + } + /** * Automatically purges cache files related to XML sitemaps. * @@ -1636,6 +1900,8 @@ public function auto_purge_home_page_cache() } unset($_file); // Just a little housekeeping. + $counter += $this->auto_purge_xml_feeds_cache('blog'); + return apply_filters(__METHOD__, $counter, get_defined_vars()); } @@ -1709,6 +1975,8 @@ public function auto_purge_posts_page_cache() } unset($_file); // Just a little housekeeping. + $counter += $this->auto_purge_xml_feeds_cache('blog'); + return apply_filters(__METHOD__, $counter, get_defined_vars()); } @@ -1813,6 +2081,9 @@ public function auto_purge_author_page_cache($post_ID, \WP_Post $post_after, \WP } unset($_file, $_author); // Just a little housekeeping. + $counter += $this->auto_purge_xml_feeds_cache('blog'); + $counter += $this->auto_purge_xml_feeds_cache('post-authors', $post_ID); + return apply_filters(__METHOD__, $counter, get_defined_vars()); } @@ -1963,6 +2234,8 @@ public function auto_purge_post_terms_cache($id, $force = FALSE) } unset($_term, $_file); // Just a little housekeeping. + $counter += $this->auto_purge_xml_feeds_cache('post-terms', $id); + return apply_filters(__METHOD__, $counter, get_defined_vars()); } @@ -2001,12 +2274,15 @@ public function auto_purge_comment_post_cache($id) return $counter; // Nothing we can do. if($comment->comment_approved === 'spam' || $comment->comment_approved === '0') + // Don't allow next `auto_purge_post_cache()` call to clear post cache. + // Also, don't allow spam to clear cache. { - static::$static['___allow_auto_purge_post_cache'] = FALSE; // Don't allow next `auto_purge_post_cache()` call to clear post cache. - return $counter; // Don't allow spam to clear cache. + static::$static['___allow_auto_purge_post_cache'] = FALSE; + return $counter; // Nothing to do here. } - - $counter = $this->auto_purge_post_cache($comment->comment_post_ID); + $counter += $this->auto_purge_xml_feeds_cache('blog-comments'); + $counter += $this->auto_purge_xml_feeds_cache('post-comments', $comment->comment_post_ID); + $counter += $this->auto_purge_post_cache($comment->comment_post_ID); return apply_filters(__METHOD__, $counter, get_defined_vars()); } @@ -2044,15 +2320,15 @@ public function auto_purge_comment_transition($new_status, $old_status, $comment if(empty($comment->comment_post_ID)) return $counter; // Nothing we can do. - if($old_status === 'approved' || ($old_status === 'unapproved' && $new_status === 'approved')) - { - $counter = $this->auto_purge_post_cache($comment->comment_post_ID); - } - else + if(!($old_status === 'approved' || ($old_status === 'unapproved' && $new_status === 'approved'))) + // If excluded here, don't allow next `auto_purge_post_cache()` call to clear post cache. { - static::$static['___allow_auto_purge_post_cache'] = FALSE; // Don't allow next `auto_purge_post_cache()` call to clear post cache. - return $counter; // Don't allow Unapproved comments not being Approved to clear cache. + static::$static['___allow_auto_purge_post_cache'] = FALSE; + return $counter; // Nothing to do here. } + $counter += $this->auto_purge_xml_feeds_cache('blog-comments'); + $counter += $this->auto_purge_xml_feeds_cache('post-comments', $comment->comment_post_ID); + $counter += $this->auto_purge_post_cache($comment->comment_post_ID); return apply_filters(__METHOD__, $counter, get_defined_vars()); }