feat: Divi release status, store, resolver and gate

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 01:46:05 +02:00
co-authored by Claude Opus 5.5
parent 6b93df94c3
commit cf3c5dfe76
14 changed files with 838 additions and 1 deletions
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release;
use Closure;
use LIT\MCP\Log;
use Throwable;
defined('ABSPATH') || exit;
/**
* The release status of this request and the lock every writing Divi path asks (spec §5.5). whileProbing() lets the
* admin check write its throwaway page whatever the status – only that post, only during the call, and only from
* code (there is no parameter or option for it).
*/
final class ReleaseGate
{
private static ?ReleaseStatus $status = null;
/** @var (Closure(): ReleaseStatus)|null */
private static ?Closure $resolver = null;
private static ?int $probe = null;
/** Spec §9: a status that cannot be determined locks writes like an incompatible Divi (and is logged). */
public static function status(): ReleaseStatus
{
if (self::$status !== null) {
return self::$status;
}
try {
self::$status = (self::$resolver ?? static fn (): ReleaseStatus => (new ReleaseResolver())->resolve())();
} catch (Throwable $e) {
Log::failure('divi release status', $e);
self::$status = new ReleaseStatus(
ReleaseState::Incompatible,
'',
null,
['Der Freigabe-Zustand ließ sich nicht ermitteln (Details im PHP-Error-Log).']
);
}
return self::$status;
}
/** @param (Closure(): ReleaseStatus)|null $resolver null = ReleaseResolver; tests set their own. */
public static function useResolver(?Closure $resolver): void
{
self::$resolver = $resolver;
self::$status = null;
self::$probe = null;
}
/** The next status() computes again (after a check, a release or a withdrawal). */
public static function forget(): void
{
self::$status = null;
}
/** @throws DiviLocked */
public static function assertWritable(?int $postId = null): void
{
if (self::$probe !== null && $postId === self::$probe) {
return;
}
$status = self::status();
if (! $status->writable()) {
throw new DiviLocked($status);
}
}
public static function whileProbing(int $postId, callable $write): mixed
{
$previous = self::$probe;
self::$probe = $postId;
try {
return $write();
} finally {
self::$probe = $previous;
}
}
}