diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index 94d2a25..507c114 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -32,15 +32,25 @@ class HomeController extends Controller }; // ── Pull flashed state ──────────────────────────────────────── - $formSuccess = !empty($_SESSION['form_success']); - $formErrors = $_SESSION['form_errors'] ?? []; - $formData = $_SESSION['form_data'] ?? null; - unset($_SESSION['form_success'], $_SESSION['form_errors'], $_SESSION['form_data']); + $formSuccess = !empty($_SESSION['form_success']); + $formErrors = $_SESSION['form_errors'] ?? []; + $formFieldErrors = $_SESSION['form_field_errors'] ?? []; + $formData = $_SESSION['form_data'] ?? null; + unset( + $_SESSION['form_success'], + $_SESSION['form_errors'], + $_SESSION['form_field_errors'], + $_SESSION['form_data'], + ); if ($formSuccess) { - $formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'visit', 'message' => '']; + $formData = self::emptyFormData(); + $formFieldErrors = []; } elseif (!is_array($formData)) { - $formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'visit', 'message' => '']; + $formData = self::emptyFormData(); + $formFieldErrors = is_array($formFieldErrors) ? $formFieldErrors : []; + } else { + $formFieldErrors = is_array($formFieldErrors) ? $formFieldErrors : []; } // ── CSRF token ──────────────────────────────────────────────── @@ -72,17 +82,19 @@ class HomeController extends Controller exit; } + // Per-field errors enable aria-invalid + aria-describedby. + $formFieldErrors = []; if ($formData['fname'] === '') { - $formErrors[] = 'form.error.fname_required'; + $formFieldErrors['fname'][] = 'form.error.fname_required'; } if ($formData['lname'] === '') { - $formErrors[] = 'form.error.lname_required'; + $formFieldErrors['lname'][] = 'form.error.lname_required'; } if ($formData['email'] === '' || !filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) { - $formErrors[] = 'form.error.email_invalid'; + $formFieldErrors['email'][] = 'form.error.email_invalid'; } if ($formData['message'] === '') { - $formErrors[] = 'form.error.message_required'; + $formFieldErrors['message'][] = 'form.error.message_required'; } if ($containsHeaderInjection($formData['email']) || $containsHeaderInjection($formData['fname'] . ' ' . $formData['lname'])) { $formErrors[] = 'form.error.header_injection'; @@ -98,7 +110,7 @@ class HomeController extends Controller $formErrors[] = 'form.error.rate_limit'; } - if (empty($formErrors)) { + if (empty($formErrors) && empty($formFieldErrors)) { $interestKey = self::INTEREST_KEYS[$formData['interest']] ?? 'form.interest.visit'; $interestLabel = I18n::t($interestKey, [], $locale); @@ -129,8 +141,9 @@ class HomeController extends Controller $formErrors[] = 'form.error.send_failed'; } - $_SESSION['form_errors'] = $formErrors; - $_SESSION['form_data'] = $formData; + $_SESSION['form_errors'] = $formErrors; + $_SESSION['form_field_errors'] = $formFieldErrors; + $_SESSION['form_data'] = $formData; header('Location: /#kontakt'); exit; } @@ -177,10 +190,26 @@ class HomeController extends Controller $this->render('home/index', [ 'formSuccess' => $formSuccess, 'formErrors' => $formErrors, + 'formFieldErrors' => $formFieldErrors, 'formData' => $formData, 'interestKeys' => self::INTEREST_KEYS, 'escapeContactValue' => $escapeContactValue, 'structuredData' => $structuredData, ]); } + + /** + * @return array{fname: string, lname: string, email: string, phone: string, interest: string, message: string} + */ + private static function emptyFormData(): array + { + return [ + 'fname' => '', + 'lname' => '', + 'email' => '', + 'phone' => '', + 'interest' => 'visit', + 'message' => '', + ]; + } } diff --git a/app/Locales/de.php b/app/Locales/de.php index 4933c97..f1a68fa 100644 --- a/app/Locales/de.php +++ b/app/Locales/de.php @@ -178,6 +178,8 @@ return [ 'footer.address' => 'Bahnhofstraße 10 · Schleusingen', 'footer.imprint' => 'Impressum', 'footer.privacy' => 'Datenschutz', + 'footer.aria' => 'Fußbereich', + 'a11y.main' => 'Hauptinhalt', // ─── Lightbox ──────────────────────────────────────────────────────── 'lightbox.aria' => 'Bildansicht', diff --git a/app/Locales/en.php b/app/Locales/en.php index cb46034..bf8f954 100644 --- a/app/Locales/en.php +++ b/app/Locales/en.php @@ -163,6 +163,8 @@ return [ 'footer.address' => 'Bahnhofstraße 10 · Schleusingen', 'footer.imprint' => 'Imprint', 'footer.privacy' => 'Privacy policy', + 'footer.aria' => 'Footer', + 'a11y.main' => 'Main content', 'lightbox.aria' => 'Image view', 'lightbox.close' => 'Close image view', diff --git a/app/Locales/ru.php b/app/Locales/ru.php index 62a42c5..faab040 100644 --- a/app/Locales/ru.php +++ b/app/Locales/ru.php @@ -163,6 +163,8 @@ return [ 'footer.address' => 'Bahnhofstraße 10 · Шлайзинген', 'footer.imprint' => 'Импрессум', 'footer.privacy' => 'Политика конфиденциальности', + 'footer.aria' => 'Подвал сайта', + 'a11y.main' => 'Основное содержимое', 'lightbox.aria' => 'Просмотр изображения', 'lightbox.close' => 'Закрыть просмотр изображения', diff --git a/app/Locales/uk.php b/app/Locales/uk.php index 269d77d..8deb259 100644 --- a/app/Locales/uk.php +++ b/app/Locales/uk.php @@ -163,6 +163,8 @@ return [ 'footer.address' => 'Bahnhofstraße 10 · Шлайзінген', 'footer.imprint' => 'Імпресум', 'footer.privacy' => 'Політика конфіденційності', + 'footer.aria' => 'Нижній колонтитул', + 'a11y.main' => 'Головний вміст', 'lightbox.aria' => 'Перегляд зображення', 'lightbox.close' => 'Закрити перегляд зображення', diff --git a/app/views/home/index.php b/app/views/home/index.php index 178c518..c4c838f 100644 --- a/app/views/home/index.php +++ b/app/views/home/index.php @@ -275,29 +275,41 @@ $gridItems = [
+ value="" + > + +

+
+ value="" + > + +

+ +
- -
+
+ value="" + > + +

+
-
+
-
+
-
+
-
+
-
+ placeholder="" + > + +

+ +
diff --git a/app/views/layouts/main.php b/app/views/layouts/main.php index d4adbf0..60a9d3a 100644 --- a/app/views/layouts/main.php +++ b/app/views/layouts/main.php @@ -114,11 +114,11 @@ $navItems = [ -
+
-