refactor: integration labels, AdminPostAction, ExposurePolicy key parsing (M9)
Integrations name their own admin group (Integration::label, registry labels()); ToolsTab no longer hard-codes ids and explains why it re-applies withGroups. AccessTab and LogTab share AdminPostAction (guard, redirect, terminate seam). ExposurePolicy parses option keys in one place, has one isRequired() check and a clear $groups docblock (null vs []). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
+4
-11
@@ -15,17 +15,14 @@ final class AccessTab implements AdminTab
|
||||
public const GROUP = 'lit_mcp_access';
|
||||
public const REVOKE_ACTION = 'lit_mcp_revoke';
|
||||
|
||||
/** @var callable(): void */
|
||||
private $terminate;
|
||||
private AdminPostAction $action;
|
||||
|
||||
public function __construct(
|
||||
private Access $access,
|
||||
private ConnectionRepository $connections,
|
||||
?callable $terminate = null
|
||||
) {
|
||||
$this->terminate = $terminate ?? static function (): void {
|
||||
exit;
|
||||
};
|
||||
$this->action = new AdminPostAction($terminate);
|
||||
}
|
||||
|
||||
public function slug(): string
|
||||
@@ -121,9 +118,7 @@ final class AccessTab implements AdminTab
|
||||
|
||||
public function handleRevoke(): void
|
||||
{
|
||||
if (! current_user_can('manage_options')) {
|
||||
wp_die(esc_html__('Keine Berechtigung.', 'wp-lit-mcp'), '', ['response' => 403]);
|
||||
}
|
||||
$this->action->guard();
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- verified right below.
|
||||
$uuid = isset($_POST['uuid']) && is_string($_POST['uuid'])
|
||||
@@ -134,9 +129,7 @@ final class AccessTab implements AdminTab
|
||||
check_admin_referer(self::REVOKE_ACTION . '_' . $uuid);
|
||||
|
||||
$revoked = $this->connections->revoke($userId, $uuid);
|
||||
$notice = $revoked ? 'revoked' : 'revoke_failed';
|
||||
wp_safe_redirect(SettingsPage::url($this->slug(), ['lit_mcp_notice' => $notice]));
|
||||
($this->terminate)();
|
||||
$this->action->redirect($this->slug(), $revoked ? 'revoked' : 'revoke_failed');
|
||||
}
|
||||
|
||||
private static function warnOnce(string $code, string $message): void
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Admin;
|
||||
|
||||
/**
|
||||
* The frame of an admin-post.php handler of the settings page: only administrators
|
||||
* (manage_options, else 403), and after the action a redirect back to the tab with a notice.
|
||||
* The terminate seam replaces exit in tests.
|
||||
*/
|
||||
final class AdminPostAction
|
||||
{
|
||||
/** @var callable(): void */
|
||||
private $terminate;
|
||||
|
||||
/** @param (callable(): void)|null $terminate Called after the redirect; default exit. */
|
||||
public function __construct(?callable $terminate = null)
|
||||
{
|
||||
$this->terminate = $terminate ?? static function (): void {
|
||||
exit;
|
||||
};
|
||||
}
|
||||
|
||||
/** Stops with 403 unless the current user may manage options. */
|
||||
public function guard(): void
|
||||
{
|
||||
if (! current_user_can('manage_options')) {
|
||||
wp_die(esc_html__('Keine Berechtigung.', 'wp-lit-mcp'), '', ['response' => 403]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Redirects to settings tab $slug with "lit_mcp_notice" = $notice and ends the request. */
|
||||
public function redirect(string $slug, string $notice): void
|
||||
{
|
||||
wp_safe_redirect(SettingsPage::url($slug, ['lit_mcp_notice' => $notice]));
|
||||
($this->terminate)();
|
||||
}
|
||||
}
|
||||
+4
-11
@@ -16,14 +16,11 @@ final class LogTab implements AdminTab
|
||||
public const CLEAR_ACTION = 'lit_mcp_clear_log';
|
||||
private const RESULTS = ['ok', 'partial'];
|
||||
|
||||
/** @var callable(): void */
|
||||
private $terminate;
|
||||
private AdminPostAction $action;
|
||||
|
||||
public function __construct(private ChangeLog $log, private Limits $limits, ?callable $terminate = null)
|
||||
{
|
||||
$this->terminate = $terminate ?? static function (): void {
|
||||
exit;
|
||||
};
|
||||
$this->action = new AdminPostAction($terminate);
|
||||
}
|
||||
|
||||
public function slug(): string
|
||||
@@ -210,13 +207,9 @@ final class LogTab implements AdminTab
|
||||
|
||||
public function handleClear(): void
|
||||
{
|
||||
if (! current_user_can('manage_options')) {
|
||||
wp_die(esc_html__('Keine Berechtigung.', 'wp-lit-mcp'), '', ['response' => 403]);
|
||||
}
|
||||
|
||||
$this->action->guard();
|
||||
check_admin_referer(self::CLEAR_ACTION);
|
||||
$this->log->clear();
|
||||
wp_safe_redirect(SettingsPage::url($this->slug(), ['lit_mcp_notice' => 'cleared']));
|
||||
($this->terminate)();
|
||||
$this->action->redirect($this->slug(), 'cleared');
|
||||
}
|
||||
}
|
||||
|
||||
+12
-16
@@ -68,8 +68,12 @@ final class ToolsTab implements AdminTab
|
||||
|
||||
public function render(): void
|
||||
{
|
||||
// Bind the policy to the groups of exactly the abilities listed below. The plugin's policy
|
||||
// carries a lazy map of the same registry (resolved here anyway); a policy built without
|
||||
// withGroups() would otherwise ignore every group mode and "nur lesen" switch on this page.
|
||||
$policy = $this->policy->withGroups($this->registry->groupMap());
|
||||
$groups = $this->registry->grouped();
|
||||
$labels = $this->registry->labels();
|
||||
|
||||
$this->renderIntro(isset($groups['divi']));
|
||||
$this->renderCounter(count($policy->directNames($this->registry->abilities())));
|
||||
@@ -84,7 +88,7 @@ final class ToolsTab implements AdminTab
|
||||
echo '<div class="lit-mcp-groups">';
|
||||
$closed = $this->savedClosedBoxes();
|
||||
foreach ($groups as $group => $abilities) {
|
||||
$this->renderGroup($policy, $group, $abilities, $closed);
|
||||
$this->renderGroup($policy, $group, $labels[$group] ?? $group, $abilities, $closed);
|
||||
}
|
||||
echo '</div>';
|
||||
submit_button();
|
||||
@@ -158,10 +162,14 @@ final class ToolsTab implements AdminTab
|
||||
* @param list<Ability> $abilities
|
||||
* @param list<string>|null $closedBoxes
|
||||
*/
|
||||
private function renderGroup(ExposurePolicy $policy, string $group, array $abilities, ?array $closedBoxes): void
|
||||
{
|
||||
private function renderGroup(
|
||||
ExposurePolicy $policy,
|
||||
string $group,
|
||||
string $label,
|
||||
array $abilities,
|
||||
?array $closedBoxes
|
||||
): void {
|
||||
$count = count($abilities);
|
||||
$label = $this->groupLabel($group);
|
||||
$boxId = 'lit-mcp-group-' . self::idPart($group);
|
||||
$modes = [ExposurePolicy::DIRECT => 0, ExposurePolicy::INDIRECT => 0, ExposurePolicy::DISABLED => 0];
|
||||
$mixed = false;
|
||||
@@ -374,18 +382,6 @@ final class ToolsTab implements AdminTab
|
||||
return trim((string) preg_replace('/[^a-z0-9]+/', '-', strtolower($value)), '-');
|
||||
}
|
||||
|
||||
private function groupLabel(string $group): string
|
||||
{
|
||||
return match ($group) {
|
||||
'core' => __('Grundlagen', 'wp-lit-mcp'),
|
||||
'wordpress' => __('WordPress', 'wp-lit-mcp'),
|
||||
'media' => __('Medien', 'wp-lit-mcp'),
|
||||
'pods' => __('Pods', 'wp-lit-mcp'),
|
||||
'divi' => __('Divi', 'wp-lit-mcp'),
|
||||
default => $group,
|
||||
};
|
||||
}
|
||||
|
||||
private function modeLabel(string $mode): string
|
||||
{
|
||||
return match ($mode) {
|
||||
|
||||
Reference in New Issue
Block a user