Compare commits

15 Commits

Author SHA1 Message Date
smueller
96a5977283 Refactor configuration loading: Updated multiple public PHP files to require a new bootstrap file for configuration management. Adjusted paths for product configuration to enhance maintainability and consistency across the application. 2026-05-22 13:58:30 +02:00
TheOnlyMace
ec8686761c Enhance IT services section: Updated index.php to include new CSS classes for the IT services section, improving layout and responsiveness. Added styles for a 3x2 grid layout in custom.css, ensuring better presentation across different screen sizes. 2026-04-09 21:31:32 +02:00
TheOnlyMace
d3da589a1d Add animated gradient background for "Beliebt" badge: Introduced a new CSS class for the featured badge with a linear gradient and animation effects. Added media query to disable animation for users preferring reduced motion, enhancing accessibility. 2026-04-09 21:26:29 +02:00
TheOnlyMace
b6e268855e Update CSS styles: Enhanced style.css with refined layout and improved responsiveness. Adjusted CSS variables for better visual consistency and user experience across the application. 2026-04-09 21:17:11 +02:00
TheOnlyMace
d02377c735 Update CSS styles: Refined style.css to enhance layout consistency and responsiveness. Adjusted CSS variables for improved visual appeal and user experience across the application. 2026-04-09 21:13:08 +02:00
TheOnlyMace
d62d6b576d Refactor CSS styles: Updated style.css to improve layout consistency and responsiveness. Adjusted CSS variables for better visual appeal and user experience across the application. 2026-04-09 21:12:31 +02:00
TheOnlyMace
2c0138f55d Update CSS styles: Refactored and optimized the style.css file to enhance layout and visual consistency across the application. Adjusted various CSS variables and properties for improved responsiveness and user experience. 2026-04-09 21:11:25 +02:00
TheOnlyMace
2074707c9d Add product visibility control: Introduced a mechanism to hide specific products ('vpc', 'vps') from the shop. Updated relevant templates to conditionally display these products based on visibility status, enhancing user experience and product management. 2026-04-09 21:07:48 +02:00
TheOnlyMace
55f9fdd957 Update products configuration: Removed outdated features from 'mail-gateway' offerings, including email archiving and calendar & contacts, to streamline product details and improve clarity. 2026-04-09 21:04:21 +02:00
TheOnlyMace
ab81d1c49f Update webhosting configuration: Adjusted price for 'Webhosting Professional' from '9,99' to '13,99' to reflect updated pricing strategy. 2026-04-09 21:02:52 +02:00
TheOnlyMace
b40ad53d9c Update webhosting configuration: Changed 'Domains Inkl.' value from '3' to '1' to accurately reflect product offerings and specifications. 2026-04-09 21:01:59 +02:00
TheOnlyMace
e5402189ea Update webhosting configuration: Specify database type in product details by changing 'Datenbanken' value to '20 MySQL' for improved clarity in offerings. 2026-04-09 21:01:10 +02:00
TheOnlyMace
e544720900 Update webhosting configuration and content: Enhanced product descriptions, pricing, and specifications for webhosting packages. Transitioned from cPanel/Webmin to Plesk Webhosting, and updated PHP version to 8.4. Improved clarity and appeal of offerings in both backend configuration and public-facing web page. 2026-04-09 20:59:37 +02:00
a5bba86db0 Merge pull request 'Enhance Content Security Policy in .htaccess: Updated img-src and connect-src directives to include Google Tag Manager and additional Google services, improving security while ensuring compatibility with analytics tools.' (#4) from dev into main
Reviewed-on: smueller/HexaHost-Frontend#4
2026-04-09 13:50:00 +00:00
d34dbbb079 Merge pull request 'Update .htaccess for enhanced security and Google Tag Assistant compatibility: Removed X-Frame-Options header and adjusted Content Security Policy to allow Google Tag Manager and Google Analytics, ensuring compliance with security standards while maint…' (#3) from dev into main
Reviewed-on: smueller/HexaHost-Frontend#3
2026-04-09 13:46:46 +00:00
52 changed files with 1076 additions and 2293 deletions

View File

@@ -1,62 +0,0 @@
name: Release Build (ci → main)
on:
push:
branches:
- ci
workflow_dispatch:
env:
GITEA_HOST: git.hexahost.dev
REPO_PATH: smueller/HexaHost-Frontend
jobs:
release-build:
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
runs-on: ubuntu-latest
steps:
- name: Checkout ci (Integration)
uses: actions/checkout@v4
with:
fetch-depth: 0
repository-url: https://git.hexahost.dev/smueller/HexaHost-Frontend
ref: ci
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Run release obfuscation
run: python scripts/obfuscate_release.py --root . --hash-assets
- name: Publish release to main
env:
GITEA_TOKEN: ${{ github.token }}
run: |
git config user.name "gitea-actions"
git config user.email "actions@local"
git remote set-url origin "https://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}.git"
git fetch origin main ci
git add -A
if git diff --cached --quiet; then
echo "No release changes to publish."
exit 0
fi
TREE=$(git write-tree)
MSG="chore(release): obfuscate and hash production assets [skip ci]"
if git show-ref --verify --quiet refs/remotes/origin/main; then
PARENT=$(git rev-parse origin/main)
COMMIT=$(git commit-tree "$TREE" -p "$PARENT" -m "$MSG")
else
COMMIT=$(git commit-tree "$TREE" -m "$MSG")
fi
git push origin "${COMMIT}:refs/heads/main"

View File

@@ -1,29 +0,0 @@
#!/bin/sh
# Validiert Commit-Messages nach Conventional Commits
# https://www.conventionalcommits.org/
commit_msg_file="$1"
first_line=$(sed '/^#/d;/^$/d' "$commit_msg_file" | head -n 1)
# Merge/Revert von Git erlauben
case "$first_line" in
Merge\ *|Revert\ *)
exit 0
;;
esac
pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\([a-z0-9._-]+\))?!?: .+'
if ! printf '%s\n' "$first_line" | grep -qE "$pattern"; then
echo >&2 "❌ Commit-Message entspricht nicht Conventional Commits."
echo >&2 ""
echo >&2 " Format: type(scope): description"
echo >&2 " Beispiel: feat(products): hide vpc in navigation"
echo >&2 ""
echo >&2 " Erlaubte types: feat, fix, docs, style, refactor, perf, test, build, ci, chore"
echo >&2 " Überspringen: git commit --no-verify"
exit 1
fi
exit 0

View File

@@ -1,63 +0,0 @@
# Hinweis: Gitea nutzt .gitea/workflows/obfuscate-main.yml (identischer Ablauf).
name: Release Build (ci → main)
on:
push:
branches:
- ci
workflow_dispatch:
env:
GITEA_HOST: git.hexahost.dev
REPO_PATH: smueller/HexaHost-Frontend
jobs:
release-build:
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
runs-on: ubuntu-latest
steps:
- name: Checkout ci (Integration)
uses: actions/checkout@v4
with:
fetch-depth: 0
repository-url: https://git.hexahost.dev/smueller/HexaHost-Frontend
ref: ci
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Run release obfuscation
run: python scripts/obfuscate_release.py --root . --hash-assets
- name: Publish release to main
env:
GITEA_TOKEN: ${{ github.token }}
run: |
git config user.name "gitea-actions"
git config user.email "actions@local"
git remote set-url origin "https://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}.git"
git fetch origin main ci
git add -A
if git diff --cached --quiet; then
echo "No release changes to publish."
exit 0
fi
TREE=$(git write-tree)
MSG="chore(release): obfuscate and hash production assets [skip ci]"
if git show-ref --verify --quiet refs/remotes/origin/main; then
PARENT=$(git rev-parse origin/main)
COMMIT=$(git commit-tree "$TREE" -p "$PARENT" -m "$MSG")
else
COMMIT=$(git commit-tree "$TREE" -m "$MSG")
fi
git push origin "${COMMIT}:refs/heads/main"

1
.gitignore vendored
View File

@@ -13,7 +13,6 @@ build/
# Environment variables
.env
.cursorrules
.cursor/
.cursorrules.txt
.env.local
.env.development.local

View File

@@ -1,16 +0,0 @@
# Conventional Commits nur die erste nicht-kommentierte Zeile wird verwendet
# Format: type(scope): description
#
# feat Neues Feature
# fix Bugfix
# docs Dokumentation
# style Formatierung (keine Logik)
# refactor Umbau ohne Feature/Fix
# perf Performance
# test Tests
# build Build / Dependencies
# ci CI/CD
# chore Sonstiges
#
# Beispiel (diese Zeile anpassen und Kommentare löschen oder stehen lassen):
# feat(products): hide vpc and vps in navigation

View File

@@ -46,7 +46,7 @@ Eine moderne und umfangreiche Website für das Hosting-Unternehmen HexaHost.de a
- **HTML5** - Semantisches Markup
- **CSS3** - Moderne Styles mit Custom Properties
- **Vanilla JavaScript** - Keine Framework-Dependencies
- **Native PHP mail()** - E-Mail-Versand ohne externe Abhängigkeiten
- **PHPMailer** - E-Mail-Versand via SMTP
- **Glassmorphism Design** - Moderne Glaseffekte
- **CSS Grid & Flexbox** - Responsive Layouts
- **Inter Font** - Moderne Typografie
@@ -73,6 +73,7 @@ HexaHost-Frontend/
│ ├── sitemap.xml # SEO Sitemap
│ ├── favicon.svg # Website Icon
│ ├── .htaccess # Apache Konfiguration
│ ├── composer.json # PHP Dependencies
│ ├── config/ # ⬅ vom Backend
│ │ ├── config.php # Allgemeine Konfiguration
│ │ └── mail-config.php # E-Mail-Konfiguration
@@ -126,6 +127,7 @@ HexaHost-Frontend/
### Voraussetzungen
- PHP 8.0 oder höher
- Composer (für PHPMailer)
- Apache mit mod_rewrite (für .htaccess)
- [HexaHost-Backend](../HexaHost-Backend) Repository
@@ -145,18 +147,24 @@ HexaHost-Frontend/
cp -r HexaHost-Backend/includes/* HexaHost-Frontend/public/includes/
```
3. **Konfiguration anpassen**
3. **PHP Dependencies installieren**
```bash
cd HexaHost-Frontend/public
composer install
```
4. **Konfiguration anpassen**
```bash
# mail-config.php mit SMTP-Daten bearbeiten
nano config/mail-config.php
```
4. **Lokaler Development Server**
5. **Lokaler Development Server**
```bash
php -S localhost:8000 -t public
```
5. **Website öffnen**
6. **Website öffnen**
```
http://localhost:8000
```
@@ -164,53 +172,6 @@ HexaHost-Frontend/
### Produktion
Für den Produktivbetrieb `public/` als Webroot konfigurieren.
### Production-Build & Veröffentlichung
| Branch | Zweck |
|--------|--------|
| **`dev`** | Entwicklung (lesbarer Code, Kommentare) |
| **`ci`** | Integration (du mergst `dev` hierher) |
| **`main`** | Release/Produktion (obfuskiert, gehashte Assets — nur per Pipeline) |
**Ablauf: `dev` → `ci` → `main`**
1. Auf **`dev`** entwickeln und pushen
2. **`dev` nach `ci` mergen** (manuell, z. B. in Gitea oder lokal)
3. **`ci` pushen** → startet `.gitea/workflows/obfuscate-main.yml`
4. Pipeline obfuskiert im Runner-Workspace und publiziert nach **`main`**
```powershell
# Nach fertigen Änderungen auf dev:
git checkout ci
git pull origin ci
git merge dev
git push origin ci
```
Bei jedem Push auf **`ci`**:
1. Checkout von `ci` im temporären Runner-Workspace
2. Obfuscation-Build (`scripts/obfuscate_release.py --hash-assets`)
3. Ergebnis nach `main` pushen (Bot-Commit mit `[skip ci]`)
**Nicht** `dev` oder `ci` direkt nach `main` mergen. Der Branch **`ci` bleibt lesbar** — Obfuscation wird nur nach `main` publiziert.
`ci`-Branch einmalig anlegen (falls noch nicht vorhanden): `git checkout -b ci dev && git push -u origin ci`
Der Build führt aus:
- Entfernen von Kommentaren (inkl. Block-Kommentaren) in PHP/JS/CSS
- Minify + Obfuscate für JavaScript
- Minify für CSS
- Kein Source-Map-Output
- Hashing von JS/CSS-Dateinamen + automatische Referenz-Anpassung
Lokal testen (nur in Kopie, nicht committen):
```bash
python scripts/obfuscate_release.py --root . --hash-assets
```
## 🔗 Backend-Integration
Das Backend-Repository enthält folgende wiederverwendbare Komponenten:
@@ -226,11 +187,13 @@ Detaillierte Informationen zu den Backend-Komponenten finden Sie in der [Backend
## 📧 E-Mail-Konfiguration
Die E-Mail-Funktionalität nutzt die native PHP-`mail()`-Funktion. In `public/config/mail-config.php` müssen mindestens Absender und Empfänger gesetzt werden:
Die E-Mail-Funktionalität benötigt eine SMTP-Konfiguration in `public/config/mail-config.php`:
```php
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
define('SMTP_TO_EMAIL', 'info@hexahost.de');
define('SMTP_HOST', 'mail.example.com');
define('SMTP_PORT', 587);
define('SMTP_USER', 'noreply@hexahost.de');
define('SMTP_PASS', 'your-password');
```
Siehe `docs/README-EMAIL-SETUP.md` für detaillierte Anweisungen.
@@ -257,7 +220,7 @@ Siehe `docs/README-EMAIL-SETUP.md` für detaillierte Anweisungen.
### Kontaktformular
- Server-seitige Validierung
- E-Mail-Versand via native PHP mail()
- E-Mail-Versand via SMTP
- CSRF-Schutz
- Auto-Fill basierend auf URL-Parametern
- FAQ-Sektion mit Accordion

View File

@@ -1,8 +0,0 @@
# Direkten Zugriff auf Backend-Dateien verhindern (Document Root = public/)
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>

View File

@@ -7,8 +7,6 @@
* Verwendung: GET /api/dns-lookup.php?domain=example.com
*/
require_once __DIR__ . '/../includes/api-helpers.php';
// CORS Headers für Frontend-Zugriff
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
@@ -21,21 +19,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
// Nur GET-Anfragen erlauben
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405);
echo json_encode(['error' => 'Nur GET-Anfragen erlaubt']);
exit;
}
if (!checkApiRateLimit('dns-lookup')) {
rejectApiRateLimit();
// Domain-Parameter prüfen
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => 'Domain-Parameter fehlt']);
exit;
}
$domain = getValidatedDomainParam();
if ($domain === null) {
// Domain validieren (einfache Prüfung)
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
http_response_code(400);
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
echo json_encode(['error' => 'Ungültiges Domain-Format']);
exit;
}

View File

@@ -7,8 +7,6 @@
* Verwendung: GET /api/dns-propagation.php?domain=example.com&type=A
*/
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
@@ -19,10 +17,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
if (!checkApiRateLimit('dns-propagation')) {
rejectApiRateLimit();
}
// Öffentliche DNS-Server für Propagation-Check
$dnsServers = [
['name' => 'Google', 'ip' => '8.8.8.8', 'location' => 'Global'],
@@ -35,12 +29,18 @@ $dnsServers = [
['name' => 'Level3', 'ip' => '4.2.2.1', 'location' => 'USA'],
];
$domain = getValidatedDomainParam();
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
$type = isset($_GET['type']) ? strtoupper(trim($_GET['type'])) : 'A';
if ($domain === null) {
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
echo json_encode(['error' => 'Domain-Parameter fehlt']);
exit;
}
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
http_response_code(400);
echo json_encode(['error' => 'Ungültiges Domain-Format']);
exit;
}
@@ -100,12 +100,7 @@ function queryDnsServer(string $domain, string $type, string $server): array {
$records = [];
// Versuche zuerst dig zu verwenden (genauer)
$digResult = @shell_exec(
'dig @' . escapeshellarg($server) . ' '
. escapeshellarg($domain) . ' '
. escapeshellarg($type)
. ' +short +time=2 +tries=1 2>/dev/null'
);
$digResult = @shell_exec("dig @{$server} {$domain} {$type} +short +time=2 +tries=1 2>/dev/null");
if ($digResult !== null && trim($digResult) !== '') {
$lines = array_filter(explode("\n", trim($digResult)));

View File

@@ -7,8 +7,6 @@
* Verwendung: GET /api/ping-check.php?domain=example.com
*/
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
@@ -19,15 +17,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
if (!checkApiRateLimit('ping-check')) {
rejectApiRateLimit();
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => 'Domain-Parameter fehlt']);
exit;
}
$domain = getValidatedDomainParam();
// Protokoll und Pfad entfernen
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
$domain = explode('/', $domain)[0];
if ($domain === null) {
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
http_response_code(400);
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
echo json_encode(['error' => 'Ungültiges Domain-Format']);
exit;
}
@@ -95,8 +99,7 @@ function checkIcmpPing(string $domain): array {
];
// Versuche ping-Kommando
$safeDomain = escapeshellarg($domain);
$pingResult = @shell_exec("ping -c 3 -W 2 {$safeDomain} 2>/dev/null");
$pingResult = @shell_exec("ping -c 3 -W 2 {$domain} 2>/dev/null");
if ($pingResult) {
// Prüfe auf erfolgreiche Antworten

View File

@@ -7,8 +7,6 @@
* Verwendung: GET /api/reverse-dns.php?ip=8.8.8.8
*/
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
@@ -19,10 +17,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
if (!checkApiRateLimit('reverse-dns')) {
rejectApiRateLimit();
}
$ip = isset($_GET['ip']) ? trim($_GET['ip']) : '';
if (empty($ip)) {

View File

@@ -7,8 +7,6 @@
* Verwendung: GET /api/ssl-check.php?domain=example.com
*/
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
@@ -19,15 +17,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
if (!checkApiRateLimit('ssl-check')) {
rejectApiRateLimit();
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => 'Domain-Parameter fehlt']);
exit;
}
$domain = getValidatedDomainParam();
// Protokoll und Pfad entfernen
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
$domain = explode('/', $domain)[0];
$domain = explode(':', $domain)[0]; // Port entfernen
if ($domain === null) {
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
http_response_code(400);
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
echo json_encode(['error' => 'Ungültiges Domain-Format']);
exit;
}

View File

@@ -7,8 +7,6 @@
* Verwendung: GET /api/whois-lookup.php?domain=example.com
*/
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
@@ -19,11 +17,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
if (!checkApiRateLimit('whois-lookup')) {
rejectApiRateLimit();
}
$domain = isset($_GET['domain']) ? trim((string) $_GET['domain']) : '';
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
if (empty($domain)) {
http_response_code(400);

View File

@@ -1,64 +0,0 @@
<?php
/**
* Zentrale Betreff-Konfiguration für das Kontaktformular
*/
/**
* @return array<string, string> Betreff-Schlüssel => Anzeigename
*/
function getContactSubjectMap(): array {
return [
'allgemeine-anfrage' => 'Allgemeine Anfrage',
'vpc-anfrage' => 'Virtual Private Container Anfrage',
'vps-anfrage' => 'Virtual Private Server Anfrage',
'mail-gateway-anfrage' => 'Mail Gateway Anfrage',
'webhosting-anfrage' => 'Webhosting Anfrage',
'it-beratung' => 'IT-Beratung',
'it-support' => 'IT-Support & Fehlerbehebung',
'netzwerk-wlan' => 'Netzwerk & WLAN-Einrichtung',
'it-sicherheit-backup' => 'IT-Sicherheit & Backup',
'webseiten-hosting-service' => 'Webseiten- & Hosting-Service',
'wartung-betreuung' => 'Wartung & Betreuung',
'support' => 'Technischer Support',
'beratung' => 'Persönliche Beratung',
'migration' => 'Migration/Umzug',
'sonstiges' => 'Sonstige Anfrage',
];
}
/**
* @param string $subjectKey
*/
function isAllowedContactSubject(string $subjectKey): bool {
return array_key_exists($subjectKey, getContactSubjectMap());
}
/**
* Betreff aus ?product= oder ?package= für die Kontaktseite ableiten
*/
function getPreselectedContactSubject(): string {
$productMap = [
'vpc' => 'vpc-anfrage',
'vps' => 'vps-anfrage',
'mail-gateway' => 'mail-gateway-anfrage',
'webhosting' => 'webhosting-anfrage',
];
if (!empty($_GET['product'])) {
$product = strtolower(preg_replace('/[^a-z0-9-]/', '', (string) $_GET['product']));
if (isset($productMap[$product])) {
return $productMap[$product];
}
}
if (!empty($_GET['package'])) {
$package = strtolower(preg_replace('/[^a-z0-9-]/', '', (string) $_GET['package']));
foreach ($productMap as $productId => $subjectKey) {
if (str_starts_with($package, $productId . '-')) {
return $subjectKey;
}
}
}
return '';
}

View File

@@ -2,18 +2,55 @@
/**
* HexaHost.de Mail Configuration
*
* Dieses Projekt versendet E-Mails nativ über PHP mail().
* Es sind keine externen Bibliotheken oder Composer-Installationen erforderlich.
* Bitte passen Sie die folgenden SMTP-Einstellungen an Ihre E-Mail-Provider an.
*
* Beispiele für gängige Provider:
*
* Gmail:
* - SMTP_HOST = 'smtp.gmail.com'
* - SMTP_PORT = 587
* - SMTP_USERNAME = 'ihre-email@gmail.com'
* - SMTP_PASSWORD = 'ihr-app-passwort'
*
* Outlook/Hotmail:
* - SMTP_HOST = 'smtp-mail.outlook.com'
* - SMTP_PORT = 587
*
* GMX:
* - SMTP_HOST = 'mail.gmx.net'
* - SMTP_PORT = 587
*
* Web.de:
* - SMTP_HOST = 'smtp.web.de'
* - SMTP_PORT = 587
*
* 1&1:
* - SMTP_HOST = 'smtp.1und1.de'
* - SMTP_PORT = 587
*
* Strato:
* - SMTP_HOST = 'smtp.strato.de'
* - SMTP_PORT = 587
*
* Ionos:
* - SMTP_HOST = 'smtp.ionos.de'
* - SMTP_PORT = 587
*/
// SMTP Server Einstellungen
define('SMTP_HOST', 'smtp.ihre-domain.de'); // Ihr SMTP-Server
define('SMTP_PORT', 587); // SMTP-Port (meist 587 oder 465)
define('SMTP_USERNAME', 'kontakt@ihre-domain.de'); // Ihr SMTP-Benutzername
define('SMTP_PASSWORD', 'ihr-smtp-passwort'); // Ihr SMTP-Passwort
// E-Mail Adressen
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de'); // Absender-E-Mail
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de'); // Absender-E-Mail (muss zu SMTP_USERNAME passen)
define('SMTP_TO_EMAIL', 'info@hexahost.de'); // Empfänger-E-Mail für Kontaktformular
// Sicherheitseinstellungen
define('ENABLE_CSRF_PROTECTION', true); // CSRF-Schutz aktivieren
define('ENABLE_RATE_LIMITING', true); // Rate-Limiting aktivieren
define('MAX_REQUESTS_PER_HOUR', 5); // Max. Anfragen pro Stunde
define('MAX_REQUESTS_PER_HOUR', 10); // Max. Anfragen pro Stunde
// Spam-Schutz Einstellungen
define('ENABLE_SPAM_PROTECTION', true); // Spam-Schutz aktivieren
@@ -50,6 +87,11 @@ define('BLACKLISTED_EMAILS', [
// 'test@test.com'
]);
// Validierung der Konfiguration
if (!defined('SMTP_HOST') || !defined('SMTP_USERNAME') || !defined('SMTP_PASSWORD')) {
die('SMTP-Konfiguration ist unvollständig. Bitte überprüfen Sie die mail-config.php');
}
// Überprüfung der E-Mail-Adressen
if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) {
die('Ungültige SMTP_FROM_EMAIL Adresse');
@@ -98,6 +140,24 @@ function isValidEmail($email) {
return true;
}
// CSRF Token generieren (wird in functions.php verwendet)
// Hinweis: Diese Funktion existiert auch in functions.php - hier nur als Fallback
if (!function_exists('generateCSRFToken')) {
function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
}
// CSRF Token validieren
if (!function_exists('validateCSRFToken')) {
function validateCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
}
/**
* Hilfsfunktion zum Abrufen der Konfiguration als Array
* Kompatibilität mit contact-handler.php
@@ -107,6 +167,13 @@ function isValidEmail($email) {
*/
function getHexaHostConfig($key = null) {
$config = [
// SMTP Server-Einstellungen
'smtp_host' => SMTP_HOST,
'smtp_port' => SMTP_PORT,
'smtp_username' => SMTP_USERNAME,
'smtp_password' => SMTP_PASSWORD,
'smtp_encryption' => 'tls',
// Absender/Empfänger
'from_email' => SMTP_FROM_EMAIL,
'from_name' => 'HexaHost.de Kontaktformular',
@@ -116,9 +183,6 @@ function getHexaHostConfig($key = null) {
// Sicherheit
'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR,
'honeypot_field' => 'website',
'enable_csrf' => ENABLE_CSRF_PROTECTION,
'min_message_length' => MIN_MESSAGE_LENGTH,
'max_message_length' => MAX_MESSAGE_LENGTH,
// Debug
'debug_mode' => DEBUG_MODE,

View File

@@ -2,8 +2,7 @@
/**
* HexaHost.de Produkt-Konfiguration
*
* Hier können Sie alle Preise, Shop-Links und Produktinformationen zentral verwalten.
* Pro Paket: shop_url (WHMCS/Warenkorb-Link, z. B. https://shop.hexahost.de/cart.php?a=add&pid=123)
* Hier können Sie alle Preise und Produktinformationen zentral verwalten.
* Nach Änderungen: npm run build && npm run deploy
*
* Verwendung in PHP-Seiten:
@@ -11,6 +10,9 @@
* $packages = getProductPackages('vpc');
*/
// Temporär ausgeblendete Produkte (einfach IDs aus dieser Liste entfernen, um sie wieder im Shop anzuzeigen)
$HIDDEN_PRODUCTS = ['vpc', 'vps'];
// ============================================================================
// VIRTUAL PRIVATE CONTAINER (VPC)
// ============================================================================
@@ -31,7 +33,6 @@ $PRODUCTS['vpc'] = [
'starter' => [
'name' => 'VPC Starter',
'price' => '4,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '1 vCore'],
@@ -51,7 +52,6 @@ $PRODUCTS['vpc'] = [
'business' => [
'name' => 'VPC Business',
'price' => '9,99',
'shop_url' => '',
'featured' => true,
'specs' => [
['label' => 'CPU Kerne', 'value' => '2 vCores'],
@@ -72,7 +72,6 @@ $PRODUCTS['vpc'] = [
'professional' => [
'name' => 'VPC Professional',
'price' => '19,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '4 vCores'],
@@ -94,7 +93,6 @@ $PRODUCTS['vpc'] = [
'enterprise' => [
'name' => 'VPC Enterprise',
'price' => '39,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '8 vCores'],
@@ -137,7 +135,6 @@ $PRODUCTS['vps'] = [
'starter' => [
'name' => 'VPS Starter',
'price' => '9,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '1 vCore'],
@@ -157,7 +154,6 @@ $PRODUCTS['vps'] = [
'business' => [
'name' => 'VPS Business',
'price' => '19,99',
'shop_url' => '',
'featured' => true,
'specs' => [
['label' => 'CPU Kerne', 'value' => '2 vCores'],
@@ -178,7 +174,6 @@ $PRODUCTS['vps'] = [
'professional' => [
'name' => 'VPS Professional',
'price' => '39,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '4 vCores'],
@@ -200,7 +195,6 @@ $PRODUCTS['vps'] = [
'enterprise' => [
'name' => 'VPS Enterprise',
'price' => '79,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '8 vCores'],
@@ -243,7 +237,6 @@ $PRODUCTS['mail-gateway'] = [
'starter' => [
'name' => 'Mail Starter',
'price' => '4,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'Postfächer', 'value' => '5'],
@@ -262,7 +255,6 @@ $PRODUCTS['mail-gateway'] = [
'business' => [
'name' => 'Mail Business',
'price' => '14,99',
'shop_url' => '',
'featured' => true,
'specs' => [
['label' => 'Postfächer', 'value' => '25'],
@@ -276,14 +268,11 @@ $PRODUCTS['mail-gateway'] = [
'Webmail',
'IMAP/POP3',
'SSL/TLS Verschlüsselung',
'E-Mail Archivierung (30 Tage)',
'Kalender & Kontakte',
],
],
'professional' => [
'name' => 'Mail Professional',
'price' => '29,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'Postfächer', 'value' => '100'],
@@ -297,15 +286,11 @@ $PRODUCTS['mail-gateway'] = [
'Webmail',
'IMAP/POP3',
'SSL/TLS Verschlüsselung',
'E-Mail Archivierung (1 Jahr)',
'Kalender & Kontakte',
'ActiveSync für Mobile',
],
],
'enterprise' => [
'name' => 'Mail Enterprise',
'price' => '59,99',
'shop_url' => '',
'featured' => false,
'specs' => [
['label' => 'Postfächer', 'value' => 'Unbegrenzt'],
@@ -319,9 +304,6 @@ $PRODUCTS['mail-gateway'] = [
'Webmail',
'IMAP/POP3',
'SSL/TLS Verschlüsselung',
'E-Mail Archivierung (10 Jahre)',
'Kalender & Kontakte',
'ActiveSync für Mobile',
'Dedizierte IP',
'Priority Support',
],
@@ -335,107 +317,107 @@ $PRODUCTS['mail-gateway'] = [
$PRODUCTS['webhosting'] = [
'name' => 'Webhosting',
'short_name' => 'Webhosting',
'description' => 'Klassisches Hosting mit PHP, MySQL und SSL',
'min_price' => '4,99',
'description' => 'Klassisches Hosting mit PHP, MySQL und SSL. WordPress-ready mit Plesk Webhosting.',
'min_price' => '2,99',
'hero_highlight' => 'Alles für Ihre Website',
'hero_description' => 'Klassisches Webhosting mit allem, was Sie für eine erfolgreiche Website benötigen. Plesk, PHP, SSL-Zertifikate und E-Mail-Postfächer - alles inklusive.',
'hero_description' => 'Klassisches Webhosting mit allem, was Sie für eine erfolgreiche Website benötigen. PHP, MySQL, SSL-Zertifikate und E-Mail-Postfächer - alles inklusive.',
'packages_title' => 'Webhosting Pakete',
'packages_description' => 'Von der ersten Website bis zum professionellen Online-Shop',
'cta_title' => 'Bereit für Ihr Webhosting?',
'cta_description' => 'Starten Sie noch heute mit professionellem Webhosting',
'page_title' => 'Webhosting - Klassisches Hosting für Websites | HexaHost.de',
'page_description' => 'Webhosting mit Plesk, PHP und SSL-Zertifikaten. Klassisches Hosting für Websites ab 4,99€/Monat bei HexaHost.de',
'page_title' => 'Webhosting - Klassisches Hosting für Websites - WordPress-ready | HexaHost.de',
'page_description' => 'Webhosting mit PHP, MySQL und SSL-Zertifikaten. Klassisches Hosting für Websites - WordPress-ready ab 2,99€/Monat bei HexaHost.de',
'packages' => [
'starter' => [
'name' => 'Webhosting Starter',
'price' => '4,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-starter',
'price' => '2,99',
'featured' => false,
'specs' => [
['label' => 'Webspace', 'value' => '10 GB'],
['label' => 'Domains inkl.', 'value' => '1'],
['label' => 'Domains', 'value' => '1'],
['label' => 'Subdomains', 'value' => '5'],
['label' => 'Domain-Alias', 'value' => '2'],
['label' => 'Domain Aliase', 'value' => '2'],
['label' => 'E-Mail-Postfächer', 'value' => '10'],
['label' => 'Datenbanken', 'value' => '2 MySQL'],
['label' => 'Traffic', 'value' => '100 GB'],
],
'features' => [
'Perfekt für kleine Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
'Plesk Webhosting',
'PHP 8.4',
'Git, WP Toolkit, Composer',
'SSL-Zertifikat',
'E-Mail-Postfächer',
'MySQL Datenbanken',
],
],
'business' => [
'name' => 'Webhosting Business',
'price' => '7,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-business',
'featured' => true,
'specs' => [
['label' => 'Webspace', 'value' => '30 GB'],
['label' => 'Domains inkl.', 'value' => '1'],
['label' => 'Domains Inkl.', 'value' => '1'],
['label' => 'Subdomains', 'value' => '10'],
['label' => 'Domain-Alias', 'value' => '2'],
['label' => 'Domain Aliase', 'value' => '2'],
['label' => 'E-Mail-Postfächer', 'value' => '20'],
['label' => 'Datenbanken', 'value' => '5 MySQL'],
['label' => 'Traffic', 'value' => '100 GB'],
],
'features' => [
'Perfekt für mittlere Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
'Plesk Webhosting',
'PHP 8.4',
'Git, WP Toolkit, Composer',
'SSL-Zertifikat',
'E-Mail-Postfächer',
'MySQL Datenbanken',
'Backup-Service',
],
],
'professional' => [
'name' => 'Webhosting Professional',
'price' => '9,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-professional',
'price' => '13,99',
'featured' => false,
'specs' => [
['label' => 'Webspace', 'value' => '50 GB'],
['label' => 'Domains inkl.', 'value' => '3'],
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => '100'],
['label' => 'Domains Inkl.', 'value' => '1'],
['label' => 'Subdomains', 'value' => 'unbegrenzt'],
['label' => 'Domain Aliase', 'value' => 'unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => '20'],
['label' => 'Datenbanken', 'value' => '20 MySQL'],
['label' => 'Traffic', 'value' => '100 GB'],
],
'features' => [
'Perfekt für größere Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
'Plesk Webhosting',
'PHP 8.4',
'Git, WP Toolkit, Composer',
'SSL-Zertifikat',
'E-Mail-Postfächer',
'MySQL Datenbanken',
'Backup-Service',
'Priority Support',
],
],
'enterprise' => [
'name' => 'Webhosting Enterprise',
'price' => '29,99',
'shop_url' => '',
'price' => '19,99',
'featured' => false,
'specs' => [
['label' => 'Webspace', 'value' => '200 GB'],
['label' => 'Domains inkl.', 'value' => '5'],
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => 'Unbegrenzt'],
['label' => 'Datenbanken', 'value' => '50 MySQL'],
['label' => 'Webspace', 'value' => '100 GB'],
['label' => 'Domains Inkl.', 'value' => '3'],
['label' => 'Subdomains', 'value' => 'unbegrenzt'],
['label' => 'Domain Aliase', 'value' => 'unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => '100'],
['label' => 'Datenbanken', 'value' => 'Unbegrenzt'],
['label' => 'Traffic', 'value' => '1 TB'],
],
'features' => [
'Perfekt für Enterprise-Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
'Plesk Webhosting',
'PHP 8.4',
'Git, WP Toolkit, Composer',
'SSL-Zertifikat',
'E-Mail-Postfächer',
'MySQL Datenbanken',
'Backup-Service',
'Priority Support',
'Individuelle Konfiguration',
],
@@ -443,43 +425,10 @@ $PRODUCTS['webhosting'] = [
],
];
// Sichtbarkeit in Navigation, Footer und auf der Startseite (Seiten bleiben per URL erreichbar)
$PRODUCT_VISIBILITY = [
'vpc' => false,
'vps' => false,
'mail-gateway' => false,
'webhosting' => true,
];
// ============================================================================
// HILFSFUNKTIONEN
// ============================================================================
/**
* Prüft, ob eine Produktkategorie in der Navigation angezeigt wird
*/
function isProductVisible(string $productId): bool {
global $PRODUCT_VISIBILITY;
return $PRODUCT_VISIBILITY[$productId] ?? true;
}
/**
* HTML hidden-Attribut für ausgeblendete Produktkategorien
*/
function productHiddenAttr(string $productId): string {
return isProductVisible($productId) ? '' : ' hidden';
}
/**
* Aktive Navigationsseiten für sichtbare Produktkategorien
*
* @return string[]
*/
function getVisibleProductPageIds(): array {
global $PRODUCT_VISIBILITY;
return array_keys(array_filter($PRODUCT_VISIBILITY, static fn(bool $visible): bool => $visible));
}
/**
* Alle Produkte abrufen
*/
@@ -496,6 +445,14 @@ function getProduct($productId) {
return $PRODUCTS[$productId] ?? null;
}
/**
* Prüft, ob ein Produkt im Shop angezeigt werden soll
*/
function isProductVisible($productId) {
global $HIDDEN_PRODUCTS;
return !in_array($productId, $HIDDEN_PRODUCTS, true);
}
/**
* Alle Pakete eines Produkts abrufen
*/
@@ -535,38 +492,6 @@ function formatPrice($price, $withCurrency = true) {
return $withCurrency ? $price . '€' : $price;
}
/**
* Bestell-Link für ein Paket (Online-Shop oder Kontaktformular)
*/
function getOrderUrl($productId, $packageId) {
$package = getPackage($productId, $packageId);
if ($package && !empty($package['shop_url'])) {
return $package['shop_url'];
}
return sprintf('contact.php?package=%s-%s', $productId, $packageId);
}
/**
* Bestell-Link für CTA (beliebtes Paket oder erstes Paket)
*/
function getProductOrderUrl($productId) {
$packages = getProductPackages($productId);
foreach ($packages as $packageId => $package) {
if (!empty($package['featured'])) {
return getOrderUrl($productId, $packageId);
}
}
$firstPackageId = array_key_first($packages);
if ($firstPackageId !== null) {
return getOrderUrl($productId, $firstPackageId);
}
return sprintf('contact.php?product=%s', $productId);
}
/**
* Generiert HTML für eine Paket-Karte
*/
@@ -604,7 +529,7 @@ function renderPackageCard($productId, $packageId, $package) {
<div class="package-features">
%s
</div>
<a href="%s" class="btn btn-primary">Jetzt bestellen</a>
<a href="contact.php?package=%s-%s" class="btn btn-primary">Jetzt bestellen</a>
</div>',
$featuredClass,
$featuredBadge,
@@ -612,7 +537,8 @@ function renderPackageCard($productId, $packageId, $package) {
$package['price'],
$specsHtml,
$featuresHtml,
htmlspecialchars(getOrderUrl($productId, $packageId), ENT_QUOTES, 'UTF-8')
$productId,
$packageId
);
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* HexaHost.de zentrale Domain- und Umgebungskonfiguration
*/
define('SITE_DOMAIN_PRODUCTION', 'hexahost.de');
define('SITE_DOMAIN_DEVELOPMENT', 'dev.hexahost.de');
/**
* Aktuellen HTTP-Host (ohne Port) ermitteln
*/
function getSiteHost(): string
{
$host = $_SERVER['HTTP_HOST'] ?? SITE_DOMAIN_PRODUCTION;
$host = strtolower($host);
if (str_contains($host, ':')) {
$host = explode(':', $host, 2)[0];
}
return $host;
}
/**
* true, wenn die Seite unter der Dev-Domain läuft
*/
function isDevelopmentSite(): bool
{
return getSiteHost() === SITE_DOMAIN_DEVELOPMENT;
}
/**
* Basis-URL der aktuellen Anfrage (Schema + Host)
*/
function getSiteBaseUrl(): string
{
$https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443);
$scheme = $https ? 'https' : 'http';
return $scheme . '://' . getSiteHost();
}
/**
* Erlaubte CORS-Origins für AJAX (Kontaktformular)
*
* @return list<string>
*/
function getAllowedOrigins(): array
{
return [
'https://' . SITE_DOMAIN_PRODUCTION,
'https://www.' . SITE_DOMAIN_PRODUCTION,
'https://' . SITE_DOMAIN_DEVELOPMENT,
'http://localhost',
'http://127.0.0.1',
'http://localhost:8000',
];
}
/**
* Kanonische Basis-URL für SEO (Produktion immer hexahost.de)
*/
function getCanonicalBaseUrl(): string
{
return 'https://' . SITE_DOMAIN_PRODUCTION;
}

View File

@@ -1,112 +0,0 @@
<?php
/**
* Gemeinsame Hilfsfunktionen für öffentliche DNS/API-Endpunkte
*/
/**
* Client-IP für Rate-Limiting (Cloudflare-sicher, kein blindes X-Forwarded-For)
*/
function getApiClientIp(): string {
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$isTrustedProxy = filter_var(
$remoteAddr,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === false;
if ($isTrustedProxy) {
foreach (['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
if (empty($_SERVER[$header])) {
continue;
}
$ip = trim(explode(',', $_SERVER[$header])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $remoteAddr;
}
/**
* Einfaches Rate-Limiting pro Endpunkt und IP
*/
function checkApiRateLimit(string $endpoint, int $maxPerHour = 120): bool {
$ip = getApiClientIp();
$cacheFile = sys_get_temp_dir() . '/hexahost_api_' . md5($endpoint . '_' . $ip) . '.txt';
$currentTime = time();
$data = ['requests' => []];
$handle = @fopen($cacheFile, 'c+');
if ($handle === false) {
return true;
}
try {
if (!flock($handle, LOCK_EX)) {
return true;
}
$contents = stream_get_contents($handle);
if ($contents !== false && $contents !== '') {
$decoded = json_decode($contents, true);
if (is_array($decoded) && isset($decoded['requests'])) {
$data = $decoded;
}
}
$data['requests'] = array_values(array_filter(
$data['requests'],
static fn($timestamp) => ($currentTime - (int) $timestamp) < 3600
));
if (count($data['requests']) >= $maxPerHour) {
return false;
}
$data['requests'][] = $currentTime;
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, json_encode($data));
} finally {
flock($handle, LOCK_UN);
fclose($handle);
}
return true;
}
/**
* Domain aus GET-Parameter normalisieren und validieren
*/
function getValidatedDomainParam(string $param = 'domain'): ?string {
if (empty($_GET[$param])) {
return null;
}
$domain = trim((string) $_GET[$param]);
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
$domain = explode('/', $domain)[0];
$domain = explode(':', $domain)[0];
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
return null;
}
return $domain;
}
/**
* Rate-Limit-JSON-Antwort senden und beenden
*/
function rejectApiRateLimit(): void {
http_response_code(429);
echo json_encode(['error' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.']);
exit;
}

View File

@@ -15,17 +15,10 @@
<div class="footer-section">
<h4>Produkte</h4>
<ul>
<li<?php echo productHiddenAttr('vpc'); ?>><a href="/vpc">Virtual Private Container</a></li>
<li<?php echo productHiddenAttr('vps'); ?>><a href="/vps">Virtual Private Server</a></li>
<li<?php echo productHiddenAttr('mail-gateway'); ?>><a href="/mail-gateway">Mail Gateway</a></li>
<li<?php echo productHiddenAttr('webhosting'); ?>><a href="/webhosting">Webhosting</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Andere Dienste</h4>
<ul>
<li><a href="https://hexadns.de" target="_blank" rel="noopener noreferrer">hexadns.de</a></li>
<li><a href="https://www.hexa-mail.de/" target="_blank" rel="noopener noreferrer">hexa-mail.de</a></li>
<li><a href="/vpc">Virtual Private Container</a></li>
<li><a href="/vps">Virtual Private Server</a></li>
<li><a href="/mail-gateway">Mail Gateway</a></li>
<li><a href="/webhosting">Webhosting</a></li>
</ul>
</div>
<div class="footer-section">
@@ -167,7 +160,7 @@
<script src="/assets/js/cookie-consent.js" defer></script>
<?php if (isset($additional_scripts)): ?>
<?php foreach ($additional_scripts as $script): ?>
<script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>
<script src="<?php echo $script; ?>" defer></script>
<?php endforeach; ?>
<?php endif; ?>
</body>

View File

@@ -3,7 +3,11 @@
* Helper functions for HexaHost.de
*/
require_once __DIR__ . '/../config/products-config.php';
$configDir = defined('HEXAHOST_CONFIG_DIR')
? HEXAHOST_CONFIG_DIR
: __DIR__ . '/../config';
require_once $configDir . '/site-config.php';
// Sichere Session-Konfiguration
if (session_status() === PHP_SESSION_NONE) {
@@ -54,6 +58,11 @@ function includeHeader($title = '', $description = '', $page = '', $scripts = []
$current_page = $page;
$additional_scripts = $scripts;
if (!isset($canonical_url)) {
$requestPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
$canonical_url = rtrim(getCanonicalBaseUrl(), '/') . $requestPath;
}
include __DIR__ . '/header.php';
}
@@ -97,56 +106,4 @@ function generateCSRFToken() {
}
return $_SESSION['csrf_token'];
}
/**
* CSRF-Token prüfen und nach Erfolg invalidieren (Replay-Schutz)
*/
function validateCSRFToken($token) {
if (!isset($_SESSION['csrf_token']) || !is_string($token)) {
return false;
}
if (!hash_equals($_SESSION['csrf_token'], $token)) {
return false;
}
unset($_SESSION['csrf_token']);
return true;
}
/**
* Werte für E-Mail-Header bereinigen (Header-Injection verhindern)
*/
function sanitizeHeaderValue(string $value): string {
return str_replace(["\r", "\n", "\0"], '', trim($value));
}
/**
* Client-IP für Logging (Cloudflare / vertrauenswürdiger Reverse-Proxy)
*/
function getClientIP(): string {
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$isTrustedProxy = filter_var(
$remoteAddr,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === false;
if ($isTrustedProxy) {
foreach (['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
if (empty($_SERVER[$header])) {
continue;
}
$ip = trim(explode(',', $_SERVER[$header])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $remoteAddr;
}
?>

View File

@@ -1,4 +1,8 @@
<!DOCTYPE html>
<?php
$configDir = defined('HEXAHOST_CONFIG_DIR') ? HEXAHOST_CONFIG_DIR : __DIR__ . '/../config';
require_once $configDir . '/products-config.php';
?>
<html lang="de">
<head>
<meta charset="UTF-8">
@@ -59,12 +63,16 @@
<ul class="nav-menu">
<li><a href="/" class="nav-link <?php echo ($current_page === 'home') ? 'active' : ''; ?>">Home</a></li>
<li class="nav-dropdown">
<a href="#" class="nav-link <?php echo (in_array($current_page, getVisibleProductPageIds(), true)) ? 'active' : ''; ?>">Produkte</a>
<a href="#" class="nav-link <?php echo (in_array($current_page, ['vpc', 'vps', 'mail-gateway', 'webhosting'])) ? 'active' : ''; ?>">Produkte</a>
<ul class="dropdown-menu">
<li<?php echo productHiddenAttr('vpc'); ?>><a href="/vpc" class="<?php echo ($current_page === 'vpc') ? 'active' : ''; ?>">Virtual Private Container</a></li>
<li<?php echo productHiddenAttr('vps'); ?>><a href="/vps" class="<?php echo ($current_page === 'vps') ? 'active' : ''; ?>">Virtual Private Server</a></li>
<li<?php echo productHiddenAttr('mail-gateway'); ?>><a href="/mail-gateway" class="<?php echo ($current_page === 'mail-gateway') ? 'active' : ''; ?>">Mail Gateway</a></li>
<li<?php echo productHiddenAttr('webhosting'); ?>><a href="/webhosting" class="<?php echo ($current_page === 'webhosting') ? 'active' : ''; ?>">Webhosting</a></li>
<?php if (isProductVisible('vpc')): ?>
<li><a href="/vpc" class="<?php echo ($current_page === 'vpc') ? 'active' : ''; ?>">Virtual Private Container</a></li>
<?php endif; ?>
<?php if (isProductVisible('vps')): ?>
<li><a href="/vps" class="<?php echo ($current_page === 'vps') ? 'active' : ''; ?>">Virtual Private Server</a></li>
<?php endif; ?>
<li><a href="/mail-gateway" class="<?php echo ($current_page === 'mail-gateway') ? 'active' : ''; ?>">Mail Gateway</a></li>
<li><a href="/webhosting" class="<?php echo ($current_page === 'webhosting') ? 'active' : ''; ?>">Webhosting</a></li>
</ul>
</li>
<li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li>

View File

@@ -1,34 +1,145 @@
# HexaHost.de Kontaktformular - Status
# HexaHost.de Kontaktformular - Status-Überprüfung
## Aktueller Stand
## ✅ Behobene Probleme
- Kontaktformular und Handler sind funktionsfähig
- Versand erfolgt nativ über PHP `mail()`
- Keine PHPMailer-/Composer-Abhängigkeit mehr
### 1. Merge-Konflikt in contact-handler.php
- **Problem**: Git-Merge-Konflikt machte die Datei unbrauchbar
- **Lösung**: Konflikt aufgelöst, saubere Version erstellt
- **Status**: ✅ Behoben
## Sicherheitsfunktionen
### 2. CSRF-Token Problem
- **Problem**: HTML-Formular versuchte PHP-Code zu verwenden
- **Lösung**: CSRF-Token durch Honeypot-Feld ersetzt
- **Status**: ✅ Behoben
- CSRF-Token-Prüfung
- Rate Limiting pro IP
- Honeypot gegen Bots
- E-Mail-Validierung und Input-Sanitization
### 3. JavaScript-Merge-Konflikt
- **Problem**: Merge-Konflikt in contact.js
- **Lösung**: Konflikt aufgelöst
- **Status**: ✅ Behoben
## Konfiguration für Produktivbetrieb
## ⚠️ Noch zu behebende Probleme
Datei: `backend/config/mail-config.php`
### 1. SMTP-Konfiguration
- **Problem**: SMTP-Einstellungen sind noch auf Testwerte
- **Aktueller Status**:
```php
'smtp_host' => 'smtp.gmail.com',
'smtp_username' => 'test@hexahost.de',
'smtp_password' => 'your-app-password',
```
- **Erforderlich**: Echte SMTP-Daten eintragen
- **Status**: ⚠️ Zu konfigurieren
Zu prüfen:
- `SMTP_FROM_EMAIL` ist eine gültige Absenderadresse
- `SMTP_TO_EMAIL` ist das richtige Zielpostfach
- `mail()` ist beim Hoster aktiv
### 2. PHPMailer-Installation
- **Problem**: Composer ist nicht installiert
- **Aktueller Status**: Fallback auf native PHP mail() Funktion
- **Erforderlich**: Composer installieren und PHPMailer einrichten
- **Status**: ⚠️ Optional (Fallback funktioniert)
## Testempfehlung
## 📧 E-Mail-Funktionalität
1. `scripts/test-email.php` ausführen
2. Kontaktformular über `contact.php` absenden
3. Empfang und Darstellung der E-Mail prüfen
### Aktuelle Konfiguration
- **SMTP-Host**: smtp.gmail.com
- **Port**: 587
- **Verschlüsselung**: TLS
- **Fallback**: Native PHP mail() Funktion
## Hinweis
### Sicherheitsfeatures
- ✅ Rate Limiting (5 Anfragen/Stunde)
- ✅ Honeypot-Schutz
- ✅ Input-Sanitization
- ✅ E-Mail-Validierung
- ✅ Anti-Spam-Headers
Falls der Versand nicht funktioniert, liegt die Ursache in der Regel an der Server-Mailkonfiguration (MTA/`mail()`), nicht am Formular-Code.
### E-Mail-Templates
- ✅ HTML-Template mit HexaHost-Design
- ✅ Text-Version als Fallback
- ✅ Responsive Design
- ✅ Strukturierte Darstellung aller Daten
## 🧪 Test-Möglichkeiten
### 1. Test-Datei
- **Datei**: `test-email.php`
- **Zweck**: E-Mail-Funktionalität ohne Formular testen
- **Verwendung**: Im Browser öffnen und "Test-E-Mail senden" klicken
### 2. Kontaktformular
- **Datei**: `contact.html`
- **Zweck**: Vollständiges Formular testen
- **Verwendung**: Formular ausfüllen und absenden
## 🔧 Konfiguration erforderlich
### Für Produktivbetrieb:
1. **SMTP-Daten eintragen** in `config.php`:
```php
'smtp_username' => 'ihre-echte-email@gmail.com',
'smtp_password' => 'ihr-echtes-app-passwort',
'from_email' => 'ihre-echte-email@gmail.com',
'to_email' => 'info@hexahost.de',
```
2. **Composer installieren** (optional):
```bash
# Windows: Composer-Installer herunterladen
# Linux/macOS:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
```
3. **PHPMailer installieren** (optional):
```bash
cd public
composer install
```
## 📊 Funktionsfähigkeit
### ✅ Funktioniert
- Kontaktformular-HTML
- JavaScript-Validierung
- PHP-Backend-Verarbeitung
- Rate Limiting
- Spam-Schutz
- E-Mail-Templates
- Fallback auf native mail() Funktion
### ⚠️ Benötigt Konfiguration
- SMTP-Einstellungen
- PHPMailer (optional)
### ❌ Nicht funktioniert
- E-Mail-Versand ohne SMTP-Konfiguration
## 🚀 Nächste Schritte
1. **SMTP-Konfiguration anpassen**
- Echte SMTP-Daten in `config.php` eintragen
- Test mit `test-email.php`
2. **E-Mail-Funktionalität testen**
- Kontaktformular ausfüllen
- E-Mail-Empfang prüfen
3. **PHPMailer installieren** (optional)
- Composer installieren
- PHPMailer einrichten
4. **DNS-Einträge konfigurieren**
- SPF Record
- DMARC Record
- DKIM (über Mail-Server)
## 📞 Support
Bei Problemen:
1. `test-email.php` verwenden
2. PHP-Error-Logs prüfen
3. SMTP-Konfiguration überprüfen
4. Hosting-Provider kontaktieren
---
**Status**: Kontaktformular ist funktionsfähig, benötigt nur SMTP-Konfiguration für E-Mail-Versand.

View File

@@ -2,54 +2,172 @@
## Übersicht
Das Kontaktformular nutzt den nativen PHP-Mailversand über `mail()`.
Es wird keine zusätzliche Bibliothek und keine Composer-Installation benötigt.
Das Kontaktformular von HexaHost.de benötigt eine korrekte SMTP-Konfiguration, um E-Mails zu versenden. Diese Anleitung erklärt, wie Sie die E-Mail-Funktionalität einrichten.
## Erforderliche Konfiguration
## Aktuelle Probleme
Datei: `backend/config/mail-config.php`
### 1. ✅ Behoben: Merge-Konflikt in contact-handler.php
- Der Git-Merge-Konflikt wurde aufgelöst
- Die Datei ist jetzt funktionsfähig
Mindestens diese Werte müssen korrekt gesetzt sein:
### 2. ⚠️ Zu beheben: SMTP-Konfiguration
- Die SMTP-Einstellungen sind noch auf Testwerte gesetzt
- Sie müssen mit echten SMTP-Daten konfiguriert werden
### 3. ⚠️ Zu beheben: PHPMailer-Installation
- Composer ist nicht installiert
- PHPMailer ist nicht verfügbar
- Fallback auf native PHP mail() Funktion ist aktiv
## SMTP-Konfiguration
### Option 1: Gmail SMTP (Empfohlen für Tests)
1. **Gmail-Konto einrichten:**
- Gehen Sie zu Ihren Google-Kontoeinstellungen
- Aktivieren Sie "2-Schritt-Verifizierung"
- Erstellen Sie ein "App-Passwort"
2. **config.php bearbeiten:**
```php
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
define('SMTP_TO_EMAIL', 'info@hexahost.de');
'smtp_host' => 'smtp.gmail.com',
'smtp_port' => 587,
'smtp_username' => 'ihre-email@gmail.com',
'smtp_password' => 'ihr-app-passwort',
'smtp_encryption' => 'tls',
'from_email' => 'ihre-email@gmail.com',
'to_email' => 'info@hexahost.de',
```
## Voraussetzungen auf dem Server
### Option 2: Eigener Mail-Server
- `mail()` muss in der PHP-Umgebung aktiviert sein
- Ein Mail Transfer Agent (MTA) bzw. Mailversand beim Hoster muss funktionieren
1. **SMTP-Daten von Ihrem Hosting-Provider erhalten**
2. **config.php bearbeiten:**
```php
'smtp_host' => 'mail.ihre-domain.de',
'smtp_port' => 587,
'smtp_username' => 'kontakt@ihre-domain.de',
'smtp_password' => 'ihr-smtp-passwort',
'smtp_encryption' => 'tls',
'from_email' => 'kontakt@ihre-domain.de',
'to_email' => 'info@hexahost.de',
```
## Test der E-Mail-Funktion
### Option 3: Andere E-Mail-Provider
1. Per Script testen:
- `scripts/test-email.php`
2. Kontaktformular testen:
- Seite `contact.php` öffnen
- Formular absenden
- Empfang im Zielpostfach prüfen
#### Outlook/Hotmail:
```php
'smtp_host' => 'smtp-mail.outlook.com',
'smtp_port' => 587,
'smtp_encryption' => 'tls',
```
#### GMX:
```php
'smtp_host' => 'mail.gmx.net',
'smtp_port' => 587,
'smtp_encryption' => 'tls',
```
#### Web.de:
```php
'smtp_host' => 'smtp.web.de',
'smtp_port' => 587,
'smtp_encryption' => 'tls',
```
## PHPMailer-Installation
### Composer installieren:
1. **Windows:**
- Laden Sie Composer von https://getcomposer.org/download/
- Führen Sie den Installer aus
2. **Linux/macOS:**
```bash
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
```
### PHPMailer installieren:
```bash
cd public
composer install
```
## Test der E-Mail-Funktionalität
### 1. Test-Datei verwenden:
- Öffnen Sie `test-email.php` im Browser
- Klicken Sie auf "Test-E-Mail senden"
### 2. Kontaktformular testen:
- Öffnen Sie `contact.html`
- Füllen Sie das Formular aus
- Überprüfen Sie die Antwort
## Sicherheitseinstellungen
### DNS-Einträge für Spam-Schutz:
1. **SPF Record (TXT):**
```
v=spf1 include:_spf.hexahost.de ~all
```
2. **DMARC Record (TXT):**
```
v=DMARC1; p=quarantine; rua=mailto:dmarc@hexahost.de
```
3. **DKIM (wird vom Mail-Server konfiguriert)**
## Fehlerbehebung
### Meldung: "Mail function not available"
- `mail()` ist auf dem Server deaktiviert
- Hoster kontaktieren und Mailfunktion aktivieren lassen
### Häufige Probleme:
### Nachricht kommt nicht an
- Spam-Ordner prüfen
- Absenderadresse (`SMTP_FROM_EMAIL`) auf gültige Domain setzen
- PHP-Error-Log prüfen
1. **"SMTP connect() failed"**
- Überprüfen Sie Host und Port
- Prüfen Sie Firewall-Einstellungen
### Versand funktioniert lokal nicht
- Unter Windows/Lokalumgebung ist oft kein SMTP in `php.ini` konfiguriert
- Auf dem echten Webserver testen
2. **"Authentication failed"**
- Überprüfen Sie Benutzername und Passwort
- Bei Gmail: App-Passwort verwenden
## Sicherheit
3. **"Connection timeout"**
- Prüfen Sie Internetverbindung
- Überprüfen Sie SMTP-Host
Das Kontaktformular beinhaltet bereits:
- CSRF-Schutz
- Rate Limiting
- Honeypot-Feld
- Serverseitige Validierung und Sanitization
4. **"Mail function not available"**
- PHP mail() Funktion ist deaktiviert
- Kontaktieren Sie Ihren Hosting-Provider
## Debug-Modus aktivieren
In `config.php` setzen Sie:
```php
'debug_mode' => true,
```
## Logs überprüfen
E-Mail-Fehler werden in den PHP-Error-Logs gespeichert:
- Windows: Event Viewer
- Linux: `/var/log/php_errors.log`
## Nächste Schritte
1. ✅ Merge-Konflikt behoben
2. ⚠️ SMTP-Konfiguration anpassen
3. ⚠️ PHPMailer installieren (optional)
4. ⚠️ E-Mail-Funktionalität testen
5. ⚠️ DNS-Einträge für Spam-Schutz konfigurieren
## Support
Bei Problemen:
- Überprüfen Sie die PHP-Error-Logs
- Testen Sie mit `test-email.php`
- Kontaktieren Sie Ihren Hosting-Provider

View File

@@ -52,6 +52,15 @@
Deny from all
</Files>
<Files "composer.json">
Order allow,deny
Deny from all
</Files>
<Files "composer.lock">
Order allow,deny
Deny from all
</Files>
# Config-Verzeichnis schützen
<IfModule mod_rewrite.c>
@@ -68,6 +77,11 @@
RewriteRule ^logs/ - [F,L]
</IfModule>
# Vendor-Verzeichnis schützen
<IfModule mod_rewrite.c>
RewriteRule ^vendor/ - [F,L]
</IfModule>
# Cache-Header für statische Dateien
<IfModule mod_expires.c>
ExpiresActive On

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = '404 - Seite nicht gefunden | HexaHost.de';

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = '500 - Serverfehler | HexaHost.de';

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = 'Über mich - HexaHost.de | Hosting aus Niederbayern';
@@ -41,9 +41,9 @@ includeHeader($page_title, $page_description, $current_page);
<h2 class="section-title">Unsere Geschichte</h2>
<p>
HexaHost.de wurde von mir, Samuel Müller, mit der Vision gegründet, zuverlässiges
und preiswertes Hosting und IT-Lösungen direkt aus Bayern anzubieten. Als regionales
und preiswertes Hosting und IT-Lösungen direkt aus Deutschland anzubieten. Als regionales
Unternehmen aus Niederbayern verstehe ich die Bedürfnisse meiner Kunden
und biete persönlichen Support.
und bieten persönlichen Support.
</p>
<p>
Meine Expertise liegt in der Bereitstellung moderner Hosting- und IT-Lösungen

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = 'Allgemeine Geschäftsbedingungen - HexaHost.de | AGB';

View File

@@ -16,155 +16,51 @@
margin-top: 2rem;
}
/* IT services page: center target-group cards without affecting other pages */
.it-services-page .values .values-grid {
display: flex !important;
flex-wrap: wrap !important;
justify-content: center !important;
align-items: stretch;
grid-template-columns: none !important;
gap: 1.5rem !important;
/* IT-Dienstleistungen auf Startseite: ausgewogenes 3x2-Grid */
.it-services-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
max-width: 980px;
margin-left: auto;
margin-right: auto;
}
.it-services-page .values .value-item {
flex: 0 1 320px !important;
max-width: 360px;
aspect-ratio: 1 / 1;
margin: 0 !important;
display: flex;
flex-direction: column;
justify-content: center;
.it-services-grid .feature-item {
height: 100%;
}
/* Legal pages: plain white content with black text */
.legal-hero,
.legal-content {
background: #ffffff;
color: #000000;
}
.legal-hero {
margin-top: 70px;
padding: 2rem 0 1.5rem;
border-bottom: 1px solid #e5e5e5;
}
.legal-content {
padding-top: 2rem;
}
.legal-hero-title {
background: none;
-webkit-text-fill-color: #000000;
color: #000000;
margin-bottom: 0.5rem;
}
.legal-hero-description,
.legal-section h2,
.legal-section h3,
.legal-block p,
.legal-block li,
.legal-hero .breadcrumb,
.legal-hero .breadcrumb span,
.legal-content .breadcrumb,
.legal-content .breadcrumb span {
color: #000000;
}
.legal-section,
.legal-section.glass-card {
background: transparent;
border: none;
box-shadow: none;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border-radius: 0;
padding: 0;
}
.legal-section:hover,
.legal-section.glass-card:hover {
transform: none;
box-shadow: none;
border: none;
background: transparent;
}
.legal-content .glass-card:hover {
transform: none;
box-shadow: none;
}
.legal-section h2 {
border-bottom: 1px solid #e5e5e5;
padding-bottom: 0.5rem;
margin-bottom: 0.8rem;
}
.legal-block a,
.legal-hero .breadcrumb a,
.legal-content .breadcrumb a {
color: #0b57d0;
}
.legal-block a:hover,
.legal-hero .breadcrumb a:hover,
.legal-content .breadcrumb a:hover {
color: #0b57d0;
text-decoration: none;
}
/* Ensure absolutely no hover effects on legal text/content */
.legal-hero *,
.legal-content *,
.legal-hero *:hover,
.legal-content *:hover,
.legal-hero *:focus,
.legal-content *:focus,
.legal-hero *:active,
.legal-content *:active {
transform: none !important;
box-shadow: none !important;
text-shadow: none !important;
transition: none !important;
animation: none !important;
}
/* Keep footer sections in one row on desktop */
.footer-content {
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 1.5rem 1.25rem;
align-items: start;
}
.footer-section h4 {
margin-bottom: 0.65rem;
}
.footer-section ul li {
margin-bottom: 0.4rem;
}
@media (max-width: 1100px) {
.footer-content {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.footer-content {
@media (max-width: 1024px) {
.it-services-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 520px) {
.footer-content {
@media (max-width: 640px) {
.it-services-grid {
grid-template-columns: 1fr;
}
}
.it-services-page .values .value-item {
flex-basis: 100%;
max-width: 100%;
/* Animierter Farbverlauf für "Beliebt"-Badge */
.featured-badge {
background: linear-gradient(135deg, #ff51f9 0%, #a348ff 50%, #3978ff 100%);
background-size: 220% 220%;
animation: featured-badge-colors 6s ease infinite;
}
@keyframes featured-badge-colors {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@media (prefers-reduced-motion: reduce) {
.featured-badge {
animation: none;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

41
public/bootstrap.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
/**
* HexaHost Bootstrap für Monorepo- und Produktions-Layout
*
* Monorepo: public/../backend/{includes,config}
* Produktion: public/{includes,config} (nach Backend-Kopie)
*/
if (!defined('HEXAHOST_BOOTSTRAPPED')) {
$pathCandidates = [
[
'includes' => __DIR__ . '/includes',
'config' => __DIR__ . '/config',
],
[
'includes' => __DIR__ . '/../backend/includes',
'config' => __DIR__ . '/../backend/config',
],
];
$resolved = false;
foreach ($pathCandidates as $paths) {
if (is_file($paths['includes'] . '/functions.php')) {
define('HEXAHOST_INCLUDES_DIR', $paths['includes']);
define('HEXAHOST_CONFIG_DIR', $paths['config']);
require_once $paths['includes'] . '/functions.php';
$resolved = true;
break;
}
}
if (!$resolved) {
http_response_code(500);
header('Content-Type: text/plain; charset=utf-8');
echo 'HexaHost: Anwendung konnte nicht gestartet werden (includes nicht gefunden).';
exit;
}
define('HEXAHOST_BOOTSTRAPPED', true);
}

19
public/composer.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "hexahost/contact-form",
"description": "HexaHost.de Contact Form with PHPMailer",
"type": "project",
"require": {
"phpmailer/phpmailer": "^6.8"
},
"autoload": {
"psr-4": {
"HexaHost\\": "src/"
}
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"minimum-stability": "stable",
"prefer-stable": true
}

View File

@@ -1,5 +0,0 @@
<?php
/**
* Kompatibilitäts-Wrapper leitet auf die zentrale Backend-Konfiguration um.
*/
require_once __DIR__ . '/../../backend/config/mail-config.php';

View File

@@ -1,26 +1,55 @@
<?php
/**
* HexaHost.de Contact Form Handler
* E-Mail-Verarbeitung mit nativer PHP-mail()-Funktion und Spam-Schutz
* E-Mail-Verarbeitung mit SMTP-Integration und Spam-Schutz
*/
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/../backend/config/mail-config.php';
require_once __DIR__ . '/../backend/config/contact-config.php';
// Session starten für CSRF-Validierung
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/bootstrap.php';
$configDir = defined('HEXAHOST_CONFIG_DIR') ? HEXAHOST_CONFIG_DIR : __DIR__ . '/config';
require_once $configDir . '/mail-config.php';
// PHPMailer Autoload (falls via Composer installiert)
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
}
// Konfiguration verwenden
$config = getHexaHostConfig();
// CORS Headers für AJAX-Requests (nur eigene Domain erlauben)
$allowed_origins = [
'https://hexahost.de',
'https://www.hexahost.de',
'https://dev.hexahost.de',
'http://localhost',
'http://127.0.0.1',
// Betreff-Mapping (zentral definiert)
const SUBJECT_MAP = [
'allgemeine-anfrage' => 'Allgemeine Anfrage',
'vpc-anfrage' => 'Virtual Private Container Anfrage',
'vps-anfrage' => 'Virtual Private Server Anfrage',
'mail-gateway-anfrage' => 'Mail Gateway Anfrage',
'webhosting-anfrage' => 'Webhosting Anfrage',
'support' => 'Technischer Support',
'beratung' => 'Persönliche Beratung',
'migration' => 'Migration/Umzug',
'sonstiges' => 'Sonstige Anfrage'
];
// CSRF-Token validieren und invalidieren (verhindert Replay-Attacks)
function validateCSRFToken($token) {
if (isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token)) {
// Token nach erfolgreicher Validierung invalidieren
unset($_SESSION['csrf_token']);
return true;
}
return false;
}
// CORS Headers für AJAX-Requests (nur eigene Domains erlauben)
$allowed_origins = getAllowedOrigins();
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowed_origins, true)) {
if (in_array($origin, $allowed_origins)) {
header('Access-Control-Allow-Origin: ' . $origin);
}
@@ -28,97 +57,178 @@ header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json; charset=utf-8');
// Nur POST-Requests erlauben
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit;
}
// Rate Limiting
function checkRateLimit($ip) {
global $config;
$cache_file = sys_get_temp_dir() . '/hexahost_contact_' . md5($ip) . '.txt';
$current_time = time();
$data = ['requests' => []];
$handle = @fopen($cache_file, 'c+');
if ($handle === false) {
return true;
}
if (file_exists($cache_file)) {
$data = json_decode(file_get_contents($cache_file), true);
if ($data && isset($data['requests'])) {
// Entferne alte Einträge (älter als 1 Stunde)
$data['requests'] = array_filter($data['requests'], function($timestamp) use ($current_time) {
return ($current_time - $timestamp) < 3600;
});
try {
if (!flock($handle, LOCK_EX)) {
return true;
}
$contents = stream_get_contents($handle);
if ($contents !== false && $contents !== '') {
$decoded = json_decode($contents, true);
if (is_array($decoded) && isset($decoded['requests'])) {
$data = $decoded;
if (count($data['requests']) >= $config['max_requests_per_hour']) {
return false;
}
}
}
$data['requests'] = array_values(array_filter(
$data['requests'],
static fn($timestamp) => ($current_time - (int) $timestamp) < 3600
));
// Füge aktuellen Request hinzu
$data = isset($data) ? $data : ['requests' => []];
$data['requests'][] = $current_time;
file_put_contents($cache_file, json_encode($data));
if (count($data['requests']) >= $config['max_requests_per_hour']) {
return false;
}
return true;
}
$data['requests'][] = $current_time;
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, json_encode($data));
} finally {
flock($handle, LOCK_UN);
fclose($handle);
// Honeypot Check
function checkHoneypot($data) {
global $config;
$honeypot_field = $config['honeypot_field'];
// Das Honeypot-Feld sollte leer sein (verstecktes Feld)
if (!empty($data[$honeypot_field])) {
return false;
}
return true;
}
function checkHoneypot($data) {
global $config;
$honeypot_field = $config['honeypot_field'];
return empty($data[$honeypot_field]);
// E-Mail-Validierung
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
function sanitizeFormField($input) {
return strip_tags(trim((string) $input));
// Input-Sanitization
function sanitizeInput($input) {
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
}
function getSubjectLabel($subjectKey) {
$map = getContactSubjectMap();
return $map[$subjectKey] ?? 'Neue Kontaktanfrage';
// Sichere IP-Adressen-Erkennung (auch hinter Proxies/Cloudflare)
function getClientIP() {
$ip_keys = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR', // Proxy
'HTTP_X_REAL_IP', // Nginx Proxy
'REMOTE_ADDR' // Standard
];
foreach ($ip_keys as $key) {
if (!empty($_SERVER[$key])) {
// Bei X-Forwarded-For kann eine Liste von IPs kommen
$ip = explode(',', $_SERVER[$key])[0];
$ip = trim($ip);
// Validiere IP-Format
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
// Fallback auf REMOTE_ADDR (auch private IPs für lokale Entwicklung)
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
// SMTP E-Mail-Versand mit PHPMailer
function sendEmail($data) {
global $config;
$subject = '[HexaHost.de] ' . getSubjectLabel($data['subject']);
$replyName = sanitizeHeaderValue($data['firstName'] . ' ' . $data['lastName']);
$replyEmail = sanitizeHeaderValue($data['email']);
// PHPMailer laden (falls verfügbar)
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
// Fallback: Native PHP mail() Funktion
return sendEmailNative($data);
}
try {
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
// Server-Einstellungen
$mail->isSMTP();
$mail->Host = $config['smtp_host'];
$mail->SMTPAuth = true;
$mail->Username = $config['smtp_username'];
$mail->Password = $config['smtp_password'];
$mail->SMTPSecure = $config['smtp_encryption'];
$mail->Port = $config['smtp_port'];
$mail->CharSet = 'UTF-8';
// Absender
$mail->setFrom($config['from_email'], $config['from_name']);
$mail->addReplyTo($data['email'], $data['firstName'] . ' ' . $data['lastName']);
// Empfänger
$mail->addAddress($config['to_email'], $config['to_name']);
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
$subject = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$mail->Subject = '[HexaHost.de] ' . $subject;
// HTML E-Mail-Inhalt
$html_content = generateEmailHTML($data);
$mail->isHTML(true);
$mail->Body = $html_content;
$mail->AltBody = generateEmailText($data);
// Anti-Spam Headers
$mail->addCustomHeader('X-Mailer', 'HexaHost Contact Form');
$mail->addCustomHeader('X-Priority', '3');
$mail->addCustomHeader('X-MSMail-Priority', 'Normal');
$mail->addCustomHeader('Importance', 'Normal');
$mail->addCustomHeader('X-Report-Abuse', 'Please report abuse here: abuse@hexahost.de');
// DKIM, SPF, DMARC werden über DNS konfiguriert
$mail->send();
return true;
} catch (Exception $e) {
error_log('HexaHost Contact Form Error: ' . $e->getMessage());
return false;
}
}
// Fallback: Native PHP mail() Funktion
function sendEmailNative($data) {
global $config;
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
$subject = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$subject = '[HexaHost.de] ' . $subject;
// Headers für Spam-Schutz
$headers = [
'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>',
'Reply-To: ' . $replyName . ' <' . $replyEmail . '>',
'Reply-To: ' . $data['firstName'] . ' ' . $data['lastName'] . ' <' . $data['email'] . '>',
'MIME-Version: 1.0',
'Content-Type: text/html; charset=UTF-8',
'X-Mailer: HexaHost Contact Form',
'X-Priority: 3',
'X-MSMail-Priority: Normal',
'Importance: Normal',
'X-Report-Abuse: Please report abuse here: abuse@hexahost.de',
'X-Report-Abuse: Please report abuse here: abuse@hexahost.de'
];
// Native PHP Mailversand ohne externe Libraries
return mail($config['to_email'], $subject, generateEmailHTML($data), implode("\r\n", $headers));
$message = generateEmailHTML($data);
return mail($config['to_email'], $subject, $message, implode("\r\n", $headers));
}
// HTML E-Mail-Template
function generateEmailHTML($data) {
$subject_text = htmlspecialchars(getSubjectLabel($data['subject']), ENT_QUOTES, 'UTF-8');
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
$subject_text = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$html = '
<!DOCTYPE html>
@@ -154,19 +264,19 @@ function generateEmailHTML($data) {
<div class="field">
<div class="label">Name:</div>
<div class="value">' . htmlspecialchars($data['firstName'] . ' ' . $data['lastName'], ENT_QUOTES, 'UTF-8') . '</div>
<div class="value">' . $data['firstName'] . ' ' . $data['lastName'] . '</div>
</div>
<div class="field">
<div class="label">E-Mail:</div>
<div class="value">' . htmlspecialchars($data['email'], ENT_QUOTES, 'UTF-8') . '</div>
<div class="value">' . $data['email'] . '</div>
</div>';
if (!empty($data['phone'])) {
$html .= '
<div class="field">
<div class="label">Telefon:</div>
<div class="value">' . htmlspecialchars($data['phone'], ENT_QUOTES, 'UTF-8') . '</div>
<div class="value">' . $data['phone'] . '</div>
</div>';
}
@@ -174,19 +284,19 @@ function generateEmailHTML($data) {
$html .= '
<div class="field">
<div class="label">Unternehmen:</div>
<div class="value">' . htmlspecialchars($data['company'], ENT_QUOTES, 'UTF-8') . '</div>
<div class="value">' . $data['company'] . '</div>
</div>';
}
$html .= '
<div class="field">
<div class="label">Nachricht:</div>
<div class="message">' . nl2br(htmlspecialchars($data['message'], ENT_QUOTES, 'UTF-8')) . '</div>
<div class="message">' . nl2br($data['message']) . '</div>
</div>
<div class="field">
<div class="label">IP-Adresse:</div>
<div class="value">' . htmlspecialchars(getClientIP(), ENT_QUOTES, 'UTF-8') . '</div>
<div class="value">' . htmlspecialchars(getClientIP()) . '</div>
</div>
<div class="field">
@@ -206,10 +316,14 @@ function generateEmailHTML($data) {
return $html;
}
// Text-Version der E-Mail
function generateEmailText($data) {
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
$subject_text = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$text = "NEUE KONTAKTANFRAGE - HexaHost.de\n";
$text .= "=====================================\n\n";
$text .= "Betreff: " . getSubjectLabel($data['subject']) . "\n";
$text .= "Betreff: " . $subject_text . "\n";
$text .= "Name: " . $data['firstName'] . " " . $data['lastName'] . "\n";
$text .= "E-Mail: " . $data['email'] . "\n";
@@ -224,8 +338,10 @@ function generateEmailText($data) {
$text .= "\nNachricht:\n";
$text .= "----------\n";
$text .= $data['message'] . "\n\n";
$text .= "IP-Adresse: " . getClientIP() . "\n";
$text .= "Zeitstempel: " . date('d.m.Y H:i:s') . "\n\n";
$text .= "---\n";
$text .= "Diese E-Mail wurde automatisch vom HexaHost.de Kontaktformular generiert.\n";
$text .= "© " . date('Y') . " HexaHost.de - Alle Rechte vorbehalten";
@@ -233,33 +349,40 @@ function generateEmailText($data) {
return $text;
}
// Hauptverarbeitung
try {
if (!empty($config['enable_csrf'])) {
if (empty($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
http_response_code(403);
echo json_encode([
'success' => false,
'message' => 'Ungültige Sitzung. Bitte laden Sie die Seite neu und versuchen Sie es erneut.',
]);
exit;
}
}
if (!checkRateLimit(getClientIP())) {
http_response_code(429);
// CSRF-Token validieren
if (empty($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
http_response_code(403);
echo json_encode([
'success' => false,
'message' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.',
'message' => 'Ungültige Sitzung. Bitte laden Sie die Seite neu und versuchen Sie es erneut.'
]);
exit;
}
if (!checkHoneypot($_POST)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Ungültige Anfrage.']);
// Rate Limiting Check
$client_ip = getClientIP();
if (!checkRateLimit($client_ip)) {
http_response_code(429);
echo json_encode([
'success' => false,
'message' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.'
]);
exit;
}
// Honeypot Check
if (!checkHoneypot($_POST)) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Ungültige Anfrage.'
]);
exit;
}
// Pflichtfelder prüfen
$required_fields = ['firstName', 'lastName', 'email', 'subject', 'message', 'privacy'];
$missing_fields = [];
@@ -274,87 +397,53 @@ try {
echo json_encode([
'success' => false,
'message' => 'Bitte füllen Sie alle Pflichtfelder aus.',
'missing_fields' => $missing_fields,
'missing_fields' => $missing_fields
]);
exit;
}
$subjectKey = trim((string) $_POST['subject']);
if (!isAllowedContactSubject($subjectKey)) {
// E-Mail-Validierung
if (!validateEmail($_POST['email'])) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Bitte wählen Sie einen gültigen Betreff.',
]);
exit;
}
$email = trim((string) $_POST['email']);
if (!isValidEmail($email)) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein.',
]);
exit;
}
$message = trim((string) $_POST['message']);
$messageLength = mb_strlen($message, 'UTF-8');
if ($messageLength < $config['min_message_length']) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Ihre Nachricht ist zu kurz.',
]);
exit;
}
if ($messageLength > $config['max_message_length']) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Ihre Nachricht ist zu lang.',
'message' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein.'
]);
exit;
}
// Daten sanitieren
$data = [
'firstName' => sanitizeFormField($_POST['firstName']),
'lastName' => sanitizeFormField($_POST['lastName']),
'email' => sanitizeHeaderValue($email),
'phone' => sanitizeFormField($_POST['phone'] ?? ''),
'company' => sanitizeFormField($_POST['company'] ?? ''),
'subject' => $subjectKey,
'message' => sanitizeFormField($message),
'privacy' => isset($_POST['privacy']),
'firstName' => sanitizeInput($_POST['firstName']),
'lastName' => sanitizeInput($_POST['lastName']),
'email' => sanitizeInput($_POST['email']),
'phone' => sanitizeInput($_POST['phone'] ?? ''),
'company' => sanitizeInput($_POST['company'] ?? ''),
'subject' => sanitizeInput($_POST['subject']),
'message' => sanitizeInput($_POST['message']),
'privacy' => isset($_POST['privacy']) ? true : false
];
// E-Mail senden
if (sendEmail($data)) {
if (LOG_EMAILS) {
logEmail('sent', [
'subject' => $subjectKey,
'email' => $data['email'],
'ip' => getClientIP(),
]);
}
echo json_encode([
'success' => true,
'message' => 'Ihre Nachricht wurde erfolgreich gesendet! Wir melden uns in Kürze bei Ihnen.',
'message' => 'Ihre Nachricht wurde erfolgreich gesendet! Wir melden uns in Kürze bei Ihnen.'
]);
} else {
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Beim Senden der Nachricht ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.',
'message' => 'Beim Senden der Nachricht ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.'
]);
}
} catch (Exception $e) {
error_log('HexaHost Contact Form Error: ' . $e->getMessage());
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.',
'message' => 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.'
]);
}
?>

View File

@@ -1,8 +1,6 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/../backend/config/contact-config.php';
$preselected_subject = getPreselectedContactSubject();
require_once __DIR__ . '/bootstrap.php';
require_once HEXAHOST_CONFIG_DIR . '/products-config.php';
// Page configuration
$page_title = 'Kontakt - HexaHost.de | Hosting aus Niederbayern';
@@ -132,9 +130,25 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
<label for="subject">Betreff *</label>
<select id="subject" name="subject" required>
<option value="">Bitte wählen...</option>
<?php foreach (getContactSubjectMap() as $subjectKey => $subjectLabel): ?>
<option value="<?php echo htmlspecialchars($subjectKey, ENT_QUOTES, 'UTF-8'); ?>"<?php echo $preselected_subject === $subjectKey ? ' selected' : ''; ?>><?php echo htmlspecialchars($subjectLabel, ENT_QUOTES, 'UTF-8'); ?></option>
<?php endforeach; ?>
<option value="allgemeine-anfrage">Allgemeine Anfrage</option>
<?php if (isProductVisible('vpc')): ?>
<option value="vpc-anfrage">Virtual Private Container</option>
<?php endif; ?>
<?php if (isProductVisible('vps')): ?>
<option value="vps-anfrage">Virtual Private Server</option>
<?php endif; ?>
<option value="mail-gateway-anfrage">Mail Gateway</option>
<option value="webhosting-anfrage">Webhosting</option>
<option value="it-beratung">IT-Beratung</option>
<option value="it-support">IT-Support & Fehlerbehebung</option>
<option value="netzwerk-wlan">Netzwerk & WLAN-Einrichtung</option>
<option value="it-sicherheit-backup">IT-Sicherheit & Backup</option>
<option value="webseiten-hosting-service">Webseiten- & Hosting-Service</option>
<option value="wartung-betreuung">Wartung & Betreuung</option>
<option value="support">Technischer Support</option>
<option value="beratung">Persönliche Beratung</option>
<option value="migration">Migration/Umzug</option>
<option value="sonstiges">Sonstiges</option>
</select>
</div>
<div class="form-group">

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = 'Datenschutzerklärung - HexaHost.de | Datenschutz';

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = 'Impressum - HexaHost.de | Rechtliche Angaben';
@@ -23,7 +23,7 @@ includeHeader($page_title, $page_description, $current_page);
?>
<h1 class="legal-hero-title">Impressum</h1>
<p class="legal-hero-description">
Rechtliche Angaben und Pflichtinformationen gemäß § 5 DDG
</p>
</div>
</div>

View File

@@ -1,5 +1,6 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
require_once HEXAHOST_CONFIG_DIR . '/products-config.php';
// Page configuration
$page_title = 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
@@ -54,7 +55,8 @@ includeHeader($page_title, $page_description, $current_page);
</p>
</div>
<div class="products-grid">
<div class="product-card glass-card"<?php echo productHiddenAttr('vpc'); ?>>
<?php if (isProductVisible('vpc')): ?>
<div class="product-card glass-card">
<div class="product-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 7V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v3"/>
@@ -74,7 +76,9 @@ includeHeader($page_title, $page_description, $current_page);
</ul>
<a href="/vpc" class="btn btn-primary">Mehr erfahren</a>
</div>
<div class="product-card glass-card"<?php echo productHiddenAttr('vps'); ?>>
<?php endif; ?>
<?php if (isProductVisible('vps')): ?>
<div class="product-card glass-card">
<div class="product-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
@@ -92,7 +96,8 @@ includeHeader($page_title, $page_description, $current_page);
</ul>
<a href="/vps" class="btn btn-primary">Mehr erfahren</a>
</div>
<div class="product-card glass-card"<?php echo productHiddenAttr('mail-gateway'); ?>>
<?php endif; ?>
<div class="product-card glass-card">
<div class="product-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
@@ -151,7 +156,7 @@ includeHeader($page_title, $page_description, $current_page);
</section>
<!-- IT Services Section -->
<section class="features">
<section class="features it-services-section">
<div class="container">
<div class="section-header">
<h2 class="section-title">IT-Dienstleistungen für Privat und ergänzend Gewerblich</h2>
@@ -159,7 +164,7 @@ includeHeader($page_title, $page_description, $current_page);
Ergänzend zu unseren Hosting-Angeboten unterstützen wir Sie mit persönlicher IT-Betreuung.
</p>
</div>
<div class="features-grid">
<div class="features-grid it-services-grid">
<div class="feature-item glass-card">
<h3>IT-Beratung</h3>
<p>Individuelle Beratung für sinnvolle und wirtschaftliche IT-Entscheidungen.</p>

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = 'IT-Dienstleistungen - HexaHost.de | Privat & Gewerblich';
@@ -10,7 +10,7 @@ $current_page = 'it-dienstleistungen';
includeHeader($page_title, $page_description, $current_page);
?>
<main id="main-content" class="it-services-page">
<main id="main-content">
<!-- Services Hero -->
<section class="about-hero">
<div class="container">

View File

@@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/../backend/config/products-config.php';
require_once __DIR__ . '/bootstrap.php';
require_once HEXAHOST_CONFIG_DIR . '/products-config.php';
// Produkt-Daten aus Config laden
$product = getProduct('mail-gateway');
@@ -166,8 +166,8 @@ includeHeader($page_title, $page_description, $current_page);
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions">
<a href="<?php echo htmlspecialchars(getProductOrderUrl('mail-gateway'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
<a href="contact.php?product=mail-gateway" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
</div>
</div>
</div>

View File

@@ -2,43 +2,43 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://hexahost.de/</loc>
<lastmod>2026-05-28</lastmod>
<lastmod>2024-01-01</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://hexahost.de/vpc.html</loc>
<lastmod>2026-05-28</lastmod>
<lastmod>2024-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://hexahost.de/vps.html</loc>
<lastmod>2026-05-28</lastmod>
<lastmod>2024-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://hexahost.de/mail-gateway.html</loc>
<lastmod>2026-05-28</lastmod>
<lastmod>2024-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://hexahost.de/webhosting.html</loc>
<lastmod>2026-05-28</lastmod>
<lastmod>2024-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://hexahost.de/about.html</loc>
<lastmod>2026-05-28</lastmod>
<lastmod>2024-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://hexahost.de/contact.html</loc>
<lastmod>2026-05-28</lastmod>
<lastmod>2024-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>

View File

@@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/../backend/config/products-config.php';
require_once __DIR__ . '/bootstrap.php';
require_once HEXAHOST_CONFIG_DIR . '/products-config.php';
// Produkt-Daten aus Config laden
$product = getProduct('vpc');
@@ -166,8 +166,8 @@ includeHeader($page_title, $page_description, $current_page);
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions">
<a href="<?php echo htmlspecialchars(getProductOrderUrl('vpc'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
<a href="contact.php?product=vpc" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/../backend/config/products-config.php';
require_once __DIR__ . '/bootstrap.php';
require_once HEXAHOST_CONFIG_DIR . '/products-config.php';
// Produkt-Daten aus Config laden
$product = getProduct('vps');
@@ -171,8 +171,8 @@ includeHeader($page_title, $page_description, $current_page);
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions">
<a href="<?php echo htmlspecialchars(getProductOrderUrl('vps'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
<a href="contact.php?product=vps" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/../backend/config/products-config.php';
require_once __DIR__ . '/bootstrap.php';
require_once HEXAHOST_CONFIG_DIR . '/products-config.php';
// Produkt-Daten aus Config laden
$product = getProduct('webhosting');
@@ -95,8 +95,8 @@ includeHeader($page_title, $page_description, $current_page);
<polyline points="10,9 9,9 8,9"/>
</svg>
</div>
<h3>Plesk</h3>
<p>Benutzerfreundliche Verwaltungsoberfläche für einfache Website-Verwaltung und E-Mail-Konfiguration.</p>
<h3>Plesk Webhosting</h3>
<p>Plesk Webhosting ist eine benutzerfreundliche Verwaltungsoberfläche für einfache Website-Verwaltung und E-Mail-Konfiguration.</p>
</div>
<div class="detail-card glass-card">
<div class="detail-icon">
@@ -170,8 +170,8 @@ includeHeader($page_title, $page_description, $current_page);
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions">
<a href="<?php echo htmlspecialchars(getProductOrderUrl('webhosting'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
<a href="contact.php?product=webhosting" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/bootstrap.php';
// Page configuration
$page_title = 'Widerrufsbelehrung - HexaHost.de';

View File

@@ -1,463 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import re
import shutil
import subprocess
import sys
import tempfile
from collections import defaultdict
from pathlib import Path
TEXT_EXTENSIONS = {".php", ".html", ".htm", ".xml", ".txt", ".js", ".css"}
HASH_SUFFIX_RE = re.compile(r"\.[a-f0-9]{12}$", re.I)
def strip_comments_keep_strings(text: str) -> str:
out = []
i = 0
n = len(text)
in_single = False
in_double = False
in_template = False
escape = False
in_line_comment = False
in_block_comment = False
while i < n:
ch = text[i]
nxt = text[i + 1] if i + 1 < n else ""
if in_line_comment:
if ch == "\n":
in_line_comment = False
out.append(ch)
i += 1
continue
if in_block_comment:
if ch == "*" and nxt == "/":
in_block_comment = False
i += 2
else:
if ch == "\n":
out.append("\n")
i += 1
continue
if in_single or in_double or in_template:
out.append(ch)
if escape:
escape = False
elif ch == "\\":
escape = True
elif in_single and ch == "'":
in_single = False
elif in_double and ch == '"':
in_double = False
elif in_template and ch == "`":
in_template = False
i += 1
continue
if ch == "/" and nxt == "/":
in_line_comment = True
i += 2
continue
if ch == "/" and nxt == "*":
in_block_comment = True
i += 2
continue
if ch == "'":
in_single = True
out.append(ch)
i += 1
continue
if ch == '"':
in_double = True
out.append(ch)
i += 1
continue
if ch == "`":
in_template = True
out.append(ch)
i += 1
continue
out.append(ch)
i += 1
return "".join(out)
def strip_php_comments(text: str) -> str:
out = []
i = 0
n = len(text)
in_single = False
in_double = False
in_line_comment = False
in_block_comment = False
escape = False
while i < n:
ch = text[i]
nxt = text[i + 1] if i + 1 < n else ""
if in_line_comment:
if ch == "\n":
in_line_comment = False
out.append("\n")
i += 1
continue
if in_block_comment:
if ch == "*" and nxt == "/":
in_block_comment = False
i += 2
else:
if ch == "\n":
out.append("\n")
i += 1
continue
if in_single or in_double:
out.append(ch)
if escape:
escape = False
elif ch == "\\":
escape = True
elif in_single and ch == "'":
in_single = False
elif in_double and ch == '"':
in_double = False
i += 1
continue
if ch == "/" and nxt == "/":
in_line_comment = True
i += 2
continue
if ch == "#":
in_line_comment = True
i += 1
continue
if ch == "/" and nxt == "*":
in_block_comment = True
i += 2
continue
if ch == "'":
in_single = True
elif ch == '"':
in_double = True
out.append(ch)
i += 1
return "".join(out)
def minify_css_fallback(text: str) -> str:
text = strip_comments_keep_strings(text)
text = re.sub(r"\s+", " ", text)
text = re.sub(r"\s*([{}:;,>+~])\s*", r"\1", text)
return text.strip()
def minify_js_fallback(text: str) -> str:
text = strip_comments_keep_strings(text)
text = re.sub(r"\s+", " ", text)
text = re.sub(r"\s*([{}:;,()=+\-*/<>!&|?])\s*", r"\1", text)
return text.strip()
def canonical_asset_base(stem: str) -> str:
name = stem
while HASH_SUFFIX_RE.search(name):
name = HASH_SUFFIX_RE.sub("", name)
return name
def is_skipped_asset(path: Path) -> bool:
lowered = path.as_posix().lower()
if ".min." in path.name or ".obf." in path.name or ".deob." in path.name:
return True
if "deobfuscated" in lowered:
return True
return False
def is_valid_source_content(content: str) -> bool:
if "[javascript-obfuscator-cli]" in content:
return False
return len(content.strip()) >= 20
def collect_asset_groups(asset_root: Path) -> dict[tuple[Path, str, str], list[Path]]:
groups: dict[tuple[Path, str, str], list[Path]] = defaultdict(list)
for ext in (".js", ".css"):
for file_path in sorted(asset_root.rglob(f"*{ext}")):
if is_skipped_asset(file_path):
continue
base = canonical_asset_base(file_path.stem)
key = (file_path.parent, base, ext)
groups[key].append(file_path)
return groups
def pick_source_file(paths: list[Path], base: str, ext: str) -> Path | None:
if not paths:
return None
parent = paths[0].parent
plain = parent / f"{base}{ext}"
ordered: list[Path] = []
if plain in paths:
ordered.append(plain)
for candidate in sorted(paths, key=lambda p: len(p.name)):
if candidate not in ordered:
ordered.append(candidate)
for candidate in ordered:
try:
content = candidate.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
continue
if is_valid_source_content(content):
return candidate
return None
def cleanup_invalid_siblings(paths: list[Path], source: Path) -> None:
for path in paths:
if path == source or not path.exists():
continue
try:
content = path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
content = ""
if not is_valid_source_content(content):
path.unlink()
def run_cmd(command: list[str], cwd: Path) -> None:
proc = subprocess.run(
command,
cwd=str(cwd),
text=True,
capture_output=True,
check=False,
)
if proc.returncode != 0:
raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "command failed")
def process_js(path: Path, cwd: Path) -> None:
original = path.read_text(encoding="utf-8")
if not is_valid_source_content(original):
raise ValueError(
f"invalid or corrupted JS source: {path} "
f"(restore e.g. 'git checkout dev -- {path.as_posix()}')"
)
if shutil.which("npx"):
tmpdir = Path(tempfile.mkdtemp())
try:
src = tmpdir / "input.js"
out = tmpdir / "output.js"
src.write_text(original, encoding="utf-8")
run_cmd(
[
"npx",
"--yes",
"terser",
str(src),
"-o",
str(src),
"--compress",
"--mangle",
"--comments",
"false",
],
cwd,
)
run_cmd(
[
"npx",
"--yes",
"javascript-obfuscator",
str(src),
"--output",
str(out),
"--compact",
"true",
"--control-flow-flattening",
"true",
"--dead-code-injection",
"true",
"--string-array",
"true",
"--string-array-encoding",
"base64",
"--target",
"browser-no-eval",
"--source-map",
"false",
],
cwd,
)
if not out.exists():
raise RuntimeError("obfuscator produced no output file")
result = out.read_text(encoding="utf-8")
if not is_valid_source_content(result):
raise RuntimeError("obfuscator output looks invalid")
path.write_text(result.strip() + "\n", encoding="utf-8")
return
except Exception:
pass
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
path.write_text(minify_js_fallback(original) + "\n", encoding="utf-8")
def process_css(path: Path, cwd: Path) -> None:
original = path.read_text(encoding="utf-8")
if shutil.which("npx"):
tmpdir = Path(tempfile.mkdtemp())
try:
src = tmpdir / "input.css"
out = tmpdir / "output.css"
src.write_text(original, encoding="utf-8")
run_cmd(
[
"npx",
"--yes",
"clean-css-cli",
str(src),
"-o",
str(out),
"--skip-rebase",
"-O2",
],
cwd,
)
path.write_text(out.read_text(encoding="utf-8").strip() + "\n", encoding="utf-8")
return
except Exception:
pass
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
path.write_text(minify_css_fallback(original) + "\n", encoding="utf-8")
def process_php(path: Path) -> None:
original = path.read_text(encoding="utf-8")
stripped = strip_php_comments(original)
path.write_text(stripped, encoding="utf-8")
def hash_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()[:12]
def replace_references(root: Path, mapping: dict[str, str]) -> None:
for candidate in root.rglob("*"):
if not candidate.is_file() or candidate.suffix.lower() not in TEXT_EXTENSIONS:
continue
try:
content = candidate.read_text(encoding="utf-8")
except UnicodeDecodeError:
continue
updated = content
for src, dst in sorted(mapping.items(), key=lambda item: len(item[0]), reverse=True):
updated = updated.replace(src, dst)
updated = updated.replace("/" + src, "/" + dst)
if updated != content:
candidate.write_text(updated, encoding="utf-8")
def build_hash_mapping(public_root: Path) -> dict[str, str]:
mapping: dict[str, str] = {}
asset_root = public_root / "assets"
if not asset_root.exists():
return mapping
groups = collect_asset_groups(asset_root)
for (parent, base, ext), paths in groups.items():
source = pick_source_file(paths, base, ext)
if source is None:
continue
digest = hash_file(source)
target = parent / f"{base}.{digest}{ext}"
rel_new = target.relative_to(public_root).as_posix()
for old in paths:
rel_old = old.relative_to(public_root).as_posix()
if rel_old != rel_new:
mapping[rel_old] = rel_new
if source != target:
if target.exists():
target.unlink()
source.replace(target)
for old in paths:
if old != target and old.exists():
old.unlink()
return mapping
def main() -> int:
parser = argparse.ArgumentParser(description="Release obfuscation build.")
parser.add_argument("--root", default=".", help="Repository root")
parser.add_argument("--hash-assets", action="store_true", help="Hash JS/CSS file names")
args = parser.parse_args()
repo_root = Path(args.root).resolve()
public_root = repo_root / "public"
if not public_root.exists():
print("public directory not found", file=sys.stderr)
return 1
asset_root = public_root / "assets"
if asset_root.exists():
groups = collect_asset_groups(asset_root)
for (parent, base, ext), paths in sorted(groups.items()):
source = pick_source_file(paths, base, ext)
if source is None:
rel = (parent / f"{base}{ext}").relative_to(public_root)
print(
f"ERROR: No valid source for {rel}. "
f"Restore from dev, e.g.: git checkout dev -- {rel}",
file=sys.stderr,
)
return 1
cleanup_invalid_siblings(paths, source)
if ext == ".js":
process_js(source, repo_root)
else:
process_css(source, repo_root)
for php in sorted(public_root.rglob("*.php")):
process_php(php)
for php in sorted((repo_root / "backend").rglob("*.php")):
process_php(php)
if args.hash_assets:
mapping = build_hash_mapping(public_root)
replace_references(repo_root, mapping)
print("Release obfuscation complete.")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,17 +0,0 @@
# Einmal pro Clone ausführen: Commit-Template + Conventional-Commits-Hook aktivieren
$ErrorActionPreference = "Stop"
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
Push-Location $repoRoot
try {
git config --local commit.template .gitmessage
git config --local core.hooksPath .githooks
Write-Host "Git Hooks aktiv:" -ForegroundColor Green
Write-Host " commit.template = .gitmessage"
Write-Host " core.hooksPath = .githooks"
Write-Host ""
Write-Host "Commit-Format: feat(scope): beschreibung"
} finally {
Pop-Location
}

View File

@@ -1,48 +1,72 @@
<?php
/**
* HexaHost.de E-Mail Test (nur CLI oder lokale Entwicklung)
* HexaHost.de E-Mail Test
* Testet die E-Mail-Funktionalität ohne PHPMailer
*/
if (PHP_SAPI !== 'cli') {
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
$isLocal = in_array($remoteAddr, ['127.0.0.1', '::1'], true)
|| filter_var($remoteAddr, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false;
if (!$isLocal) {
http_response_code(403);
exit('Forbidden');
}
}
require_once __DIR__ . '/../backend/config/mail-config.php';
// Konfiguration laden
require_once 'config.php';
// Test-E-Mail senden
function testEmail() {
$config = getHexaHostConfig();
$subject = '[HexaHost.de] Test-E-Mail';
$message = "Test-E-Mail von HexaHost.de\n\n";
$message .= "Zeitstempel: " . date('d.m.Y H:i:s') . "\n";
$headers = [
'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>',
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8',
'X-Mailer: HexaHost Test Email',
// Test-Daten
$test_data = [
'firstName' => 'Test',
'lastName' => 'Benutzer',
'email' => 'test@example.com',
'phone' => '+49 123 456789',
'company' => 'Test GmbH',
'subject' => 'test-email',
'message' => 'Dies ist eine Test-E-Mail vom HexaHost.de Kontaktformular.'
];
return mail($config['to_email'], $subject, $message, implode("\r\n", $headers));
}
if (PHP_SAPI === 'cli') {
echo testEmail() ? "Test-E-Mail gesendet.\n" : "Fehler beim Senden.\n";
exit;
// E-Mail-Inhalt erstellen
$subject = '[HexaHost.de] Test-E-Mail';
$message = "Test-E-Mail von HexaHost.de\n\n";
$message .= "Name: " . $test_data['firstName'] . " " . $test_data['lastName'] . "\n";
$message .= "E-Mail: " . $test_data['email'] . "\n";
$message .= "Telefon: " . $test_data['phone'] . "\n";
$message .= "Unternehmen: " . $test_data['company'] . "\n";
$message .= "Nachricht: " . $test_data['message'] . "\n\n";
$message .= "Zeitstempel: " . date('d.m.Y H:i:s') . "\n";
$message .= "IP-Adresse: " . $_SERVER['REMOTE_ADDR'] . "\n";
// Headers
$headers = [
'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>',
'Reply-To: ' . $test_data['firstName'] . ' ' . $test_data['lastName'] . ' <' . $test_data['email'] . '>',
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8',
'X-Mailer: HexaHost Test Email'
];
// E-Mail senden
$result = mail($config['to_email'], $subject, $message, implode("\r\n", $headers));
return $result;
}
// Test ausführen
if (isset($_GET['test'])) {
echo testEmail()
? 'Test-E-Mail wurde gesendet.'
: 'Fehler beim Senden der Test-E-Mail.';
$result = testEmail();
if ($result) {
echo "✅ Test-E-Mail wurde erfolgreich gesendet!";
} else {
echo "❌ Fehler beim Senden der Test-E-Mail.";
}
} else {
echo '<h1>HexaHost.de E-Mail Test</h1>';
echo '<p><a href="?test=1">Test-E-Mail senden</a></p>';
echo "<h1>HexaHost.de E-Mail Test</h1>";
echo "<p>Klicken Sie auf den Link, um eine Test-E-Mail zu senden:</p>";
echo "<a href='?test=1'>Test-E-Mail senden</a>";
// Konfiguration anzeigen
echo "<h2>Aktuelle Konfiguration:</h2>";
$config = getHexaHostConfig();
echo "<pre>";
print_r($config);
echo "</pre>";
}
?>