| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* Per-request state store for regex redirect lookup caching. |
| 9 |
* |
| 10 |
* Redirect matching and redirect mutation both need to clear or inspect this |
| 11 |
* cache, so the state lives in one focused owner instead of on the broad |
| 12 |
* RedirectsRepository facade. |
| 13 |
*/ |
| 14 |
class ABJ_404_Solution_RedirectRegexCacheStore { |
| 15 |
|
| 16 |
/** Maximum number of regex redirects to cache per request. */ |
| 17 |
const MAX_COUNT = 50; |
| 18 |
|
| 19 |
/** @var array<int, array<string, mixed>>|null */ |
| 20 |
private static $regexRedirectsCache = null; |
| 21 |
|
| 22 |
/** @var bool */ |
| 23 |
private static $regexCacheDisabled = false; |
| 24 |
|
| 25 |
/** |
| 26 |
* Blog id the cache above (or the disabled flag) was populated for. This |
| 27 |
* is a CLASS-STATIC cache -- shared by every RedirectsBulkReader / |
| 28 |
* RedirectsRepository instance in the process, not just one object -- |
| 29 |
* memoizing rows read from the per-blog {wp_prefix}_abj404_redirects |
| 30 |
* table. The underlying query is correctly blog-scoped ($wpdb->prefix |
| 31 |
* follows switch_to_blog()), but without this guard the cache sitting in |
| 32 |
* front of it is not: a multisite process that switches blogs mid-request |
| 33 |
* (a WP-CLI network-wide script looping get_sites()/switch_to_blog(), or |
| 34 |
* an Action Scheduler worker draining queued cross-site actions |
| 35 |
* sequentially) would resolve blog A's regex redirects once, cache them |
| 36 |
* process-wide, then silently keep serving blog A's rows to blog B (and |
| 37 |
* every blog after it) for the rest of the process. Every real caller of |
| 38 |
* getRedirectsWithRegEx() (WPCLIRedirectCommandService, RestApiReadService, |
| 39 |
* ViewReadService) inherits this guard automatically since they never |
| 40 |
* touch the cache fields directly. |
| 41 |
* |
| 42 |
* @var int|null |
| 43 |
*/ |
| 44 |
private static $regexRedirectsCacheBlogId = null; |
| 45 |
|
| 46 |
public function clear(): void { |
| 47 |
self::clearStatic(); |
| 48 |
} |
| 49 |
|
| 50 |
public static function clearStatic(): void { |
| 51 |
self::$regexRedirectsCache = null; |
| 52 |
self::$regexCacheDisabled = false; |
| 53 |
self::$regexRedirectsCacheBlogId = null; |
| 54 |
} |
| 55 |
|
| 56 |
/** @return array<int, array<string, mixed>>|null */ |
| 57 |
public static function getRegexRedirectsCache() { |
| 58 |
self::invalidateIfBlogChanged(); |
| 59 |
return self::$regexRedirectsCache; |
| 60 |
} |
| 61 |
|
| 62 |
/** @param array<int, array<string, mixed>>|null $cache */ |
| 63 |
public static function setRegexRedirectsCache($cache): void { |
| 64 |
self::$regexRedirectsCache = $cache; |
| 65 |
self::$regexRedirectsCacheBlogId = self::currentBlogId(); |
| 66 |
} |
| 67 |
|
| 68 |
public static function isRegexCacheDisabled(): bool { |
| 69 |
self::invalidateIfBlogChanged(); |
| 70 |
return self::$regexCacheDisabled; |
| 71 |
} |
| 72 |
|
| 73 |
public static function setRegexCacheDisabled(bool $disabled): void { |
| 74 |
self::$regexCacheDisabled = $disabled; |
| 75 |
self::$regexRedirectsCacheBlogId = self::currentBlogId(); |
| 76 |
} |
| 77 |
|
| 78 |
/** |
| 79 |
* Drop the cache (and the disabled flag) if the active WordPress blog |
| 80 |
* has changed since the cache was populated. A no-op when nothing has |
| 81 |
* been cached yet, so an uninitialized cache never records a blog id. |
| 82 |
* |
| 83 |
* @return void |
| 84 |
*/ |
| 85 |
private static function invalidateIfBlogChanged(): void { |
| 86 |
if (self::$regexRedirectsCache === null && self::$regexCacheDisabled === false) { |
| 87 |
return; |
| 88 |
} |
| 89 |
|
| 90 |
if (self::$regexRedirectsCacheBlogId !== self::currentBlogId()) { |
| 91 |
self::$regexRedirectsCache = null; |
| 92 |
self::$regexCacheDisabled = false; |
| 93 |
self::$regexRedirectsCacheBlogId = null; |
| 94 |
} |
| 95 |
} |
| 96 |
|
| 97 |
/** @return int */ |
| 98 |
private static function currentBlogId(): int { |
| 99 |
return function_exists('get_current_blog_id') ? (int)get_current_blog_id() : 0; |
| 100 |
} |
| 101 |
} |
| 102 |
|