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>
136 lines
4.5 KiB
PHP
136 lines
4.5 KiB
PHP
<?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;
|
|
}
|
|
}
|