Neu: Tier, Profile, Compatibility, CapabilityLocked, CapabilityGate, CompatibilityComponent, ProvidesCompatibility, DiviProfile sowie PodsFeature, PodsCompatibility und PodsIntegration. Das CapabilityGate sperrt gesperrte und unbekannte Fähigkeits-IDs (übernimmt die Sperre unbekannter IDs vom DiviFeatureGate) und delegiert nur Divi ausdrücklich. Pods-Schema, -Lesen, -Schreiben und -Feldrechte laufen über usable(PodsFeature::…); Schreiben verlangt FieldRead und FieldWrite, damit ein Rollback keine Werte verliert. lit-site-status meldet Pods über die CompatibilityComponent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
158 lines
4.5 KiB
PHP
158 lines
4.5 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Integrations;
|
||
|
||
use LIT\MCP\Abilities\Ability;
|
||
use LIT\MCP\Compatibility\Compatibility;
|
||
use LIT\MCP\Compatibility\Profile;
|
||
use LIT\MCP\Compatibility\ProvidesCompatibility;
|
||
use LogicException;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
final class IntegrationRegistry
|
||
{
|
||
/** @var list<Integration> */
|
||
private array $integrations;
|
||
|
||
/** @var list<array{Integration, list<Ability>}>|null Available integrations with their abilities. */
|
||
private ?array $loaded = null;
|
||
|
||
private bool $booted = false;
|
||
private ?Compatibility $compatibility = null;
|
||
|
||
public function __construct(Integration ...$integrations)
|
||
{
|
||
$this->integrations = array_values($integrations);
|
||
}
|
||
|
||
/**
|
||
* Boots every Bootable integration once, in registry order – available or not: Plugin boots on plugins_loaded,
|
||
* before the theme (Divi) is loaded, so isAvailable() is not known yet (as the hooks were registered before
|
||
* Bootable). A second call does nothing.
|
||
*/
|
||
public function boot(): void
|
||
{
|
||
if ($this->booted) {
|
||
return;
|
||
}
|
||
|
||
$this->booted = true;
|
||
foreach ($this->integrations as $integration) {
|
||
if ($integration instanceof Bootable) {
|
||
$integration->boot();
|
||
}
|
||
}
|
||
}
|
||
|
||
/** @return list<Integration> */
|
||
public function available(): array
|
||
{
|
||
return array_values(array_filter(
|
||
$this->integrations,
|
||
static fn (Integration $integration): bool => $integration->isAvailable()
|
||
));
|
||
}
|
||
|
||
/** @return list<Ability> */
|
||
public function abilities(): array
|
||
{
|
||
$abilities = [];
|
||
foreach ($this->loaded() as [, $own]) {
|
||
array_push($abilities, ...$own);
|
||
}
|
||
|
||
return $abilities;
|
||
}
|
||
|
||
/**
|
||
* Abilities of the available integrations, grouped by integration id (registry order). An integration without
|
||
* abilities (Pods) is no group.
|
||
*
|
||
* @return array<string, list<Ability>>
|
||
*/
|
||
public function grouped(): array
|
||
{
|
||
$groups = [];
|
||
foreach ($this->loaded() as [$integration, $own]) {
|
||
if ($own === []) {
|
||
continue;
|
||
}
|
||
$groups[$integration->id()] = array_merge($groups[$integration->id()] ?? [], $own);
|
||
}
|
||
|
||
return $groups;
|
||
}
|
||
|
||
/** @return array<string, string> Ability name => integration id (the ability's group). */
|
||
public function groupMap(): array
|
||
{
|
||
$map = [];
|
||
foreach ($this->grouped() as $id => $abilities) {
|
||
foreach ($abilities as $ability) {
|
||
$map[$ability->name()] = $id;
|
||
}
|
||
}
|
||
|
||
return $map;
|
||
}
|
||
|
||
/** @return array<string, string> Integration id => label, of the available integrations. */
|
||
public function labels(): array
|
||
{
|
||
$labels = [];
|
||
foreach ($this->loaded() as [$integration]) {
|
||
$labels[$integration->id()] = $integration->label();
|
||
}
|
||
|
||
return $labels;
|
||
}
|
||
|
||
/** @return list<Profile> Profiles of every registered integration that provides some, available or not. */
|
||
public function compatibilities(): array
|
||
{
|
||
$profiles = [];
|
||
foreach ($this->integrations as $integration) {
|
||
if ($integration instanceof ProvidesCompatibility) {
|
||
array_push($profiles, ...$integration->compatibilities());
|
||
}
|
||
}
|
||
|
||
return $profiles;
|
||
}
|
||
|
||
/**
|
||
* The compatibility state of all profiles, one shared instance (its matrices are memoised per request).
|
||
*
|
||
* @throws LogicException When two profiles or capabilities share an ID (a programming error, PluginTest).
|
||
*/
|
||
public function compatibility(): Compatibility
|
||
{
|
||
return $this->compatibility ??= new Compatibility($this->compatibilities());
|
||
}
|
||
|
||
/** @return list<string> Ids of all known integrations, available or not. */
|
||
public function ids(): array
|
||
{
|
||
return array_values(array_unique(array_map(
|
||
static fn (Integration $integration): string => $integration->id(),
|
||
$this->integrations
|
||
)));
|
||
}
|
||
|
||
/** @return list<array{Integration, list<Ability>}> */
|
||
private function loaded(): array
|
||
{
|
||
if ($this->loaded === null) {
|
||
$this->loaded = array_map(
|
||
static fn (Integration $integration): array => [$integration, $integration->abilities()],
|
||
$this->available()
|
||
);
|
||
}
|
||
|
||
return $this->loaded;
|
||
}
|
||
}
|