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
+32 -4
View File
@@ -23,7 +23,8 @@ defined('ABSPATH') || exit;
/**
* Tab "Divi" (spec §6): release status of the installed Divi, the latest check report, "Prüfung starten" (REST,
* DiviCheckRoute), "Divi <Version> freigeben" (only after a fresh green report) and "Freigabe zurückziehen" (only
* for a database release). Opening the tab removes check pages a dead check left behind.
* for a database release). Loading the tab removes check pages a dead check left behind – in the page's load hook
* (removeOrphans()), not while rendering.
*/
final class DiviTab implements AdminTab
{
@@ -31,6 +32,9 @@ final class DiviTab implements AdminTab
public const RELEASE_ACTION = 'lit_mcp_divi_release';
public const WITHDRAW_ACTION = 'lit_mcp_divi_withdraw';
/** @var list<int> Check pages removeOrphans() deleted in this request; render() reports them. */
private array $removed = [];
/**
* @param (Closure(): string)|null $version Installed Divi version ('' = none); null = DiviEnvironment.
* @param (Closure(string): string)|null $stateKey null = ReleaseResolver::stateKey().
@@ -63,6 +67,22 @@ final class DiviTab implements AdminTab
{
add_action('admin_post_' . self::RELEASE_ACTION, [$this, 'release']);
add_action('admin_post_' . self::WITHDRAW_ACTION, [$this, 'withdraw']);
add_action('load-' . SettingsPage::SCREEN_HOOK, [$this, 'removeOrphans']);
}
/**
* load-<page> of the settings page, before any output: on this tab, for manage_options and with Divi active,
* deletes the check pages a dead check left behind (WriteProbe::removeOrphans(): marked drafts with the check
* title, older than ten minutes – no running check uses them). On a GET without nonce on purpose: it removes
* only the plugin's own leftovers and is idempotent. render() only reports what was deleted.
*/
public function removeOrphans(): void
{
if (! $this->requested() || ! current_user_can('manage_options') || $this->version() === '') {
return;
}
$this->removed = WriteProbe::removeOrphans();
}
public function release(): void
@@ -127,7 +147,6 @@ final class DiviTab implements AdminTab
return;
}
$removed = WriteProbe::removeOrphans();
$status = ReleaseGate::status();
$report = $this->reports()->latest();
@@ -137,13 +156,13 @@ final class DiviTab implements AdminTab
'LIT MCP schreibt nur mit Divi-Versionen, die geprüft sind. Nach einem Divi-Update hier die Prüfung starten und die Version freigeben; bis dahin lesen die Divi-Werkzeuge nur.',
'wp-lit-mcp'
) . '</p>';
if ($removed !== []) {
if ($this->removed !== []) {
printf(
'<div class="notice notice-warning inline"><p>%s</p></div>',
esc_html(sprintf(
/* translators: %s: comma separated page IDs */
__('Liegengebliebene Prüfseiten gelöscht: %s', 'wp-lit-mcp'),
implode(', ', $removed)
implode(', ', $this->removed)
))
);
}
@@ -305,6 +324,15 @@ final class DiviTab implements AdminTab
) . '</p>';
}
/** Whether this request opens this tab of the settings page (the load hook fires for every tab). */
private function requested(): bool
{
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab switch.
$tab = isset($_GET['tab']) ? sanitize_key(wp_unslash((string) $_GET['tab'])) : '';
return $tab === self::SLUG;
}
private function version(): string
{
return ($this->version ?? static fn (): string => DiviEnvironment::version() ?? '')();