From c2f27097904c797c013a9efcc5e0c60177d1fb7b Mon Sep 17 00:00:00 2001 From: greggy Date: Thu, 14 May 2026 19:12:43 +0000 Subject: [PATCH 1/4] feat(contact): server-side PHP mail handler for contact form Fix #34: E-Mail-Versand via PHP - PHP POST handler with server-side validation (name, email, message) - mail() with From/Reply-To set to form email address - Recipient: mki@kies-media.de - Honeypot spam protection (hidden field) - Minimum submit time check (3 seconds) - Session-based rate limiting (60s between submissions) - Header injection protection - Error messages displayed above form - Success message after successful send - Form values preserved on validation errors - Removed client-side mailto: JavaScript logic - Added CSS for error display and honeypot hiding --- css/haus-schleusingen.css | 30 ++++++++ index.php | 152 ++++++++++++++++++++++++++++++++++---- js/haus-schleusingen.js | 30 +------- 3 files changed, 170 insertions(+), 42 deletions(-) diff --git a/css/haus-schleusingen.css b/css/haus-schleusingen.css index 0bf8e52..68a09b1 100644 --- a/css/haus-schleusingen.css +++ b/css/haus-schleusingen.css @@ -991,6 +991,36 @@ nav.scrolled .nav-hamburger span::after { transform: translateY(-1px); } +.form-errors { + background: #fdf2f2; + border: 1px solid #e8a0a0; + padding: 1rem 1.2rem; + margin-bottom: 1.2rem; +} + +.form-errors ul { + margin: 0; + padding: 0 0 0 1.2rem; + list-style: disc; +} + +.form-errors li { + font-size: 0.85rem; + color: #9e2c2c; + line-height: 1.6; +} + +.hp-field { + position: absolute; + left: -9999px; + top: -9999px; + width: 0; + height: 0; + overflow: hidden; + opacity: 0; + pointer-events: none; +} + .form-success { display: none; text-align: center; diff --git a/index.php b/index.php index b59badf..c1c1587 100644 --- a/index.php +++ b/index.php @@ -1,3 +1,104 @@ + '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => '']; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Collect and normalize input + $formData['fname'] = normalizeContactValue((string) ($_POST['fname'] ?? '')); + $formData['lname'] = normalizeContactValue((string) ($_POST['lname'] ?? '')); + $formData['email'] = normalizeContactValue((string) ($_POST['email'] ?? '')); + $formData['phone'] = normalizeContactValue((string) ($_POST['phone'] ?? '')); + $formData['interest'] = normalizeContactValue((string) ($_POST['interest'] ?? '')); + $formData['message'] = normalizeContactValue((string) ($_POST['message'] ?? '')); + + // Honeypot check – hidden field must be empty + $honeypot = normalizeContactValue((string) ($_POST['website'] ?? '')); + if ($honeypot !== '') { + // Bot detected – pretend success + $formSuccess = true; + $formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => '']; + } else { + // Server-side validation + if ($formData['fname'] === '') { + $formErrors[] = 'Bitte geben Sie Ihren Vornamen an.'; + } + if ($formData['lname'] === '') { + $formErrors[] = 'Bitte geben Sie Ihren Nachnamen an.'; + } + if ($formData['email'] === '' || !filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) { + $formErrors[] = 'Bitte geben Sie eine gültige E-Mail-Adresse an.'; + } + if ($formData['message'] === '') { + $formErrors[] = 'Bitte geben Sie eine Nachricht ein.'; + } + + // Header injection check + if (containsHeaderInjection($formData['email']) || containsHeaderInjection($formData['fname'] . ' ' . $formData['lname'])) { + $formErrors[] = 'Ungültige Zeichen in den Eingabefeldern.'; + } + + // Minimum time check – form submitted too fast (< 3 seconds) + $formTime = isset($_POST['form_time']) ? (int) $_POST['form_time'] : 0; + if ($formTime > 0 && (time() - $formTime) < 3) { + $formErrors[] = 'Das Formular wurde zu schnell abgeschickt. Bitte versuchen Sie es erneut.'; + } + + // Session rate limit – max 1 submission per 60 seconds + $lastSubmit = $_SESSION['last_contact_submit'] ?? 0; + if ($lastSubmit && (time() - $lastSubmit) < 60) { + $formErrors[] = 'Bitte warten Sie einen Moment vor der nächsten Anfrage.'; + } + + // Send email if no errors + if (empty($formErrors)) { + $to = 'mki@kies-media.de'; + $subject = 'Kontaktanfrage: ' . $formData['interest']; + $body = "Von: {$formData['fname']} {$formData['lname']}\n" + . "E-Mail: {$formData['email']}\n"; + if ($formData['phone'] !== '') { + $body .= "Telefon: {$formData['phone']}\n"; + } + $body .= "Anliegen: {$formData['interest']}\n\n" + . $formData['message']; + + $headers = "From: {$formData['email']}\r\n"; + $headers .= "Reply-To: {$formData['email']}\r\n"; + $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $headers .= "X-Mailer: PHP/" . phpversion(); + + $mailSent = mail($to, $subject, $body, $headers); + + if ($mailSent) { + $formSuccess = true; + $_SESSION['last_contact_submit'] = time(); + $formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => '']; + } else { + $formErrors[] = 'Leider konnte die E-Mail nicht gesendet werden. Bitte versuchen Sie es später erneut oder schreiben Sie uns direkt an mki@kies-media.de.'; + } + } + } +} +?> @@ -531,15 +632,31 @@ paar Terminvorschläge an.

-
+ +
+

Vielen Dank für Ihre Anfrage!

+
+ Wir haben Ihre Nachricht erhalten und melden uns innerhalb von 24 Stunden bei Ihnen. +
+ + +
+
    + +
  • + +
+
+ +
- +
- +
@@ -551,20 +668,25 @@ name="email" placeholder="max@beispiel.de" required + value="" />
- +
@@ -576,16 +698,20 @@ name="message" rows="4" placeholder="Ihre Nachricht ..." - > + required + > + + + + -
-

Vielen Dank für Ihre Anfrage!

-
- Ihr E-Mail-Programm wurde geöffnet. Bitte senden Sie die E-Mail ab, damit Ihre Anfrage bei uns eingeht. -
+

Oder schreiben Sie uns direkt: mki@kies-media.de

diff --git a/js/haus-schleusingen.js b/js/haus-schleusingen.js index 38fbcd5..492e556 100644 --- a/js/haus-schleusingen.js +++ b/js/haus-schleusingen.js @@ -71,35 +71,7 @@ $(function () { } }); - // Form submit – opens email client with pre-filled mailto: link - $("#contactForm").on("submit", function (e) { - e.preventDefault(); - - var fname = $("#fname").val().trim(); - var lname = $("#lname").val().trim(); - var email = $("#email").val().trim(); - var phone = $("#phone").val().trim(); - var interest = $("#interest").val(); - var message = $("#message").val().trim(); - - var subject = "Kontaktanfrage: " + interest; - var body = "Von: " + fname + " " + lname + "\n"; - body += "E-Mail: " + email + "\n"; - if (phone) body += "Telefon: " + phone + "\n"; - body += "Anliegen: " + interest + "\n\n"; - body += message; - - var mailto = - "mailto:mki@kies-media.de" + - "?subject=" + encodeURIComponent(subject) + - "&body=" + encodeURIComponent(body); - - window.location.href = mailto; - - // Show success message - $("#contactForm").hide(); - $("#formSuccess").fadeIn(400); - }); + // Form submit is handled server-side by PHP – no JS intervention needed. }); // Mobile hamburger menu (vanilla JS) From 2c6ed749d5fe8c7c64d171cc655ff5c9bf430922 Mon Sep 17 00:00:00 2001 From: greggy Date: Thu, 14 May 2026 22:20:59 +0000 Subject: [PATCH 2/4] Fix: Use AgentMail API instead of mail(), fix reply_to format --- index.php | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/index.php b/index.php index c1c1587..ba03de4 100644 --- a/index.php +++ b/index.php @@ -17,6 +17,40 @@ function containsHeaderInjection(string $value): bool return (bool) preg_match('/[\r\n]/', $value); } +// --- Send email via AgentMail API --- +function sendContactEmail(string $subject, string $body, string $replyToEmail, string $replyToName): bool +{ + $apiKey = getenv('AGENTMAIL_API_KEY') ?: ''; + if ($apiKey === '') { + return false; + } + + $apiUrl = 'https://api.agentmail.to/v0/inboxes/max-kies-media-ai-assistent@agentmail.to/messages/send'; + $payload = json_encode([ + 'to' => ['mki@kies-media.de'], + 'subject' => $subject, + 'text' => $body, + 'reply_to' => $replyToEmail, + ]); + + $ch = curl_init($apiUrl); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . $apiKey, + 'Content-Type: application/json', + ], + ]); + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + return $httpCode >= 200 && $httpCode < 300; +} + // --- Form processing --- $formErrors = []; $formSuccess = false; @@ -69,9 +103,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $formErrors[] = 'Bitte warten Sie einen Moment vor der nächsten Anfrage.'; } - // Send email if no errors + // Send email via AgentMail API if no errors if (empty($formErrors)) { - $to = 'mki@kies-media.de'; $subject = 'Kontaktanfrage: ' . $formData['interest']; $body = "Von: {$formData['fname']} {$formData['lname']}\n" . "E-Mail: {$formData['email']}\n"; @@ -81,12 +114,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $body .= "Anliegen: {$formData['interest']}\n\n" . $formData['message']; - $headers = "From: {$formData['email']}\r\n"; - $headers .= "Reply-To: {$formData['email']}\r\n"; - $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; - $headers .= "X-Mailer: PHP/" . phpversion(); - - $mailSent = mail($to, $subject, $body, $headers); + $mailSent = sendContactEmail($subject, $body, $formData['email'], $formData['fname'] . ' ' . $formData['lname']); if ($mailSent) { $formSuccess = true; From 2307c379dc82c4d6129b025333dc33dd8c88c63e Mon Sep 17 00:00:00 2001 From: greggy Date: Thu, 14 May 2026 22:25:24 +0000 Subject: [PATCH 3/4] Revert to PHP mail() for portability, remove AgentMail API dependency --- index.php | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/index.php b/index.php index ba03de4..7f8e8e9 100644 --- a/index.php +++ b/index.php @@ -17,39 +17,7 @@ function containsHeaderInjection(string $value): bool return (bool) preg_match('/[\r\n]/', $value); } -// --- Send email via AgentMail API --- -function sendContactEmail(string $subject, string $body, string $replyToEmail, string $replyToName): bool -{ - $apiKey = getenv('AGENTMAIL_API_KEY') ?: ''; - if ($apiKey === '') { - return false; - } - $apiUrl = 'https://api.agentmail.to/v0/inboxes/max-kies-media-ai-assistent@agentmail.to/messages/send'; - $payload = json_encode([ - 'to' => ['mki@kies-media.de'], - 'subject' => $subject, - 'text' => $body, - 'reply_to' => $replyToEmail, - ]); - - $ch = curl_init($apiUrl); - curl_setopt_array($ch, [ - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 10, - CURLOPT_HTTPHEADER => [ - 'Authorization: Bearer ' . $apiKey, - 'Content-Type: application/json', - ], - ]); - $response = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - return $httpCode >= 200 && $httpCode < 300; -} // --- Form processing --- $formErrors = []; @@ -103,8 +71,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $formErrors[] = 'Bitte warten Sie einen Moment vor der nächsten Anfrage.'; } - // Send email via AgentMail API if no errors + // Send email if no errors if (empty($formErrors)) { + $to = 'mki@kies-media.de'; $subject = 'Kontaktanfrage: ' . $formData['interest']; $body = "Von: {$formData['fname']} {$formData['lname']}\n" . "E-Mail: {$formData['email']}\n"; @@ -114,7 +83,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $body .= "Anliegen: {$formData['interest']}\n\n" . $formData['message']; - $mailSent = sendContactEmail($subject, $body, $formData['email'], $formData['fname'] . ' ' . $formData['lname']); + $headers = "From: {$formData['email']}\r\n"; + $headers .= "Reply-To: {$formData['email']}\r\n"; + $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $headers .= "X-Mailer: PHP/" . phpversion(); + + $mailSent = mail($to, $subject, $body, $headers); if ($mailSent) { $formSuccess = true; From bf53da13bec814b7fc83b88458043a43618d9a4d Mon Sep 17 00:00:00 2001 From: greggy Date: Thu, 14 May 2026 22:38:27 +0000 Subject: [PATCH 4/4] Fix: Scroll to form result after submission (PRG pattern with anchor) --- index.php | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/index.php b/index.php index 7f8e8e9..7c2b817 100644 --- a/index.php +++ b/index.php @@ -22,7 +22,20 @@ function containsHeaderInjection(string $value): bool // --- Form processing --- $formErrors = []; $formSuccess = false; -$formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => '']; +if (!empty($_SESSION['form_success'])) { + $formSuccess = true; + unset($_SESSION['form_success']); +} +if (!empty($_SESSION['form_errors'])) { + $formErrors = $_SESSION['form_errors']; + unset($_SESSION['form_errors']); +} +if (!empty($_SESSION['form_data'])) { + $formData = $_SESSION['form_data']; + unset($_SESSION['form_data']); +} else { + $formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => '']; +} if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Collect and normalize input @@ -37,8 +50,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $honeypot = normalizeContactValue((string) ($_POST['website'] ?? '')); if ($honeypot !== '') { // Bot detected – pretend success - $formSuccess = true; - $formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => '']; + header('Location: ' . $_SERVER['REQUEST_URI'] . '#form-result'); + $_SESSION['form_success'] = true; + exit; } else { // Server-side validation if ($formData['fname'] === '') { @@ -91,14 +105,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $mailSent = mail($to, $subject, $body, $headers); if ($mailSent) { - $formSuccess = true; $_SESSION['last_contact_submit'] = time(); - $formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => '']; + header('Location: ' . $_SERVER['REQUEST_URI'] . '#form-result'); + $_SESSION['form_success'] = true; + exit; } else { $formErrors[] = 'Leider konnte die E-Mail nicht gesendet werden. Bitte versuchen Sie es später erneut oder schreiben Sie uns direkt an mki@kies-media.de.'; } } } + if (!empty($formErrors)) { + header('Location: ' . $_SERVER['REQUEST_URI'] . '#form-result'); + $_SESSION['form_errors'] = $formErrors; + $_SESSION['form_data'] = $formData; + exit; + } } ?> @@ -635,14 +656,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {

-
+

Vielen Dank für Ihre Anfrage!


Wir haben Ihre Nachricht erhalten und melden uns innerhalb von 24 Stunden bei Ihnen.
-
+