feat(contact): server-side PHP mail handler for contact form
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
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
This commit is contained in:
@@ -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;
|
||||
|
||||
152
index.php
152
index.php
@@ -1,3 +1,104 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// --- Helper functions ---
|
||||
function normalizeContactValue(string $value): string
|
||||
{
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
function escapeContactValue(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function containsHeaderInjection(string $value): bool
|
||||
{
|
||||
return (bool) preg_match('/[\r\n]/', $value);
|
||||
}
|
||||
|
||||
// --- Form processing ---
|
||||
$formErrors = [];
|
||||
$formSuccess = false;
|
||||
$formData = ['fname' => '', '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.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
@@ -531,15 +632,31 @@
|
||||
paar Terminvorschläge an.
|
||||
</p>
|
||||
<div class="contact-form">
|
||||
<form id="contactForm">
|
||||
<?php if ($formSuccess): ?>
|
||||
<div class="form-success" style="display: block">
|
||||
<p>Vielen Dank für Ihre Anfrage!</p>
|
||||
<br />
|
||||
<small>Wir haben Ihre Nachricht erhalten und melden uns innerhalb von 24 Stunden bei Ihnen.</small>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php if (!empty($formErrors)): ?>
|
||||
<div class="form-errors">
|
||||
<ul>
|
||||
<?php foreach ($formErrors as $error): ?>
|
||||
<li><?= escapeContactValue($error) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<form id="contactForm" method="post">
|
||||
<div class="form-row">
|
||||
<div class="form-field">
|
||||
<label for="fname">Vorname</label>
|
||||
<input type="text" id="fname" name="fname" placeholder="Max" required />
|
||||
<input type="text" id="fname" name="fname" placeholder="Max" required value="<?= escapeContactValue($formData['fname']) ?>" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="lname">Nachname</label>
|
||||
<input type="text" id="lname" name="lname" placeholder="Mustermann" required />
|
||||
<input type="text" id="lname" name="lname" placeholder="Mustermann" required value="<?= escapeContactValue($formData['lname']) ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -551,20 +668,25 @@
|
||||
name="email"
|
||||
placeholder="max@beispiel.de"
|
||||
required
|
||||
value="<?= escapeContactValue($formData['email']) ?>"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="phone">Telefon</label>
|
||||
<input type="tel" id="phone" name="phone" placeholder="+49 ..." />
|
||||
<input type="tel" id="phone" name="phone" placeholder="+49 ..." value="<?= escapeContactValue($formData['phone']) ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-field full">
|
||||
<label for="interest">Anliegen</label>
|
||||
<select id="interest" name="interest">
|
||||
<option>Besichtigung anfragen</option>
|
||||
<option>Allgemeine Informationen</option>
|
||||
<option>Mietbewerbung einreichen</option>
|
||||
<?php
|
||||
$interestOptions = ['Besichtigung anfragen', 'Allgemeine Informationen', 'Mietbewerbung einreichen'];
|
||||
foreach ($interestOptions as $opt):
|
||||
$selected = ($formData['interest'] === $opt) ? ' selected' : '';
|
||||
?>
|
||||
<option<?= $selected ?>><?= escapeContactValue($opt) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -576,16 +698,20 @@
|
||||
name="message"
|
||||
rows="4"
|
||||
placeholder="Ihre Nachricht ..."
|
||||
></textarea>
|
||||
required
|
||||
><?= escapeContactValue($formData['message']) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Honeypot: hidden field for spam bots -->
|
||||
<div class="hp-field" aria-hidden="true">
|
||||
<label for="website">Website</label>
|
||||
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off" />
|
||||
</div>
|
||||
<!-- Form load timestamp for minimum-submit-time check -->
|
||||
<input type="hidden" name="form_time" value="<?= time() ?>" />
|
||||
<button type="submit" class="btn-submit">Anfrage absenden</button>
|
||||
</form>
|
||||
<div class="form-success" id="formSuccess">
|
||||
<p>Vielen Dank für Ihre Anfrage!</p>
|
||||
<br />
|
||||
<small>Ihr E-Mail-Programm wurde geöffnet. Bitte senden Sie die E-Mail ab, damit Ihre Anfrage bei uns eingeht.</small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="contact-details">
|
||||
<p>Oder schreiben Sie uns direkt: <a href="mailto:mki@kies-media.de">mki@kies-media.de</a></p>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user