Template
Maßgeschneiderte Website für Olzinger Haustechnik
Neuer statischer Webauftritt für die Olzinger GmbH (Heizung, Sanitär, Lüftung, regenerative Energien – Ergolding bei Landshut, seit 1985). - Hero mit live berechnetem WebGL-„Wärmebild“: Maus als Wärmequelle, Wärmespur, Isothermen und Temperaturanzeige; Preloader „Thermostat“ - Gestapelte Leistungskarten mit animierten SVG-Illustrationen - Interaktives Energie-Haus (Wärmepumpe, Solarthermie, Speicher, Fußbodenheizung, Bad, Lüftung) mit Tabs, Hotspots und Autoplay - Heizungs-Finder mit Empfehlung und Übernahme ins Kontaktformular - Ablauf als sich füllende Rohrleitung, Zeitleiste mit Zählwerk, Live-Öffnungsstatus inkl. bayerischer Feiertage - Kontaktformular mit PHP-Versand (Honeypot, Zeitfalle) und mailto-Fallback; Impressum, Datenschutz, 404-Seite - Smooth-Scroll (Lenis) und GSAP-Animationen, alles lokal gehostet; reduzierte Bewegung, Betrieb ohne JavaScript, axe ohne Befunde - SEO: Schema.org HVACBusiness, Open-Graph-Bild, Sitemap, robots.txt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kjd7UMh3HKyVziCgiZ9A2i
This commit is contained in:
34 files changed
+5377
No files matched your search
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* Olzinger Haustechnik – Versand des Kontaktformulars
|
||||
*
|
||||
* Wird per fetch() aus assets/js/contact.js aufgerufen und antwortet mit JSON.
|
||||
* Ist PHP auf dem Server nicht verfügbar, öffnet das Formular automatisch das
|
||||
* E-Mail-Programm des Besuchers (Fallback in contact.js).
|
||||
*
|
||||
* Schutzmaßnahmen: Honeypot-Feld, Mindest-Ausfüllzeit, Längenbegrenzung,
|
||||
* Validierung der E-Mail-Adresse (verhindert Header-Injection über Reply-To),
|
||||
* kodierter Betreff und feste Empfängeradresse.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
const RECIPIENT = 'kontakt@olzinger.de';
|
||||
// Absender sollte eine Adresse der eigenen Domain sein (SPF/DKIM), sonst landen Mails im Spam.
|
||||
const SENDER = 'kontakt@olzinger.de';
|
||||
const MIN_SECONDS = 3;
|
||||
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('Cache-Control: no-store');
|
||||
|
||||
function respond(int $status, array $payload): void
|
||||
{
|
||||
http_response_code($status);
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function field(string $key, int $max): string
|
||||
{
|
||||
$value = isset($_POST[$key]) && is_string($_POST[$key]) ? trim($_POST[$key]) : '';
|
||||
$value = str_replace(["\r\n", "\r"], "\n", $value);
|
||||
// Steuerzeichen außer Zeilenumbruch und Tab entfernen
|
||||
$value = preg_replace('/[^\P{C}\n\t]/u', '', $value) ?? '';
|
||||
return function_exists('mb_substr') ? mb_substr($value, 0, $max, 'UTF-8') : substr($value, 0, $max);
|
||||
}
|
||||
|
||||
function single_line(string $value): string
|
||||
{
|
||||
return trim(preg_replace('/\s+/u', ' ', $value) ?? '');
|
||||
}
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
header('Allow: POST');
|
||||
respond(405, ['ok' => false, 'error' => 'method_not_allowed']);
|
||||
}
|
||||
|
||||
// Honeypot: echte Besucher sehen dieses Feld nicht.
|
||||
if (field('website', 200) !== '') {
|
||||
respond(200, ['ok' => true]);
|
||||
}
|
||||
|
||||
// Zeitfalle: Bots senden Formulare meist sofort ab.
|
||||
$started = (int) ($_POST['ts'] ?? 0);
|
||||
if ($started > 0 && (microtime(true) * 1000 - $started) < MIN_SECONDS * 1000) {
|
||||
respond(200, ['ok' => true]);
|
||||
}
|
||||
|
||||
$name = single_line(field('name', 120));
|
||||
$email = single_line(field('email', 200));
|
||||
$phone = single_line(field('phone', 60));
|
||||
$place = single_line(field('place', 120));
|
||||
$topic = single_line(field('topic', 120));
|
||||
$message = field('message', 5000);
|
||||
$privacy = !empty($_POST['privacy']);
|
||||
|
||||
$errors = [];
|
||||
if ($name === '') {
|
||||
$errors[] = 'name';
|
||||
}
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
$errors[] = 'email';
|
||||
}
|
||||
if ((function_exists('mb_strlen') ? mb_strlen($message, 'UTF-8') : strlen($message)) < 5) {
|
||||
$errors[] = 'message';
|
||||
}
|
||||
if (!$privacy) {
|
||||
$errors[] = 'privacy';
|
||||
}
|
||||
if ($errors) {
|
||||
respond(422, ['ok' => false, 'errors' => $errors]);
|
||||
}
|
||||
|
||||
$subject = 'Anfrage über olzinger.de' . ($topic !== '' ? ' – ' . $topic : '');
|
||||
$body = implode("\n", [
|
||||
'Neue Anfrage über das Kontaktformular auf olzinger.de',
|
||||
'',
|
||||
'Anliegen: ' . ($topic !== '' ? $topic : '–'),
|
||||
'Name: ' . $name,
|
||||
'E-Mail: ' . $email,
|
||||
'Telefon: ' . ($phone !== '' ? $phone : '–'),
|
||||
'PLZ / Ort: ' . ($place !== '' ? $place : '–'),
|
||||
'',
|
||||
'Nachricht:',
|
||||
$message,
|
||||
'',
|
||||
'—',
|
||||
'Gesendet am ' . date('d.m.Y') . ' um ' . date('H:i') . ' Uhr',
|
||||
]);
|
||||
|
||||
$headers = implode("\r\n", [
|
||||
'From: Website Olzinger <' . SENDER . '>',
|
||||
'Reply-To: ' . $email,
|
||||
'MIME-Version: 1.0',
|
||||
'Content-Type: text/plain; charset=UTF-8',
|
||||
'Content-Transfer-Encoding: 8bit',
|
||||
'X-Mailer: olzinger.de',
|
||||
]);
|
||||
|
||||
$encodedSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
||||
$sent = @mail(RECIPIENT, $encodedSubject, $body, $headers, '-f' . SENDER);
|
||||
|
||||
respond($sent ? 200 : 500, ['ok' => (bool) $sent]);
|
||||
Reference in new issue
Block a user