Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
4.9 KiB
PHP
149 lines
4.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Integrations\Divi\Release;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* The release state of every Divi feature (E1). A feature the constructor did not get is incompatible (fail
|
||
* closed), so of() always answers. The overall state of the matrix is its worst feature state.
|
||
*/
|
||
final class CompatibilityMatrix
|
||
{
|
||
public const MISSING = 'Für diese Fähigkeit liegt kein Prüfergebnis vor.';
|
||
|
||
/** @var array<string, FeatureStatus> keyed by DiviFeature value, in DiviFeature::cases() order */
|
||
private array $features = [];
|
||
|
||
/** @param array<string, FeatureStatus> $features keyed by DiviFeature value */
|
||
public function __construct(array $features)
|
||
{
|
||
foreach (DiviFeature::cases() as $feature) {
|
||
$status = $features[$feature->value] ?? null;
|
||
$this->features[$feature->value] = $status instanceof FeatureStatus && $status->feature === $feature
|
||
? $status
|
||
: new FeatureStatus($feature, ReleaseState::Incompatible, [self::MISSING]);
|
||
}
|
||
}
|
||
|
||
public static function allReleased(string $source = ReleaseStatus::SOURCE_CODE): self
|
||
{
|
||
return self::uniform(ReleaseState::Released, [], $source);
|
||
}
|
||
|
||
public static function allIncompatible(string $problem): self
|
||
{
|
||
return self::uniform(ReleaseState::Incompatible, [$problem]);
|
||
}
|
||
|
||
/**
|
||
* Every feature in $state – the matrix of a status built without one (ReleaseStatus' constructor).
|
||
*
|
||
* @param list<string> $problems
|
||
*/
|
||
public static function uniform(ReleaseState $state, array $problems = [], ?string $source = null): self
|
||
{
|
||
$released = $state === ReleaseState::Released;
|
||
$features = [];
|
||
foreach (DiviFeature::cases() as $feature) {
|
||
$features[$feature->value] = new FeatureStatus(
|
||
$feature,
|
||
$state,
|
||
$released ? [] : $problems,
|
||
$released ? ($source ?? ReleaseStatus::SOURCE_CODE) : null
|
||
);
|
||
}
|
||
|
||
return new self($features);
|
||
}
|
||
|
||
public function of(DiviFeature $feature): FeatureStatus
|
||
{
|
||
return $this->features[$feature->value];
|
||
}
|
||
|
||
/** @param list<DiviFeature> $features */
|
||
public function usable(array $features): bool
|
||
{
|
||
foreach ($features as $feature) {
|
||
if (! $this->of($feature)->usable()) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* The statuses of those of $features that are not usable – each once, in the order of $features. The one answer
|
||
* to "what locks this call" for the gate (DiviFeatureGate), the adapters (ReleaseGate::assertFeatures()) and the
|
||
* refusal (ReleaseStatus::refusalFor()).
|
||
*
|
||
* @param list<DiviFeature> $features
|
||
* @return list<FeatureStatus>
|
||
*/
|
||
public function locked(array $features): array
|
||
{
|
||
$locked = [];
|
||
foreach ($features as $feature) {
|
||
$status = $this->of($feature);
|
||
if (! $status->usable()) {
|
||
$locked[$feature->value] = $status;
|
||
}
|
||
}
|
||
|
||
return array_values($locked);
|
||
}
|
||
|
||
/** @return list<FeatureStatus> */
|
||
public function unusable(): array
|
||
{
|
||
return array_values(array_filter(
|
||
$this->features,
|
||
static fn (FeatureStatus $status): bool => ! $status->usable()
|
||
));
|
||
}
|
||
|
||
/** Incompatible before untested before released. */
|
||
public function state(): ReleaseState
|
||
{
|
||
$states = array_map(static fn (FeatureStatus $status): ReleaseState => $status->state, $this->features);
|
||
|
||
return match (true) {
|
||
in_array(ReleaseState::Incompatible, $states, true) => ReleaseState::Incompatible,
|
||
in_array(ReleaseState::Untested, $states, true) => ReleaseState::Untested,
|
||
default => ReleaseState::Released,
|
||
};
|
||
}
|
||
|
||
/** The common source of a fully released matrix; null when not all released or the sources differ. */
|
||
public function source(): ?string
|
||
{
|
||
$sources = array_unique(array_map(
|
||
static fn (FeatureStatus $status): string => (string) $status->source,
|
||
$this->features
|
||
));
|
||
|
||
return $this->state() === ReleaseState::Released && count($sources) === 1 ? (string) reset($sources) : null;
|
||
}
|
||
|
||
/** @return list<string> Every problem label once, in feature order. */
|
||
public function problems(): array
|
||
{
|
||
$problems = [];
|
||
foreach ($this->features as $status) {
|
||
$problems = array_merge($problems, $status->problems);
|
||
}
|
||
|
||
return array_values(array_unique($problems));
|
||
}
|
||
|
||
/** @return list<array{id: string, label: string, state: string, problems: list<string>, source: ?string}> */
|
||
public function toArray(): array
|
||
{
|
||
return array_values(array_map(static fn (FeatureStatus $status): array => $status->toArray(), $this->features));
|
||
}
|
||
}
|