PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.0
2.14.0 2.13.0 2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 All 55 releases
← All changes | includes/seo/class-cache-purger.php +142 -38 2.13.0 → 2.14.0 View file →
@@ -52,8 +52,39 @@
52 52 */
53 53 class Cache_Purger {
54 54
55 55 /**
56 + * A cache this class can drop one URL from.
57 + *
58 + * @since 2.14.0
59 + * @var string
60 + */
61 + public const PURGE_URL = 'url';
62 +
63 + /**
64 + * A cache whose narrowest purge is one post.
65 + *
66 + * Three of the integrations below expose a per-post hook and no per-URL
67 + * one. They are purged from `purge_posts()`, which has the post ID, and
68 + * cannot be reached by `purge_urls()` alone: an llms.txt document has no
69 + * post behind it. That difference is what `$target` reports on, rather
70 + * than a flat "purgeable" that would be true on one path and false on the
71 + * other while saying the same thing to both.
72 + *
73 + * @since 2.14.0
74 + * @var string
75 + */
76 + public const PURGE_POST = 'post';
77 +
78 + /**
79 + * A cache that cannot be purged from here at all.
80 + *
81 + * @since 2.14.0
82 + * @var string
83 + */
84 + public const PURGE_NONE = 'none';
85 +
86 + /**
56 87 * Elementor's per-document rendered-element cache.
57 88 *
58 89 * Mirrors `Elementor\Core\Base\Document::CACHE_META_KEY`. The literal is
59 90 * used rather than the constant because Elementor may not be loaded, and
@@ -79,66 +110,94 @@
79 110 */
80 111 private static array $purged = [];
81 112
82 113 /**
83 - * Caching plugins ThinkRank knows about, and whether it can purge them.
114 + * Caching plugins ThinkRank knows about, and how narrowly it can purge
115 + * each one.
84 116 *
85 117 * Detection is a presence check on the plugin's own constant or class, not
86 118 * on its purge function: a plugin that loads after this one would report as
87 - * absent, and the plugins that matter most for the warning are the ones
88 - * that expose no purge function at all.
119 + * absent, and the plugin that matters most for the warning is the one that
120 + * exposes no purge API at all.
89 121 *
122 + * Every entry here was read from the plugin's own source at the version
123 + * named, not from its documentation. Five of them were marked unpurgeable
124 + * on the strength of the documentation alone and were wrong (#883).
125 + *
90 126 * @since 2.12.0
91 - * @return array<string, array{detect: callable, purgeable: bool}>
127 + * @return array<string, array{detect: callable, purge: string}>
92 128 */
93 129 private static function known_caches(): array {
94 130 return [
95 131 'WP Rocket' => [
96 - 'detect' => static fn(): bool => defined('WP_ROCKET_VERSION'),
97 - 'purgeable' => true,
132 + 'detect' => static fn(): bool => defined('WP_ROCKET_VERSION'),
133 + 'purge' => self::PURGE_URL,
98 134 ],
99 135 'W3 Total Cache' => [
100 - 'detect' => static fn(): bool => defined('W3TC'),
101 - 'purgeable' => true,
136 + 'detect' => static fn(): bool => defined('W3TC'),
137 + 'purge' => self::PURGE_URL,
102 138 ],
103 139 'WP Super Cache' => [
104 - 'detect' => static fn(): bool => defined('WPCACHEHOME'),
105 - 'purgeable' => true,
140 + 'detect' => static fn(): bool => defined('WPCACHEHOME'),
141 + 'purge' => self::PURGE_URL,
106 142 ],
107 143 'LiteSpeed Cache' => [
108 - 'detect' => static fn(): bool => defined('LSCWP_V'),
109 - 'purgeable' => true,
144 + 'detect' => static fn(): bool => defined('LSCWP_V'),
145 + 'purge' => self::PURGE_URL,
110 146 ],
111 147 'Nginx Helper' => [
112 - 'detect' => static fn(): bool => class_exists('Nginx_Helper'),
113 - 'purgeable' => true,
148 + 'detect' => static fn(): bool => class_exists('Nginx_Helper'),
149 + 'purge' => self::PURGE_URL,
114 150 ],
115 - // Everything below is detected but not purgeable from here. Each
116 - // one either exposes no per-URL API or needs credentials ThinkRank
117 - // does not hold, so the honest answer is a warning to the caller.
118 - 'WP Fastest Cache' => [
119 - 'detect' => static fn(): bool => defined('WPFC_MAIN_PATH') || class_exists('WpFastestCache'),
120 - 'purgeable' => false,
151 + // Cache Enabler 1.8.17 registers both of its clear hooks in
152 + // Cache_Enabler::init(): `cache_enabler_clear_page_cache_by_url`
153 + // runs clear_page_cache_by_url(). The hook is the published API and
154 + // survives the 1.8.0 deprecation of clear_page_cache_by_post_id(),
155 + // so this integrates against the hook rather than the class.
156 + 'Cache Enabler' => [
157 + 'detect' => static fn(): bool => class_exists('Cache_Enabler'),
158 + 'purge' => self::PURGE_URL,
121 159 ],
160 + // SiteGround Optimizer 7.8.3 exposes
161 + // Supercacher::purge_cache_request($url) as a public static. It has
162 + // no published hook, so the method is called directly, guarded.
122 163 'SiteGround Optimizer' => [
123 - 'detect' => static fn(): bool => class_exists('SiteGround_Optimizer\\Supercacher\\Supercacher'),
124 - 'purgeable' => false,
164 + 'detect' => static fn(): bool => class_exists('SiteGround_Optimizer\\Supercacher\\Supercacher'),
165 + 'purge' => self::PURGE_URL,
125 166 ],
126 - 'Cache Enabler' => [
127 - 'detect' => static fn(): bool => class_exists('Cache_Enabler'),
128 - 'purgeable' => false,
167 + // The three below purge a post, not a URL. See PURGE_POST.
168 + //
169 + // WP Fastest Cache 1.5.2 listens on `wpfc_clear_post_cache_by_id`
170 + // with singleDeleteCache($comment_id, $post_id, $clear_parents),
171 + // so the post ID is the second argument and the first is unused.
172 + 'WP Fastest Cache' => [
173 + 'detect' => static fn(): bool => defined('WPFC_MAIN_PATH') || class_exists('WpFastestCache'),
174 + 'purge' => self::PURGE_POST,
129 175 ],
176 + // Hummingbird 3.21.2 listens on `wphb_clear_page_cache` with
177 + // clear_cache_action($post_id), which purges that post when given
178 + // an ID and the whole cache when given nothing. It is always given
179 + // an ID here.
180 + 'Hummingbird' => [
181 + 'detect' => static fn(): bool => defined('WPHB_VERSION'),
182 + 'purge' => self::PURGE_POST,
183 + ],
184 + // Breeze 2.6.0 listens on `purge_post_cache` with
185 + // purge_post_cache($post_id). The hook name carries no vendor
186 + // prefix, which is Breeze's choice and not something this can fix;
187 + // it is fired with a post ID, which is the only shape Breeze reads.
130 188 'Breeze' => [
131 - 'detect' => static fn(): bool => defined('BREEZE_VERSION'),
132 - 'purgeable' => false,
189 + 'detect' => static fn(): bool => defined('BREEZE_VERSION'),
190 + 'purge' => self::PURGE_POST,
133 191 ],
134 - 'Hummingbird' => [
135 - 'detect' => static fn(): bool => defined('WPHB_VERSION'),
136 - 'purgeable' => false,
137 - ],
192 + // Cloudflare stays unpurgeable, and is the only one that does.
193 + // Its plugin purges on its own post events through
194 + // Hooks::purgeCacheByRelevantURLs(), an internal method with no
195 + // published hook in front of it, so there is nothing to call that
196 + // is not reaching into its internals.
138 197 'Cloudflare' => [
139 - 'detect' => static fn(): bool => class_exists('CF\\WordPress\\Hooks'),
140 - 'purgeable' => false,
198 + 'detect' => static fn(): bool => class_exists('CF\\WordPress\\Hooks'),
199 + 'purge' => self::PURGE_NONE,
141 200 ],
142 201 ];
143 202 }
144 203
@@ -176,8 +235,9 @@
176 235 // still gets purged. The detector decides only what to warn about.
177 236 // These are third-party hook names ThinkRank fires, not ours to prefix.
178 237 foreach ($urls as $url) {
179 238 do_action('litespeed_purge_url', $url); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
239 + do_action('cache_enabler_clear_page_cache_by_url', $url); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
180 240
181 241 if (function_exists('w3tc_flush_url')) {
182 242 w3tc_flush_url($url);
183 243 }
@@ -183,8 +243,15 @@
183 243 }
184 244 if (function_exists('wpsc_delete_url_cache')) {
185 245 wpsc_delete_url_cache($url);
186 246 }
247 + // SiteGround publishes no hook for this, so the static is called
248 + // directly. It returns early on its own when the site is not on
249 + // SiteGround and file caching is off, so calling it unconditionally
250 + // costs nothing on a site that merely has the plugin installed.
251 + if (is_callable(['SiteGround_Optimizer\\Supercacher\\Supercacher', 'purge_cache_request'])) {
252 + \SiteGround_Optimizer\Supercacher\Supercacher::purge_cache_request($url);
253 + }
187 254 }
188 255
189 256 // Nginx Helper only exposes a purge-everything hook, and WP Rocket
190 257 // takes the whole list at once, so both run once per call.
@@ -222,8 +289,18 @@
222 289 delete_post_meta($post_id, self::ELEMENTOR_CACHE_META_KEY);
223 290
224 291 clean_post_cache($post_id);
225 292
293 + // Caches whose narrowest purge is a post, not a URL. Fired here
294 + // rather than in purge_urls() because this is the only path that
295 + // has a post ID; see PURGE_POST. Each is a no-op when the plugin
296 + // that listens for it is absent.
297 + // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- third-party hook names ThinkRank fires, not ours to prefix.
298 + do_action('wpfc_clear_post_cache_by_id', false, $post_id);
299 + do_action('wphb_clear_page_cache', $post_id);
300 + do_action('purge_post_cache', $post_id);
301 + // phpcs:enable
302 +
226 303 $permalink = get_permalink($post_id);
227 304 if (is_string($permalink) && '' !== $permalink) {
228 305 $urls[] = $permalink;
229 306 }
@@ -239,15 +316,35 @@
239 316 /**
240 317 * Caching layers that are installed but cannot be purged from here.
241 318 *
242 319 * @since 2.12.0
320 + * @since 2.14.0 Takes the kind of purge being reported on.
321 + *
322 + * @param string $target What the caller purged: self::PURGE_POST when it
323 + * had a post ID (the default, and what every caller
324 + * in the plugin passes today), or self::PURGE_URL for
325 + * a URL with no post behind it, such as llms.txt. A
326 + * post-granularity cache is unreachable in the second
327 + * case and is listed there and not in the first.
243 328 * @return string[] Plugin names, in the order they are declared.
244 329 */
245 - public static function unpurgeable_caches(): array {
330 + public static function unpurgeable_caches(string $target = self::PURGE_POST): array {
246 331 $names = [];
247 332
248 333 foreach (self::known_caches() as $name => $cache) {
249 - if (!$cache['purgeable'] && ($cache['detect'])()) {
334 + $purge = $cache['purge'];
335 +
336 + if (self::PURGE_URL === $purge) {
337 + continue;
338 + }
339 +
340 + // A per-post cache is purged on the post path and missed on the
341 + // URL path, so only the URL path warns about it.
342 + if (self::PURGE_POST === $purge && self::PURGE_POST === $target) {
343 + continue;
344 + }
345 +
346 + if (($cache['detect'])()) {
250 347 $names[] = $name;
251 348 }
252 349 }
253 350
@@ -262,19 +359,26 @@
262 359 *
263 360 * @since 2.12.0
264 361 * @return string[] Empty when everything detected was purged.
265 362 */
266 - public static function warnings(): array {
267 - $unpurgeable = self::unpurgeable_caches();
363 + public static function warnings(string $target = self::PURGE_POST): array {
364 + $unpurgeable = self::unpurgeable_caches($target);
268 365
269 366 if ([] === $unpurgeable) {
270 367 return [];
271 368 }
272 369
370 + // Two plugins read "A, B is active and offers" in the single-string
371 + // version this replaces.
273 372 return [
274 373 sprintf(
275 374 /* translators: %s: comma-separated list of caching plugin names. */
276 - __('%s is active and offers no way to clear a single page from here. Clear its cache to see the change on the front end.', 'thinkrank'),
375 + _n(
376 + '%s is active and offers no way to clear a single page from here. Clear its cache to see the change on the front end.',
377 + '%s are active and offer no way to clear a single page from here. Clear their caches to see the change on the front end.',
378 + count($unpurgeable),
379 + 'thinkrank'
380 + ),
277 381 implode(', ', $unpurgeable)
278 382 ),
279 383 ];
280 384 }