feat: Divi-Adapter sperren je Fähigkeit – ReleaseGate::feature/assertFeature(s), DiviLocked und Ablehnung nennen die betroffenen Fähigkeiten (D6), DiviSaver prüft Seiten schreiben und die Fähigkeit des Speicherbereichs (D5), DesignArea::feature; Test-Standard alle Fähigkeiten freigegeben
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c4efeb3ba4
commit
e387d8d29e
@@ -11,21 +11,28 @@ use WP_Error;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/** A write refused because the installed Divi is not released (spec §5.5); AbilityRegistrar returns toError(). */
|
||||
/**
|
||||
* A Divi call refused because a feature it needs is not released (spec §5.5, D6); AbilityRegistrar returns
|
||||
* toError(). Without features it is the general write lock of the overall status (texts of spec §5.6).
|
||||
*/
|
||||
final class DiviLocked extends RuntimeException implements ToolRefusal
|
||||
{
|
||||
public function __construct(public readonly ReleaseStatus $status)
|
||||
/** @param list<DiviFeature> $features The locked features the refused call needs; [] = writing in general. */
|
||||
public function __construct(public readonly ReleaseStatus $status, public readonly array $features = [])
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
'%s is %s for LIT MCP – write refused',
|
||||
'%s is %s for LIT MCP – %s refused',
|
||||
$status->versionLabel(),
|
||||
$status->state->value
|
||||
$status->state->value,
|
||||
$features === []
|
||||
? 'write'
|
||||
: implode(', ', array_map(static fn (DiviFeature $feature): string => $feature->value, $features))
|
||||
));
|
||||
}
|
||||
|
||||
public function toError(): WP_Error
|
||||
{
|
||||
return $this->status->refusal();
|
||||
return $this->features === [] ? $this->status->refusal() : $this->status->refusalFor($this->features);
|
||||
}
|
||||
|
||||
/** The lock anywhere in $e's chain of previous exceptions (callers wrap failures in WriteFailed). */
|
||||
|
||||
@@ -11,9 +11,9 @@ 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).
|
||||
* The release status of this request and the lock every writing Divi path asks for the feature it needs (spec §5.5,
|
||||
* E1). 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
|
||||
{
|
||||
@@ -57,17 +57,49 @@ final class ReleaseGate
|
||||
self::$status = null;
|
||||
}
|
||||
|
||||
/** @throws DiviLocked */
|
||||
public static function assertWritable(?int $postId = null): void
|
||||
public static function feature(DiviFeature $feature): FeatureStatus
|
||||
{
|
||||
return self::status()->matrix->of($feature);
|
||||
}
|
||||
|
||||
/** @throws DiviLocked When $feature is not usable – before anything is written. */
|
||||
public static function assertFeature(DiviFeature $feature, ?int $postId = null): void
|
||||
{
|
||||
self::assertFeatures([$feature], $postId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every one of $features usable, else DiviLocked with the locked ones. The probe post passes whatever the status.
|
||||
*
|
||||
* @param list<DiviFeature> $features
|
||||
* @throws DiviLocked
|
||||
*/
|
||||
public static function assertFeatures(array $features, ?int $postId = null): void
|
||||
{
|
||||
if (self::$probe !== null && $postId === self::$probe) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = self::status();
|
||||
if (! $status->writable()) {
|
||||
throw new DiviLocked($status);
|
||||
$locked = [];
|
||||
foreach ($features as $feature) {
|
||||
if (! $status->matrix->of($feature)->usable()) {
|
||||
$locked[$feature->value] = $feature;
|
||||
}
|
||||
}
|
||||
if ($locked !== []) {
|
||||
throw new DiviLocked($status, array_values($locked));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every writing feature (CompatibilityMatrix::writeFeatures()) – for callers that know no feature.
|
||||
*
|
||||
* @throws DiviLocked
|
||||
*/
|
||||
public static function assertWritable(?int $postId = null): void
|
||||
{
|
||||
self::assertFeatures(CompatibilityMatrix::writeFeatures(), $postId);
|
||||
}
|
||||
|
||||
public static function whileProbing(int $postId, callable $write): mixed
|
||||
|
||||
@@ -30,6 +30,15 @@ final class ReleaseStatus
|
||||
private const INCOMPATIBLE_HINT = 'Den Nutzer informieren, dass LIT MCP aktualisiert werden muss; lesende '
|
||||
. 'Divi-Werkzeuge funktionieren weiter.';
|
||||
private const NOTICE = 'Achtung: %s ist für LIT MCP %s; Lesen funktioniert, Schreiben ist gesperrt.';
|
||||
private const FEATURE_UNTESTED = 'Achtung: %1$s ist auf dieser Website für %2$s noch nicht für LIT MCP '
|
||||
. 'freigegeben. Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Divi die Prüfung '
|
||||
. 'ausführt und die Version freigibt. Es wurde nichts geändert.';
|
||||
private const FEATURE_UNTESTED_HINT = 'Gesperrt: %s. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre '
|
||||
. 'Fähigkeiten freigegeben sind. Den Nutzer bitten, die Divi-Prüfung auszuführen und die Version freizugeben.';
|
||||
private const FEATURE_INCOMPATIBLE = '%1$s hat Stellen geändert, die LIT MCP für %2$s nutzt. Das ist gesperrt, '
|
||||
. 'bis LIT MCP aktualisiert wurde. Es wurde nichts geändert.';
|
||||
private const FEATURE_INCOMPATIBLE_HINT = 'Gesperrt: %s. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre '
|
||||
. 'Fähigkeiten freigegeben sind. Den Nutzer informieren, dass LIT MCP aktualisiert werden muss.';
|
||||
|
||||
public readonly CompatibilityMatrix $matrix;
|
||||
|
||||
@@ -99,6 +108,56 @@ final class ReleaseStatus
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The error of a call refused because some of $features are locked (D6): divi_incompatible when one of them is
|
||||
* incompatible, else divi_untested; HTTP 423. The message names the features in that state, the hint every
|
||||
* locked one; details.features lists them (id, label, state, problems), details.problems the problems of an
|
||||
* incompatible refusal as before.
|
||||
*
|
||||
* @param list<DiviFeature> $features What the refused call needs.
|
||||
* @throws LogicException When all of them are usable.
|
||||
*/
|
||||
public function refusalFor(array $features): WP_Error
|
||||
{
|
||||
$locked = [];
|
||||
foreach ($features as $feature) {
|
||||
$status = $this->matrix->of($feature);
|
||||
if (! $status->usable()) {
|
||||
$locked[$feature->value] = $status;
|
||||
}
|
||||
}
|
||||
if ($locked === []) {
|
||||
throw new LogicException('usable features refuse nothing');
|
||||
}
|
||||
|
||||
$locked = array_values($locked);
|
||||
$incompatible = array_values(array_filter(
|
||||
$locked,
|
||||
static fn (FeatureStatus $status): bool => $status->state === ReleaseState::Incompatible
|
||||
));
|
||||
$details = ['features' => array_map(static fn (FeatureStatus $status): array => $status->summary(), $locked)];
|
||||
if ($incompatible !== []) {
|
||||
$details['problems'] = array_values(array_unique(array_merge(
|
||||
...array_map(static fn (FeatureStatus $status): array => $status->problems, $locked)
|
||||
)));
|
||||
}
|
||||
|
||||
return Errors::make(
|
||||
$incompatible !== [] ? 'divi_incompatible' : 'divi_untested',
|
||||
sprintf(
|
||||
$incompatible !== [] ? self::FEATURE_INCOMPATIBLE : self::FEATURE_UNTESTED,
|
||||
$this->versionLabel(),
|
||||
self::named($incompatible !== [] ? $incompatible : $locked)
|
||||
),
|
||||
sprintf(
|
||||
$incompatible !== [] ? self::FEATURE_INCOMPATIBLE_HINT : self::FEATURE_UNTESTED_HINT,
|
||||
self::named($locked)
|
||||
),
|
||||
423,
|
||||
$details
|
||||
);
|
||||
}
|
||||
|
||||
/** The warning a reading Divi tool carries while writes are locked. */
|
||||
public function notice(): string
|
||||
{
|
||||
@@ -109,6 +168,22 @@ final class ReleaseStatus
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* "„Seiten schreiben“", "„A“ und „B“", "„A“, „B“ und „C“".
|
||||
*
|
||||
* @param non-empty-list<FeatureStatus> $statuses
|
||||
*/
|
||||
private static function named(array $statuses): string
|
||||
{
|
||||
$labels = array_map(
|
||||
static fn (FeatureStatus $status): string => '„' . $status->feature->label() . '“',
|
||||
$statuses
|
||||
);
|
||||
$last = array_pop($labels);
|
||||
|
||||
return $labels === [] ? $last : implode(', ', $labels) . ' und ' . $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{state: string, version: string, source: ?string, released_at: ?string, released_by: ?int,
|
||||
* problems: list<string>}
|
||||
|
||||
Reference in New Issue
Block a user