From 9104989c4387ae8946a6b48c6fade0bb958f8b0a Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sat, 26 Sep 2026 03:56:34 +0200 Subject: [PATCH] fix: neutral hint form for remaining client-facing tool mentions, Limits bounds accessor Fix round 1 (task 10 review): - FieldValidator's "unknown field" validation error (reaches the client via validation_failed's fields) and PostPresenter's DIVI_HINT (surfaced in the result's hints array) still named lit-content-types/lit-content-update as plain lit-; converted both to the neutral "lit/ (direkt: lit-)" form like the other hints from task 10. Re-grepped src/ for any other client-facing error/field/hints text naming a direct-by-default tool as lit- (descriptions/schemas excluded per Q11c) - none left. - Added Limits::bounds() (field => [min, max]) and made LimitsTab use it for the number inputs' min/max instead of a second, hardcoded copy of the same bounds. - LimitsTab now shows a description note when the configured upload_mb exceeds wp_max_upload_size() (PHP's upload_max_filesize/post_max_size), since WordPress would refuse a larger upload before this limit ever applies. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7 --- src/Admin/LimitsTab.php | 34 +++++++++++++++--- .../WordPress/Content/PostPresenter.php | 2 +- .../WordPress/Schema/FieldValidator.php | 3 +- src/Limits.php | 11 ++++++ tests/Unit/Admin/LimitsTabTest.php | 36 +++++++++++++++++++ tests/Unit/LimitsTest.php | 19 ++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) diff --git a/src/Admin/LimitsTab.php b/src/Admin/LimitsTab.php index cdb029f..36a6e1b 100644 --- a/src/Admin/LimitsTab.php +++ b/src/Admin/LimitsTab.php @@ -40,16 +40,18 @@ final class LimitsTab implements AdminTab public function render(): void { $values = (new Limits())->all(); - $fields = [ - 'upload_mb' => [__('Maximale Upload-/Download-Größe (MB)', 'wp-lit-mcp'), 1, 100], - 'log_retention_days' => [__('Protokoll aufbewahren (Tage)', 'wp-lit-mcp'), 1, 3650], - 'snapshot_count' => [__('Anzahl Designsystem-Snapshots (ab Etappe 4)', 'wp-lit-mcp'), 1, 100], + $bounds = Limits::bounds(); + $labels = [ + 'upload_mb' => __('Maximale Upload-/Download-Größe (MB)', 'wp-lit-mcp'), + 'log_retention_days' => __('Protokoll aufbewahren (Tage)', 'wp-lit-mcp'), + 'snapshot_count' => __('Anzahl Designsystem-Snapshots (ab Etappe 4)', 'wp-lit-mcp'), ]; echo '
'; settings_fields(self::GROUP); echo ''; - foreach ($fields as $key => [$label, $min, $max]) { + foreach ($labels as $key => $label) { + [$min, $max] = $bounds[$key]; printf( '
' . '' @@ -63,7 +65,29 @@ final class LimitsTab implements AdminTab ); } echo '
'; + $this->maybeRenderUploadSizeNotice($values['upload_mb']); submit_button(); echo '
'; } + + /** + * PHP's own upload_max_filesize/post_max_size (via wp_max_upload_size()) can be lower than + * what is configured here – WordPress itself would then refuse a large upload/download before + * this limit ever applies. A warning only, since the two are independent knobs. + */ + private function maybeRenderUploadSizeNotice(int $uploadMb): void + { + $serverMaxMb = (int) floor(wp_max_upload_size() / (1024 * 1024)); + if ($serverMaxMb <= 0 || $uploadMb <= $serverMaxMb) { + return; + } + + $message = sprintf( + /* translators: 1: configured limit in MB, 2: server limit in MB */ + __('%1$d MB übersteigt das Server-Limit von %2$d MB (upload_max_filesize/post_max_size).', 'wp-lit-mcp'), + $uploadMb, + $serverMaxMb + ); + printf('

%s

', esc_html($message)); + } } diff --git a/src/Integrations/WordPress/Content/PostPresenter.php b/src/Integrations/WordPress/Content/PostPresenter.php index df50177..f9a3cf5 100644 --- a/src/Integrations/WordPress/Content/PostPresenter.php +++ b/src/Integrations/WordPress/Content/PostPresenter.php @@ -19,7 +19,7 @@ class PostPresenter public const DIVI_HINT = 'Dieser Inhalt ist mit Divi gebaut. Die Divi-Struktur wird über lit-divi-outline ' . 'gelesen und mit den lit-divi-*-Tools bearbeitet; fehlen diese Tools, kann der Divi-Inhalt hier nicht ' . 'bearbeitet werden. content wird deshalb nicht ausgegeben; Titel, Auszug, Felder und Begriffe sind ' - . 'über lit-content-update änderbar.'; + . 'über lit/content-update (direkt: lit-content-update) änderbar.'; public function __construct(private ContentSchema $schema) { diff --git a/src/Integrations/WordPress/Schema/FieldValidator.php b/src/Integrations/WordPress/Schema/FieldValidator.php index 8bcbbcb..7bf3926 100644 --- a/src/Integrations/WordPress/Schema/FieldValidator.php +++ b/src/Integrations/WordPress/Schema/FieldValidator.php @@ -28,7 +28,8 @@ final class FieldValidator $key = (string) $key; $field = $definitions[$key] ?? null; if ($field === null) { - $errors[$key] = 'Unbekanntes Feld; verfügbare Felder über lit-content-types abrufen.'; + $errors[$key] = 'Unbekanntes Feld; verfügbare Felder über lit/content-types (direkt: ' + . 'lit-content-types) abrufen.'; continue; } diff --git a/src/Limits.php b/src/Limits.php index f16b81f..aaedb71 100644 --- a/src/Limits.php +++ b/src/Limits.php @@ -39,6 +39,17 @@ final class Limits return self::sanitize(get_option(self::OPTION, [])); } + /** + * The [min, max] each field is clamped to by sanitize() – for admin UI form attributes, + * so they can't drift from the actual bounds. + * + * @return array + */ + public static function bounds(): array + { + return self::BOUNDS; + } + /** * Settings API sanitize callback: always returns all keys, clamped to their bounds. * diff --git a/tests/Unit/Admin/LimitsTabTest.php b/tests/Unit/Admin/LimitsTabTest.php index afd36b1..c233d43 100644 --- a/tests/Unit/Admin/LimitsTabTest.php +++ b/tests/Unit/Admin/LimitsTabTest.php @@ -31,6 +31,7 @@ final class LimitsTabTest extends TestCase Functions\when('settings_fields')->justReturn(null); Functions\when('submit_button')->justReturn(null); Functions\when('get_option')->justReturn(['upload_mb' => 8]); + Functions\when('wp_max_upload_size')->justReturn(64 * 1024 * 1024); ob_start(); (new LimitsTab())->render(); @@ -39,5 +40,40 @@ final class LimitsTabTest extends TestCase self::assertSame(3, substr_count($html, 'type="number"')); self::assertStringContainsString('name="lit_mcp_limits[upload_mb]" value="8"', $html); self::assertStringContainsString('name="lit_mcp_limits[log_retention_days]" value="90"', $html); + self::assertStringContainsString('min="1" max="100"', $html); + self::assertStringContainsString('min="1" max="3650"', $html); + } + + public function testWarnsWhenUploadMbExceedsTheServersOwnUploadLimit(): void + { + Functions\stubEscapeFunctions(); + Functions\stubTranslationFunctions(); + Functions\when('settings_fields')->justReturn(null); + Functions\when('submit_button')->justReturn(null); + Functions\when('get_option')->justReturn(['upload_mb' => 50]); + Functions\when('wp_max_upload_size')->justReturn(2 * 1024 * 1024); + + ob_start(); + (new LimitsTab())->render(); + $html = (string) ob_get_clean(); + + self::assertStringContainsString('class="description"', $html); + self::assertMatchesRegularExpression('/50 MB.*2 MB/s', $html); + } + + public function testNoWarningWhenUploadMbIsWithinTheServersOwnUploadLimit(): void + { + Functions\stubEscapeFunctions(); + Functions\stubTranslationFunctions(); + Functions\when('settings_fields')->justReturn(null); + Functions\when('submit_button')->justReturn(null); + Functions\when('get_option')->justReturn(['upload_mb' => 5]); + Functions\when('wp_max_upload_size')->justReturn(64 * 1024 * 1024); + + ob_start(); + (new LimitsTab())->render(); + $html = (string) ob_get_clean(); + + self::assertStringNotContainsString('class="description"', $html); } } diff --git a/tests/Unit/LimitsTest.php b/tests/Unit/LimitsTest.php index 943547e..7e4ce6d 100644 --- a/tests/Unit/LimitsTest.php +++ b/tests/Unit/LimitsTest.php @@ -42,4 +42,23 @@ final class LimitsTest extends TestCase ); self::assertSame(Limits::DEFAULTS, Limits::sanitize(null)); } + + public function testBoundsMatchesWhatSanitizeClampsTo(): void + { + self::assertSame([1, 100], Limits::bounds()['upload_mb']); + self::assertSame([1, 3650], Limits::bounds()['log_retention_days']); + self::assertSame([1, 100], Limits::bounds()['snapshot_count']); + + // Cross-check against sanitize()'s actual clamping, so the two can't drift apart. + $tooLow = array_map(static fn (array $bound): int => $bound[0] - 1, Limits::bounds()); + $tooHigh = array_map(static fn (array $bound): int => $bound[1] + 1, Limits::bounds()); + self::assertSame( + array_map(static fn (array $bound): int => $bound[0], Limits::bounds()), + Limits::sanitize($tooLow) + ); + self::assertSame( + array_map(static fn (array $bound): int => $bound[1], Limits::bounds()), + Limits::sanitize($tooHigh) + ); + } }