feat(cache): Tool lit/cache-clear zum gezielten Leeren des Caches
lit/cache-clear leert den Seiten-Cache von WP Super Cache für die ganze Website (scope all, Recht manage_options) oder einen Beitrag (scope post, Recht edit_post); Probelauf ohne confirm, Ausführen mit confirm: true. Das CapabilityGate sperrt das Tool, wenn WP Super Cache bricht; eine Sperre zur Laufzeit wird als Fehler gemeldet. Listener und Tool teilen die 30-s-Drosselung mit Nachlauf (SuperCachePurger); der Listener ignoriert die Einträge des Tools. Behoben vor dem Commit: die Verdrahtung des Purgers erfasste die Registry zu früh (immer gesperrt), jetzt per Referenz und mit Test über Plugin::integrations(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d3e5d4fac0
commit
1ac347e76f
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\SuperCache;
|
||||
|
||||
use Closure;
|
||||
use LIT\MCP\Log;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* The two purges of WP Super Cache, shared by SuperCacheListener (after every write) and lit/cache-clear: the
|
||||
* capability check, the 30 s throttle of the whole-cache purge with its one trailing purge (TRAILING_HOOK) and the
|
||||
* stamp (PURGED_AT). The state lives in the transient and the cron event, so any number of instances share it.
|
||||
*/
|
||||
final class SuperCachePurger
|
||||
{
|
||||
public const TRAILING_HOOK = 'lit_mcp_wpsc_trailing_purge';
|
||||
/** Transient with the time of the last whole-cache purge (also in Uninstaller). */
|
||||
public const PURGED_AT = 'lit_mcp_wpsc_purged_at';
|
||||
/** Seconds between two whole-cache purges. */
|
||||
private const THROTTLE = 30;
|
||||
|
||||
public const PURGED = 'purged';
|
||||
public const THROTTLED = 'throttled';
|
||||
public const LOCKED = 'locked';
|
||||
|
||||
private Closure $usable;
|
||||
private Closure $cacheEnabled;
|
||||
private Closure $purgePost;
|
||||
private Closure $purgeAll;
|
||||
private bool $warned = false;
|
||||
|
||||
/**
|
||||
* Defaults are the real WPSC functions; closures only for tests.
|
||||
*
|
||||
* @param Closure(SuperCacheFeature): bool $usable Compatibility::usable().
|
||||
* @param (Closure(): bool)|null $cacheEnabled WPSC caching switched on ($cache_enabled).
|
||||
* @param (Closure(int): void)|null $purgePost wp_cache_post_edit($postId).
|
||||
* @param (Closure(int): void)|null $purgeAll wp_cache_clear_cache($blogId), 0 = all sites.
|
||||
*/
|
||||
public function __construct(
|
||||
Closure $usable,
|
||||
?Closure $cacheEnabled = null,
|
||||
?Closure $purgePost = null,
|
||||
?Closure $purgeAll = null
|
||||
) {
|
||||
$this->usable = $usable;
|
||||
$this->cacheEnabled = $cacheEnabled ?? static fn (): bool => ! empty($GLOBALS['cache_enabled']);
|
||||
$this->purgePost = $purgePost ?? static function (int $id): void {
|
||||
wp_cache_post_edit($id);
|
||||
};
|
||||
$this->purgeAll = $purgeAll ?? static function (int $blog): void {
|
||||
wp_cache_clear_cache($blog);
|
||||
};
|
||||
}
|
||||
|
||||
public function cacheEnabled(): bool
|
||||
{
|
||||
return ($this->cacheEnabled)();
|
||||
}
|
||||
|
||||
/** Whether a whole-cache purge now would only be postponed. */
|
||||
public function throttled(): bool
|
||||
{
|
||||
return self::within($this->lastPurge());
|
||||
}
|
||||
|
||||
private static function within(?int $last): bool
|
||||
{
|
||||
return $last !== null && time() - $last < self::THROTTLE;
|
||||
}
|
||||
|
||||
/** Time of the last whole-cache purge; null = none within the lock time. Transients may come back as strings. */
|
||||
private function lastPurge(): ?int
|
||||
{
|
||||
$last = get_transient(self::PURGED_AT);
|
||||
|
||||
return is_numeric($last) ? (int) $last : null;
|
||||
}
|
||||
|
||||
/** wp_cache_post_edit for one post; false = locked. Never throttled. */
|
||||
public function post(int $postId): bool
|
||||
{
|
||||
if (! $this->allowed(SuperCacheFeature::PurgePost)) {
|
||||
return false;
|
||||
}
|
||||
($this->purgePost)($postId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whole cache, at most every THROTTLE seconds. Inside the lock time one trailing purge is scheduled (cron events
|
||||
* are per site: the callback runs on this blog, no blog switch, wp_cache_clear_cache takes the blog ID).
|
||||
*
|
||||
* @return self::PURGED|self::THROTTLED|self::LOCKED
|
||||
*/
|
||||
public function everything(): string
|
||||
{
|
||||
if (! $this->allowed(SuperCacheFeature::PurgeAll)) {
|
||||
return self::LOCKED;
|
||||
}
|
||||
|
||||
$last = $this->lastPurge();
|
||||
if ($last !== null && self::within($last)) {
|
||||
if (wp_next_scheduled(self::TRAILING_HOOK) === false) {
|
||||
wp_schedule_single_event($last + self::THROTTLE, self::TRAILING_HOOK);
|
||||
}
|
||||
|
||||
return self::THROTTLED;
|
||||
}
|
||||
|
||||
// Known and unavoidable on a multisite: WPSC also empties the shared blogs/ meta directory of all sites
|
||||
// in its blog branch (wp-cache-phase2.php, wp_cache_clear_cache).
|
||||
($this->purgeAll)(is_multisite() ? get_current_blog_id() : 0);
|
||||
set_transient(self::PURGED_AT, time(), self::THROTTLE);
|
||||
|
||||
return self::PURGED;
|
||||
}
|
||||
|
||||
private function allowed(SuperCacheFeature $feature): bool
|
||||
{
|
||||
if (($this->usable)($feature)) {
|
||||
return true;
|
||||
}
|
||||
if (! $this->warned) {
|
||||
$this->warned = true;
|
||||
Log::line(sprintf('WP Super Cache not purged: %s not usable (see tab Compatibility)', $feature->id()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user