feat(admin): Prüfroute je Profil und Reiter „Compatibility“

Die generische CheckRoute (POST lit-mcp/v1/<id>-check, für Divi weiter /divi-check) ersetzt DiviCheckRoute: Lock je Profil mit Besitzer-Token, Bericht je Profil, Rücksprung auf den neuen Reiter und Hook lit_mcp_compat_changed (Divi lädt darauf seinen Freigabe-Status neu). Der Reiter „Compatibility“ zeigt je Plugin Zustand (Wort und Icon), Hinweis zur Stufe, Fähigkeiten, getestete und freigegebene Versionen und den letzten Bericht, mit Prüfen, Freigeben und Zurückziehen (Recht manage_options, Allowlist der Profil-ID, Nonce je Profil). Freigeben serverseitig nur mit frischem grünem Bericht. Der Load-Hook lit_mcp_compat_profile_load bereitet den Abbau des DiviTab vor, der bis dahin parallel läuft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-06 13:15:59 +01:00
co-authored by Claude Opus 5.5
parent 7d6a97a54e
commit 85f4138b36
22 changed files with 1940 additions and 334 deletions
+89 -6
View File
@@ -25,7 +25,7 @@ final class SettingsPageTest extends TestCase
protected function tearDown(): void
{
unset($_GET['tab'], $_GET['lit_mcp_notice']);
unset($_GET['tab'], $_GET['lit_mcp_notice'], $_GET['lit_mcp_profile']);
parent::tearDown();
}
@@ -195,9 +195,6 @@ final class SettingsPageTest extends TestCase
public static function diviNotices(): array
{
return [
['divi_checked', 'success', 'Divi-Prüfung abgeschlossen'],
['divi_check_running', 'warning', 'bereits eine Divi-Prüfung'],
['divi_check_failed', 'error', 'PHP-Error-Log'],
['divi_released', 'success', 'für LIT MCP freigegeben'],
['divi_release_refused', 'error', 'Prüfung erneut ausführen'],
['divi_withdrawn', 'success', 'Freigabe zurückgezogen'],
@@ -205,11 +202,96 @@ final class SettingsPageTest extends TestCase
];
}
public function testTheNoticeParameterIsRemovedFromTheAddressBarAfterwards(): void
/** @dataProvider compatNotices */
public function testCompatNoticesNameTheKnownProfile(string $key, string $class, string $text): void
{
Functions\when('current_user_can')->justReturn(true);
$_GET['lit_mcp_notice'] = $key;
$_GET['lit_mcp_profile'] = 'pods';
ob_start();
$this->pageWithProfiles()->render();
$html = (string) ob_get_clean();
self::assertStringContainsString('notice-' . $class, $html);
self::assertStringContainsString('Pods &lt;1&gt;: ', $html);
self::assertStringContainsString($text, $html);
}
/** @return list<list<string>> */
public static function compatNotices(): array
{
return [
['compat_checked', 'success', 'Prüfung abgeschlossen'],
['compat_check_running', 'warning', 'bereits eine Prüfung'],
['compat_check_failed', 'error', 'PHP-Error-Log'],
['compat_released', 'success', 'freigegeben'],
['compat_release_refused', 'error', 'Prüfung erneut ausführen'],
['compat_withdrawn', 'success', 'Freigabe zurückgezogen'],
['compat_withdraw_failed', 'error', 'keine Freigabe'],
];
}
public function testAnUnknownProfileInTheNoticeIsIgnored(): void
{
Functions\when('current_user_can')->justReturn(true);
$_GET['lit_mcp_notice'] = 'compat_checked';
$_GET['lit_mcp_profile'] = '<script>x</script>';
ob_start();
$this->pageWithProfiles()->render();
$html = (string) ob_get_clean();
self::assertStringContainsString('Prüfung abgeschlossen', $html);
self::assertStringNotContainsString('script', $html);
self::assertStringNotContainsString('Pods', $html);
}
/** The checks of all profiles end in compat_* notices; the old Divi-only check notices are gone. */
public function testTheRetiredDiviCheckNoticesShowNothing(): void
{
Functions\when('current_user_can')->justReturn(true);
foreach (['divi_checked', 'divi_check_running', 'divi_check_failed'] as $key) {
$_GET['lit_mcp_notice'] = $key;
ob_start();
$this->pageWithProfiles()->render();
self::assertStringNotContainsString('class="notice', (string) ob_get_clean(), $key);
}
}
public function testArrayValuesInTheQueryAreIgnoredWithoutAWarning(): void
{
Functions\when('current_user_can')->justReturn(true);
$_GET['tab'] = ['x'];
$_GET['lit_mcp_notice'] = ['compat_checked'];
$_GET['lit_mcp_profile'] = ['pods'];
ob_start();
$this->pageWithProfiles()->render();
$html = (string) ob_get_clean();
self::assertStringNotContainsString('class="notice', $html);
$_GET['lit_mcp_notice'] = 'compat_checked';
ob_start();
$this->pageWithProfiles()->render();
self::assertStringNotContainsString('Pods', (string) ob_get_clean());
}
private function pageWithProfiles(): SettingsPage
{
return new SettingsPage(
[$this->tab('verbindung', 'Verbindung')],
profileLabel: static fn (string $id): ?string => $id === 'pods' ? 'Pods <1>' : null
);
}
public function testTheNoticeParametersAreRemovedFromTheAddressBarAfterwards(): void
{
$page = new SettingsPage([$this->tab('verbindung', 'Verbindung')]);
self::assertSame(['a', 'lit_mcp_notice'], $page->removableQueryArgs(['a']));
self::assertSame(['a', 'lit_mcp_notice', 'lit_mcp_profile'], $page->removableQueryArgs(['a']));
}
public function testAddsAnOverviewHelpTab(): void
@@ -221,6 +303,7 @@ final class SettingsPageTest extends TestCase
static fn (array $tab): bool => $tab['id'] === 'lit-mcp-overview'
&& str_contains($tab['content'], 'Tab „Verbindung“')
&& str_contains($tab['content'], 'Tab „Tools“')
&& str_contains($tab['content'], 'Tab „Compatibility“')
));
(new SettingsPage([$this->tab('verbindung', 'Verbindung')]))->helpTabFor($screen);