refactor: Divi-Boot-Schritte in DiviIntegration::boot() (Bootable) – Upgrader-Hook, Prüf-Route, CLI und Reiter „Divi“ über lit_mcp_admin_tabs; IntegrationRegistry::boot() bootet jede Integration genau einmal; Prüfseiten-Aufräumen im load-Hook statt beim Rendern

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 21:53:04 +02:00
co-authored by Claude Opus 5.5
parent 06148fb8c1
commit 6c59934caa
10 changed files with 293 additions and 37 deletions
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations;
defined('ABSPATH') || exit;
/**
* An integration with boot steps of its own (hooks, REST routes, WP-CLI commands, admin tabs), so the composition
* root does not need to know them. IntegrationRegistry::boot() calls boot() whether or not the integration is
* available: Plugin boots on plugins_loaded, before the theme – isAvailable() cannot be asked yet.
*/
interface Bootable
{
/** Registers the integration's own hooks, routes, CLI commands and admin tabs. Called once by Plugin. */
public function boot(): void;
}
+30 -2
View File
@@ -5,7 +5,11 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi;
use Closure;
use LIT\MCP\Admin\DiviTab;
use LIT\MCP\Admin\SettingsPage;
use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Cli\DiviCheckCommand;
use LIT\MCP\Integrations\Bootable;
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
@@ -47,6 +51,9 @@ use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
use LIT\MCP\Integrations\Divi\Pages\DiviUpdateAbility;
use LIT\MCP\Integrations\Divi\Pages\DiviWriter;
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
use LIT\MCP\Integrations\Divi\Release\CompatibilityCheck;
use LIT\MCP\Integrations\Divi\Release\DiviCheckRoute;
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
use LIT\MCP\Integrations\Divi\Schema\DiviCatalogAbility;
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
@@ -66,9 +73,9 @@ defined('ABSPATH') || exit;
* Divi 5 pages (spec §5, Etappe 2). Only available when the Divi 5 builder is loaded
* (spec §2 principle 4); Divi 4 is never edited (spec §3.4). The write abilities share one
* DiviWriter; each binds the composition to its entry via DiviWriter::composition().
* contentComposer() gives lit/content-create the Divi short form (spec §6.2).
* contentComposer() gives lit/content-create the Divi short form (spec §6.2). boot() registers Divi's own hooks.
*/
final class DiviIntegration implements Integration
final class DiviIntegration implements Integration, Bootable
{
private DiviModules $modules;
private DesignSystemReader $design;
@@ -190,6 +197,27 @@ final class DiviIntegration implements Integration
);
}
/**
* Divi's own boot steps, registered whether or not Divi is loaded yet (Bootable): a theme update checks the
* fingerprints again at once (spec §5.4), the admin check as REST route and WP-CLI command (spec §6, §7) and
* the tab "Divi" after the plugin's own tabs.
*/
public function boot(): void
{
// Spec §5.4: a theme update checks the Divi fingerprints again at once (the state key would, too).
add_action('upgrader_process_complete', static function (): void {
delete_transient(ReleaseResolver::CACHE);
});
// Spec §6: the admin check runs as a REST request (same context as MCP calls). Its short form has no
// Tools-tab gate: the check tests Divi, not this site's tool switches.
$check = fn (): CompatibilityCheck => CompatibilityCheck::create($this->contentComposer());
(new DiviCheckRoute($check))->register();
DiviCheckCommand::register($check);
add_filter(SettingsPage::TABS_FILTER, fn (array $tabs): array => [...$tabs, new DiviTab(log: $this->log)]);
}
public function abilities(): array
{
$loader = $this->loader();
+21
View File
@@ -16,11 +16,32 @@ final class IntegrationRegistry
/** @var list<array{Integration, list<Ability>}>|null Available integrations with their abilities. */
private ?array $loaded = null;
private bool $booted = false;
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
{