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:
co-authored by
Claude Opus 5.5
parent
06148fb8c1
commit
6c59934caa
+32
-4
@@ -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() ?? '')();
|
||||
|
||||
+33
-13
@@ -16,24 +16,27 @@ final class SettingsPage
|
||||
/** Hook suffix WordPress gives this page (add_options_page → "settings_page_<slug>"). */
|
||||
public const SCREEN_HOOK = 'settings_page_' . self::SLUG;
|
||||
|
||||
/** @var array<string, AdminTab> */
|
||||
private array $tabs = [];
|
||||
/**
|
||||
* Filter on the tab list (list<AdminTab>): integrations append their own tabs in Bootable::boot() – Divi its
|
||||
* tab "Divi". Applied once, on first use of the tabs (after Plugin booted the integrations).
|
||||
*/
|
||||
public const TABS_FILTER = 'lit_mcp_admin_tabs';
|
||||
|
||||
/** @var array<string, AdminTab>|null null = TABS_FILTER not applied yet. */
|
||||
private ?array $tabs = null;
|
||||
|
||||
/**
|
||||
* @param list<AdminTab> $tabs The first tab is the default.
|
||||
* @param list<AdminTab> $baseTabs The plugin's own tabs; the first tab is the default.
|
||||
* @param string $logoUrl Loosen-IT logo shown top right of the page ('' = none).
|
||||
* @param string $assetsUrl URL of the plugin's assets/ directory ('' = no own CSS/JS).
|
||||
* @param string $version Asset version for cache busting.
|
||||
*/
|
||||
public function __construct(
|
||||
array $tabs,
|
||||
private array $baseTabs,
|
||||
private string $logoUrl = '',
|
||||
private string $assetsUrl = '',
|
||||
private string $version = ''
|
||||
) {
|
||||
foreach ($tabs as $tab) {
|
||||
$this->tabs[$tab->slug()] = $tab;
|
||||
}
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
@@ -41,7 +44,7 @@ final class SettingsPage
|
||||
add_action('admin_menu', [$this, 'addMenu']);
|
||||
add_action('admin_init', [$this, 'registerSettings']);
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']);
|
||||
foreach ($this->tabs as $tab) {
|
||||
foreach ($this->tabs() as $tab) {
|
||||
$tab->registerActions();
|
||||
}
|
||||
}
|
||||
@@ -59,7 +62,7 @@ final class SettingsPage
|
||||
|
||||
public function registerSettings(): void
|
||||
{
|
||||
foreach ($this->tabs as $tab) {
|
||||
foreach ($this->tabs() as $tab) {
|
||||
$tab->registerSettings();
|
||||
}
|
||||
}
|
||||
@@ -90,7 +93,7 @@ final class SettingsPage
|
||||
/** @return list<string> */
|
||||
public function tabSlugs(): array
|
||||
{
|
||||
return array_keys($this->tabs);
|
||||
return array_keys($this->tabs());
|
||||
}
|
||||
|
||||
public function currentTab(): string
|
||||
@@ -98,7 +101,9 @@ final class SettingsPage
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab switch.
|
||||
$requested = isset($_GET['tab']) ? sanitize_key(wp_unslash((string) $_GET['tab'])) : '';
|
||||
|
||||
return isset($this->tabs[$requested]) ? $requested : (string) array_key_first($this->tabs);
|
||||
$tabs = $this->tabs();
|
||||
|
||||
return isset($tabs[$requested]) ? $requested : (string) array_key_first($tabs);
|
||||
}
|
||||
|
||||
/** @param array<string, string> $args */
|
||||
@@ -123,7 +128,7 @@ final class SettingsPage
|
||||
echo '</div><hr class="wp-header-end">';
|
||||
$this->renderNotice();
|
||||
printf('<nav class="nav-tab-wrapper wp-clearfix" aria-label="%s">', esc_attr__('Bereiche', 'wp-lit-mcp'));
|
||||
foreach ($this->tabs as $slug => $tab) {
|
||||
foreach ($this->tabs() as $slug => $tab) {
|
||||
printf(
|
||||
'<a href="%s" class="nav-tab%s"%s>%s</a>',
|
||||
esc_url(self::url($slug)),
|
||||
@@ -133,10 +138,25 @@ final class SettingsPage
|
||||
);
|
||||
}
|
||||
echo '</nav>';
|
||||
$this->tabs[$current]->render();
|
||||
$this->tabs()[$current]->render();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/** @return array<string, AdminTab> The plugin's tabs and those the integrations add (TABS_FILTER), by slug. */
|
||||
private function tabs(): array
|
||||
{
|
||||
if ($this->tabs === null) {
|
||||
$this->tabs = [];
|
||||
foreach ((array) apply_filters(self::TABS_FILTER, $this->baseTabs) as $tab) {
|
||||
if ($tab instanceof AdminTab) {
|
||||
$this->tabs[$tab->slug()] = $tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->tabs;
|
||||
}
|
||||
|
||||
private function renderLogo(): void
|
||||
{
|
||||
if ($this->logoUrl === '') {
|
||||
|
||||
Reference in New Issue
Block a user