fix: Admin-UX aus dem Review – Live-Gruppenfeedback, Protokollfilter, Divi-Hinweis, Rollen-Rückfrage

- Tools: „Ganze Gruppe“ und „Nur lesen“ wirken sofort auf die Zeilen
  (Option „wie Gruppe (…)“, gesperrte Selects mit Hinweis), der Server
  bleibt die Wahrheit; Markup trägt die nötigen data-Attribute.
- Protokoll: Filter in eigenem Block über der Tabelle (Core blendet
  .tablenav .actions auf Telefonen aus); „Protokoll leeren“ nur bei
  Einträgen, rechtsbündig, mit Gesamtzahl über ChangeLog::count().
- Divi: Beschreibung unter „Prüfung starten“, Button primär solange
  ungetestet ohne frischen Bericht.
- Zugriff: Rückfrage vor dem Speichern ohne Rolle oder ohne eigene Rolle.
- Verbindung: HTTPS-Hinweis nicht doppelt neben der Statusliste.
- Mobil: breitere Einstellungs-Spalte im Tools-Reiter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-08 16:39:44 +02:00
co-authored by Claude Fable 5.1
parent cc72f9b946
commit 1b73f0652a
17 changed files with 462 additions and 150 deletions
+54 -16
View File
@@ -87,7 +87,12 @@ final class ToolsTab implements AdminTab
echo '<form method="post" action="options.php">';
settings_fields(self::GROUP);
submit_button(null, 'primary', 'submit-top');
echo '<div class="lit-mcp-groups">';
// The admin script mirrors a group's mode onto its rows before saving; it needs the label pattern.
printf(
'<div class="lit-mcp-groups" data-like-group="%s">',
/* translators: %s: mode inherited from the group */
esc_attr__('wie Gruppe (%s)', 'wp-lit-mcp')
);
$closed = $this->savedClosedBoxes();
foreach ($groups as $group => $abilities) {
$this->renderGroup($policy, $group, $labels[$group] ?? $group, $abilities, $closed);
@@ -174,7 +179,12 @@ final class ToolsTab implements AdminTab
? $count > 0 && $modes[ExposurePolicy::DISABLED] === $count
: in_array($boxId, $closedBoxes, true);
printf('<div id="%s" class="postbox lit-mcp-group%s">', esc_attr($boxId), $closed ? ' closed' : '');
printf(
'<div id="%s" class="postbox lit-mcp-group%s" data-group="%s">',
esc_attr($boxId),
$closed ? ' closed' : '',
esc_attr($group)
);
echo '<div class="postbox-header">';
printf(
'<h2 class="hndle">%s <span class="count">(%s)</span> <span class="lit-mcp-group-summary">%s</span></h2>',
@@ -236,7 +246,8 @@ final class ToolsTab implements AdminTab
echo '<div class="lit-mcp-group-controls">';
printf(
'<span class="lit-mcp-control"><label for="%1$s">%2$s</label> <select name="%3$s[%4$s]" id="%1$s">',
'<span class="lit-mcp-control"><label for="%1$s">%2$s</label>'
. ' <select name="%3$s[%4$s]" class="lit-mcp-group-mode" id="%1$s">',
esc_attr($selectId),
esc_html__('Ganze Gruppe', 'wp-lit-mcp'),
esc_attr(ExposurePolicy::OPTION),
@@ -257,7 +268,8 @@ final class ToolsTab implements AdminTab
}
echo '</select></span>';
printf(
'<span class="lit-mcp-control"><label><input type="checkbox" name="%s[%s]" value="1"%s> %s</label></span>',
'<span class="lit-mcp-control"><label>'
. '<input type="checkbox" class="lit-mcp-group-readonly" name="%s[%s]" value="1"%s> %s</label></span>',
esc_attr(ExposurePolicy::OPTION),
esc_attr(ExposurePolicy::READONLY_PREFIX . $group),
checked($policy->isReadonly($group), true, false),
@@ -277,13 +289,16 @@ final class ToolsTab implements AdminTab
$own = $policy->toolOverride($name);
$locked = $policy->disabledByReadonly($ability);
$required = in_array($name, ExposurePolicy::REQUIRED, true);
$writes = ($ability->annotations()['readonly'] ?? false) !== true && ! $required;
$selectId = 'lit-mcp-tool-' . self::idPart($name);
/* translators: %s: default mode */
$defaultLabel = sprintf(__('Standard (%s)', 'wp-lit-mcp'), $this->modeLabel($policy->defaultMode($ability)));
printf(
'<tr><td class="column-tool"><strong>%1$s</strong><br><span class="lit-mcp-tool-name">%2$s</span></td>'
. '<td class="column-kind">%3$s</td><td class="column-mode">'
. '<label class="screen-reader-text" for="%4$s">%5$s</label>'
. '<select name="%6$s[%7$s]"%8$s id="%4$s">',
. '<select name="%6$s[%7$s]" class="lit-mcp-tool-mode" data-default-label="%9$s"%10$s%8$s id="%4$s">',
esc_html($ability->label()),
esc_html($name),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in kind().
@@ -296,7 +311,10 @@ final class ToolsTab implements AdminTab
)),
esc_attr(ExposurePolicy::OPTION),
esc_attr($name),
$locked ? ' disabled' : ''
$locked ? ' disabled' : '',
esc_attr($defaultLabel),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed attribute names only.
self::scriptFlags($name, $writes, $required)
);
$inherited = $this->modeLabel($policy->inheritedMode($ability));
printf(
@@ -305,8 +323,7 @@ final class ToolsTab implements AdminTab
esc_html($groupOverride && ! $required
/* translators: %s: mode inherited from the group */
? sprintf(__('wie Gruppe (%s)', 'wp-lit-mcp'), $inherited)
/* translators: %s: default mode */
: sprintf(__('Standard (%s)', 'wp-lit-mcp'), $this->modeLabel($policy->defaultMode($ability))))
: $defaultLabel)
);
foreach (ExposurePolicy::MODES as $mode) {
if (! ExposurePolicy::isAllowed($name, $mode)) {
@@ -331,18 +348,39 @@ final class ToolsTab implements AdminTab
);
}
$note = match (true) {
$locked => __('Durch „Nur lesen“ aus', 'wp-lit-mcp'),
$own === null && $policy->groupModeAdjusted($ability)
=> __('„indirekt“ geht hier nicht – bleibt direkt', 'wp-lit-mcp'),
default => '',
};
if ($note !== '') {
echo '<p class="description">' . esc_html($note) . '</p>';
if ($writes) {
// Always in the row so the admin script can show it as soon as "nur lesen" is switched on.
printf(
'<p class="description lit-mcp-readonly-note"%s>%s</p>',
$locked ? '' : ' hidden',
esc_html__('Durch „Nur lesen“ aus', 'wp-lit-mcp')
);
}
if ($own === null && $policy->groupModeAdjusted($ability)) {
echo '<p class="description">'
. esc_html__('„indirekt“ geht hier nicht – bleibt direkt', 'wp-lit-mcp')
. '</p>';
}
echo '</td></tr>';
}
/** Data attributes telling the admin script how a row reacts to its group's controls. */
private static function scriptFlags(string $name, bool $writes, bool $required): string
{
$flags = '';
if ($writes) {
$flags .= ' data-writes="1"';
}
if ($required) {
$flags .= ' data-required="1"';
}
if (in_array($name, ExposurePolicy::DIRECT_ONLY, true)) {
$flags .= ' data-direct-only="1"';
}
return $flags;
}
/**
* "liest" / "schreibt" / "überschreibt" / "löscht" / "Papierkorb" with a dashicon. The
* "destructive" annotation is a broad hint (deleting, replacing, swapping, ruling S47),