Compare commits
19 Commits
b41f0ef425
...
v1.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56f3f90d95 | ||
|
|
c6b483ca25 | ||
|
|
0df5dc9b57 | ||
|
|
9c92df4ae4 | ||
|
|
e0bcf15121 | ||
|
|
1d4b751316 | ||
|
|
186b5ae199 | ||
|
|
bbc3cbae4e | ||
|
|
e9d5b55459 | ||
|
|
8f985da61f | ||
|
|
6c9114e0a7 | ||
|
|
f097da7eb1 | ||
|
|
b4b1dde484 | ||
|
|
481d223747 | ||
|
|
67fbc68d45 | ||
|
|
d44aaa197b | ||
|
|
ebf6f82bb6 | ||
|
|
5f5be4a4cb | ||
|
|
73ba422655 |
62
.gitea/workflows/obfuscate-main.yml
Normal file
62
.gitea/workflows/obfuscate-main.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
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"
|
||||
29
.githooks/commit-msg
Normal file
29
.githooks/commit-msg
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/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
|
||||
63
.github/workflows/obfuscate-main.yml
vendored
Normal file
63
.github/workflows/obfuscate-main.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# 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
1
.gitignore
vendored
@@ -13,6 +13,7 @@ build/
|
||||
# Environment variables
|
||||
.env
|
||||
.cursorrules
|
||||
.cursor/
|
||||
.cursorrules.txt
|
||||
.env.local
|
||||
.env.development.local
|
||||
|
||||
16
.gitmessage
Normal file
16
.gitmessage
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
73
README.md
73
README.md
@@ -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
|
||||
- **PHPMailer** - E-Mail-Versand via SMTP
|
||||
- **Native PHP mail()** - E-Mail-Versand ohne externe Abhängigkeiten
|
||||
- **Glassmorphism Design** - Moderne Glaseffekte
|
||||
- **CSS Grid & Flexbox** - Responsive Layouts
|
||||
- **Inter Font** - Moderne Typografie
|
||||
@@ -73,7 +73,6 @@ 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
|
||||
@@ -127,7 +126,6 @@ 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
|
||||
|
||||
@@ -147,24 +145,18 @@ HexaHost-Frontend/
|
||||
cp -r HexaHost-Backend/includes/* HexaHost-Frontend/public/includes/
|
||||
```
|
||||
|
||||
3. **PHP Dependencies installieren**
|
||||
```bash
|
||||
cd HexaHost-Frontend/public
|
||||
composer install
|
||||
```
|
||||
|
||||
4. **Konfiguration anpassen**
|
||||
3. **Konfiguration anpassen**
|
||||
```bash
|
||||
# mail-config.php mit SMTP-Daten bearbeiten
|
||||
nano config/mail-config.php
|
||||
```
|
||||
|
||||
5. **Lokaler Development Server**
|
||||
4. **Lokaler Development Server**
|
||||
```bash
|
||||
php -S localhost:8000 -t public
|
||||
```
|
||||
|
||||
6. **Website öffnen**
|
||||
5. **Website öffnen**
|
||||
```
|
||||
http://localhost:8000
|
||||
```
|
||||
@@ -172,6 +164,53 @@ 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:
|
||||
@@ -187,13 +226,11 @@ Detaillierte Informationen zu den Backend-Komponenten finden Sie in der [Backend
|
||||
|
||||
## 📧 E-Mail-Konfiguration
|
||||
|
||||
Die E-Mail-Funktionalität benötigt eine SMTP-Konfiguration in `public/config/mail-config.php`:
|
||||
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:
|
||||
|
||||
```php
|
||||
define('SMTP_HOST', 'mail.example.com');
|
||||
define('SMTP_PORT', 587);
|
||||
define('SMTP_USER', 'noreply@hexahost.de');
|
||||
define('SMTP_PASS', 'your-password');
|
||||
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
|
||||
define('SMTP_TO_EMAIL', 'info@hexahost.de');
|
||||
```
|
||||
|
||||
Siehe `docs/README-EMAIL-SETUP.md` für detaillierte Anweisungen.
|
||||
@@ -220,7 +257,7 @@ Siehe `docs/README-EMAIL-SETUP.md` für detaillierte Anweisungen.
|
||||
|
||||
### Kontaktformular
|
||||
- Server-seitige Validierung
|
||||
- E-Mail-Versand via SMTP
|
||||
- E-Mail-Versand via native PHP mail()
|
||||
- CSRF-Schutz
|
||||
- Auto-Fill basierend auf URL-Parametern
|
||||
- FAQ-Sektion mit Accordion
|
||||
|
||||
8
backend/.htaccess
Normal file
8
backend/.htaccess
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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>
|
||||
@@ -7,6 +7,8 @@
|
||||
* 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: *');
|
||||
@@ -19,26 +21,21 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (!checkApiRateLimit('dns-lookup')) {
|
||||
rejectApiRateLimit();
|
||||
}
|
||||
|
||||
// Domain validieren (einfache Prüfung)
|
||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
|
||||
$domain = getValidatedDomainParam();
|
||||
|
||||
if ($domain === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Ungültiges Domain-Format']);
|
||||
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* 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');
|
||||
@@ -17,6 +19,10 @@ 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'],
|
||||
@@ -29,18 +35,12 @@ $dnsServers = [
|
||||
['name' => 'Level3', 'ip' => '4.2.2.1', 'location' => 'USA'],
|
||||
];
|
||||
|
||||
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
|
||||
$domain = getValidatedDomainParam();
|
||||
$type = isset($_GET['type']) ? strtoupper(trim($_GET['type'])) : 'A';
|
||||
|
||||
if (empty($domain)) {
|
||||
if ($domain === null) {
|
||||
http_response_code(400);
|
||||
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']);
|
||||
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,12 @@ function queryDnsServer(string $domain, string $type, string $server): array {
|
||||
$records = [];
|
||||
|
||||
// Versuche zuerst dig zu verwenden (genauer)
|
||||
$digResult = @shell_exec("dig @{$server} {$domain} {$type} +short +time=2 +tries=1 2>/dev/null");
|
||||
$digResult = @shell_exec(
|
||||
'dig @' . escapeshellarg($server) . ' '
|
||||
. escapeshellarg($domain) . ' '
|
||||
. escapeshellarg($type)
|
||||
. ' +short +time=2 +tries=1 2>/dev/null'
|
||||
);
|
||||
|
||||
if ($digResult !== null && trim($digResult) !== '') {
|
||||
$lines = array_filter(explode("\n", trim($digResult)));
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* 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');
|
||||
@@ -17,21 +19,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
|
||||
|
||||
if (empty($domain)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Domain-Parameter fehlt']);
|
||||
exit;
|
||||
if (!checkApiRateLimit('ping-check')) {
|
||||
rejectApiRateLimit();
|
||||
}
|
||||
|
||||
// Protokoll und Pfad entfernen
|
||||
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
|
||||
$domain = explode('/', $domain)[0];
|
||||
$domain = getValidatedDomainParam();
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
|
||||
if ($domain === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Ungültiges Domain-Format']);
|
||||
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -99,7 +95,8 @@ function checkIcmpPing(string $domain): array {
|
||||
];
|
||||
|
||||
// Versuche ping-Kommando
|
||||
$pingResult = @shell_exec("ping -c 3 -W 2 {$domain} 2>/dev/null");
|
||||
$safeDomain = escapeshellarg($domain);
|
||||
$pingResult = @shell_exec("ping -c 3 -W 2 {$safeDomain} 2>/dev/null");
|
||||
|
||||
if ($pingResult) {
|
||||
// Prüfe auf erfolgreiche Antworten
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* 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');
|
||||
@@ -17,6 +19,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!checkApiRateLimit('reverse-dns')) {
|
||||
rejectApiRateLimit();
|
||||
}
|
||||
|
||||
$ip = isset($_GET['ip']) ? trim($_GET['ip']) : '';
|
||||
|
||||
if (empty($ip)) {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* 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');
|
||||
@@ -17,22 +19,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
|
||||
|
||||
if (empty($domain)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Domain-Parameter fehlt']);
|
||||
exit;
|
||||
if (!checkApiRateLimit('ssl-check')) {
|
||||
rejectApiRateLimit();
|
||||
}
|
||||
|
||||
// Protokoll und Pfad entfernen
|
||||
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
|
||||
$domain = explode('/', $domain)[0];
|
||||
$domain = explode(':', $domain)[0]; // Port entfernen
|
||||
$domain = getValidatedDomainParam();
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
|
||||
if ($domain === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Ungültiges Domain-Format']);
|
||||
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* 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');
|
||||
@@ -17,7 +19,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
|
||||
if (!checkApiRateLimit('whois-lookup')) {
|
||||
rejectApiRateLimit();
|
||||
}
|
||||
|
||||
$domain = isset($_GET['domain']) ? trim((string) $_GET['domain']) : '';
|
||||
|
||||
if (empty($domain)) {
|
||||
http_response_code(400);
|
||||
|
||||
64
backend/config/contact-config.php
Normal file
64
backend/config/contact-config.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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 '';
|
||||
}
|
||||
@@ -2,55 +2,18 @@
|
||||
/**
|
||||
* HexaHost.de Mail Configuration
|
||||
*
|
||||
* 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
|
||||
* Dieses Projekt versendet E-Mails nativ über PHP mail().
|
||||
* Es sind keine externen Bibliotheken oder Composer-Installationen erforderlich.
|
||||
*/
|
||||
|
||||
// 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 (muss zu SMTP_USERNAME passen)
|
||||
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de'); // Absender-E-Mail
|
||||
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', 10); // Max. Anfragen pro Stunde
|
||||
define('MAX_REQUESTS_PER_HOUR', 5); // Max. Anfragen pro Stunde
|
||||
|
||||
// Spam-Schutz Einstellungen
|
||||
define('ENABLE_SPAM_PROTECTION', true); // Spam-Schutz aktivieren
|
||||
@@ -87,11 +50,6 @@ 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');
|
||||
@@ -140,24 +98,6 @@ 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
|
||||
@@ -167,13 +107,6 @@ if (!function_exists('validateCSRFToken')) {
|
||||
*/
|
||||
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',
|
||||
@@ -183,6 +116,9 @@ 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,
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
/**
|
||||
* HexaHost.de Produkt-Konfiguration
|
||||
*
|
||||
* Hier können Sie alle Preise und Produktinformationen zentral verwalten.
|
||||
* 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)
|
||||
* Nach Änderungen: npm run build && npm run deploy
|
||||
*
|
||||
* Verwendung in PHP-Seiten:
|
||||
@@ -30,6 +31,7 @@ $PRODUCTS['vpc'] = [
|
||||
'starter' => [
|
||||
'name' => 'VPC Starter',
|
||||
'price' => '4,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
||||
@@ -49,6 +51,7 @@ $PRODUCTS['vpc'] = [
|
||||
'business' => [
|
||||
'name' => 'VPC Business',
|
||||
'price' => '9,99',
|
||||
'shop_url' => '',
|
||||
'featured' => true,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
||||
@@ -69,6 +72,7 @@ $PRODUCTS['vpc'] = [
|
||||
'professional' => [
|
||||
'name' => 'VPC Professional',
|
||||
'price' => '19,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
||||
@@ -90,6 +94,7 @@ $PRODUCTS['vpc'] = [
|
||||
'enterprise' => [
|
||||
'name' => 'VPC Enterprise',
|
||||
'price' => '39,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
||||
@@ -132,6 +137,7 @@ $PRODUCTS['vps'] = [
|
||||
'starter' => [
|
||||
'name' => 'VPS Starter',
|
||||
'price' => '9,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
||||
@@ -151,6 +157,7 @@ $PRODUCTS['vps'] = [
|
||||
'business' => [
|
||||
'name' => 'VPS Business',
|
||||
'price' => '19,99',
|
||||
'shop_url' => '',
|
||||
'featured' => true,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
||||
@@ -171,6 +178,7 @@ $PRODUCTS['vps'] = [
|
||||
'professional' => [
|
||||
'name' => 'VPS Professional',
|
||||
'price' => '39,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
||||
@@ -192,6 +200,7 @@ $PRODUCTS['vps'] = [
|
||||
'enterprise' => [
|
||||
'name' => 'VPS Enterprise',
|
||||
'price' => '79,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
||||
@@ -234,6 +243,7 @@ $PRODUCTS['mail-gateway'] = [
|
||||
'starter' => [
|
||||
'name' => 'Mail Starter',
|
||||
'price' => '4,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'Postfächer', 'value' => '5'],
|
||||
@@ -252,6 +262,7 @@ $PRODUCTS['mail-gateway'] = [
|
||||
'business' => [
|
||||
'name' => 'Mail Business',
|
||||
'price' => '14,99',
|
||||
'shop_url' => '',
|
||||
'featured' => true,
|
||||
'specs' => [
|
||||
['label' => 'Postfächer', 'value' => '25'],
|
||||
@@ -272,6 +283,7 @@ $PRODUCTS['mail-gateway'] = [
|
||||
'professional' => [
|
||||
'name' => 'Mail Professional',
|
||||
'price' => '29,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'Postfächer', 'value' => '100'],
|
||||
@@ -293,6 +305,7 @@ $PRODUCTS['mail-gateway'] = [
|
||||
'enterprise' => [
|
||||
'name' => 'Mail Enterprise',
|
||||
'price' => '59,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'Postfächer', 'value' => 'Unbegrenzt'],
|
||||
@@ -323,94 +336,106 @@ $PRODUCTS['webhosting'] = [
|
||||
'name' => 'Webhosting',
|
||||
'short_name' => 'Webhosting',
|
||||
'description' => 'Klassisches Hosting mit PHP, MySQL und SSL',
|
||||
'min_price' => '1,99',
|
||||
'min_price' => '4,99',
|
||||
'hero_highlight' => 'Alles für Ihre Website',
|
||||
'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.',
|
||||
'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.',
|
||||
'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 PHP, MySQL und SSL-Zertifikaten. Klassisches Hosting für Websites ab 1,99€/Monat bei HexaHost.de',
|
||||
'page_description' => 'Webhosting mit Plesk, PHP und SSL-Zertifikaten. Klassisches Hosting für Websites ab 4,99€/Monat bei HexaHost.de',
|
||||
'packages' => [
|
||||
'starter' => [
|
||||
'name' => 'Webhosting Starter',
|
||||
'price' => '1,99',
|
||||
'price' => '4,99',
|
||||
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-starter',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'Webspace', 'value' => '5 GB'],
|
||||
['label' => 'Domains', 'value' => '1'],
|
||||
['label' => 'E-Mail-Postfächer', 'value' => '5'],
|
||||
['label' => 'Datenbanken', 'value' => '1 MySQL'],
|
||||
['label' => 'Traffic', 'value' => '10 GB'],
|
||||
['label' => 'Webspace', 'value' => '10 GB'],
|
||||
['label' => 'Domains inkl.', 'value' => '1'],
|
||||
['label' => 'Subdomains', 'value' => '5'],
|
||||
['label' => 'Domain-Alias', 'value' => '2'],
|
||||
['label' => 'E-Mail-Postfächer', 'value' => '10'],
|
||||
['label' => 'Datenbanken', 'value' => '2 MySQL'],
|
||||
['label' => 'Traffic', 'value' => '100 GB'],
|
||||
],
|
||||
'features' => [
|
||||
'cPanel/Webmin',
|
||||
'PHP 8.1',
|
||||
'SSL-Zertifikat',
|
||||
'E-Mail-Postfächer',
|
||||
'MySQL Datenbank',
|
||||
'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.',
|
||||
],
|
||||
],
|
||||
'business' => [
|
||||
'name' => 'Webhosting Business',
|
||||
'price' => '4,99',
|
||||
'price' => '7,99',
|
||||
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-business',
|
||||
'featured' => true,
|
||||
'specs' => [
|
||||
['label' => 'Webspace', 'value' => '20 GB'],
|
||||
['label' => 'Domains', 'value' => '5'],
|
||||
['label' => 'E-Mail-Postfächer', 'value' => '25'],
|
||||
['label' => 'Webspace', 'value' => '30 GB'],
|
||||
['label' => 'Domains inkl.', 'value' => '1'],
|
||||
['label' => 'Subdomains', 'value' => '10'],
|
||||
['label' => 'Domain-Alias', 'value' => '2'],
|
||||
['label' => 'E-Mail-Postfächer', 'value' => '20'],
|
||||
['label' => 'Datenbanken', 'value' => '5 MySQL'],
|
||||
['label' => 'Traffic', 'value' => '50 GB'],
|
||||
['label' => 'Traffic', 'value' => '100 GB'],
|
||||
],
|
||||
'features' => [
|
||||
'cPanel/Webmin',
|
||||
'PHP 8.1',
|
||||
'SSL-Zertifikat',
|
||||
'E-Mail-Postfächer',
|
||||
'MySQL Datenbanken',
|
||||
'Backup-Service',
|
||||
'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.',
|
||||
],
|
||||
],
|
||||
'professional' => [
|
||||
'name' => 'Webhosting Professional',
|
||||
'price' => '9,99',
|
||||
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-professional',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'Webspace', 'value' => '50 GB'],
|
||||
['label' => 'Domains', 'value' => 'Unbegrenzt'],
|
||||
['label' => 'Domains inkl.', 'value' => '3'],
|
||||
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
|
||||
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
|
||||
['label' => 'E-Mail-Postfächer', 'value' => '100'],
|
||||
['label' => 'Datenbanken', 'value' => 'Unbegrenzt'],
|
||||
['label' => 'Traffic', 'value' => '200 GB'],
|
||||
['label' => 'Datenbanken', 'value' => '20 MySQL'],
|
||||
['label' => 'Traffic', 'value' => '100 GB'],
|
||||
],
|
||||
'features' => [
|
||||
'cPanel/Webmin',
|
||||
'PHP 8.1',
|
||||
'SSL-Zertifikat',
|
||||
'E-Mail-Postfächer',
|
||||
'MySQL Datenbanken',
|
||||
'Backup-Service',
|
||||
'Priority Support',
|
||||
'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.',
|
||||
],
|
||||
],
|
||||
'enterprise' => [
|
||||
'name' => 'Webhosting Enterprise',
|
||||
'price' => '19,99',
|
||||
'price' => '29,99',
|
||||
'shop_url' => '',
|
||||
'featured' => false,
|
||||
'specs' => [
|
||||
['label' => 'Webspace', 'value' => '100 GB'],
|
||||
['label' => 'Domains', 'value' => 'Unbegrenzt'],
|
||||
['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' => 'Unbegrenzt'],
|
||||
['label' => 'Traffic', 'value' => '500 GB'],
|
||||
['label' => 'Datenbanken', 'value' => '50 MySQL'],
|
||||
['label' => 'Traffic', 'value' => '1 TB'],
|
||||
],
|
||||
'features' => [
|
||||
'cPanel/Webmin',
|
||||
'PHP 8.1',
|
||||
'SSL-Zertifikat',
|
||||
'E-Mail-Postfächer',
|
||||
'MySQL Datenbanken',
|
||||
'Backup-Service',
|
||||
'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.',
|
||||
'Priority Support',
|
||||
'Individuelle Konfiguration',
|
||||
],
|
||||
@@ -418,10 +443,43 @@ $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
|
||||
*/
|
||||
@@ -477,6 +535,38 @@ 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
|
||||
*/
|
||||
@@ -514,7 +604,7 @@ function renderPackageCard($productId, $packageId, $package) {
|
||||
<div class="package-features">
|
||||
%s
|
||||
</div>
|
||||
<a href="contact.php?package=%s-%s" class="btn btn-primary">Jetzt bestellen</a>
|
||||
<a href="%s" class="btn btn-primary">Jetzt bestellen</a>
|
||||
</div>',
|
||||
$featuredClass,
|
||||
$featuredBadge,
|
||||
@@ -522,8 +612,7 @@ function renderPackageCard($productId, $packageId, $package) {
|
||||
$package['price'],
|
||||
$specsHtml,
|
||||
$featuresHtml,
|
||||
$productId,
|
||||
$packageId
|
||||
htmlspecialchars(getOrderUrl($productId, $packageId), ENT_QUOTES, 'UTF-8')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
112
backend/includes/api-helpers.php
Normal file
112
backend/includes/api-helpers.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -15,10 +15,17 @@
|
||||
<div class="footer-section">
|
||||
<h4>Produkte</h4>
|
||||
<ul>
|
||||
<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>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
@@ -160,7 +167,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 $script; ?>" defer></script>
|
||||
<script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</body>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Helper functions for HexaHost.de
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/products-config.php';
|
||||
|
||||
// Sichere Session-Konfiguration
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
// Session-Cookie-Sicherheit
|
||||
@@ -95,4 +97,56 @@ 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;
|
||||
}
|
||||
?>
|
||||
@@ -59,12 +59,12 @@
|
||||
<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, ['vpc', 'vps', 'mail-gateway', 'webhosting'])) ? 'active' : ''; ?>">Produkte</a>
|
||||
<a href="#" class="nav-link <?php echo (in_array($current_page, getVisibleProductPageIds(), true)) ? 'active' : ''; ?>">Produkte</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/vpc" class="<?php echo ($current_page === 'vpc') ? 'active' : ''; ?>">Virtual Private Container</a></li>
|
||||
<li><a href="/vps" class="<?php echo ($current_page === 'vps') ? 'active' : ''; ?>">Virtual Private Server</a></li>
|
||||
<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>
|
||||
<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>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li>
|
||||
|
||||
@@ -1,145 +1,34 @@
|
||||
# HexaHost.de Kontaktformular - Status-Überprüfung
|
||||
# HexaHost.de Kontaktformular - Status
|
||||
|
||||
## ✅ Behobene Probleme
|
||||
## Aktueller Stand
|
||||
|
||||
### 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
|
||||
- Kontaktformular und Handler sind funktionsfähig
|
||||
- Versand erfolgt nativ über PHP `mail()`
|
||||
- Keine PHPMailer-/Composer-Abhängigkeit mehr
|
||||
|
||||
### 2. CSRF-Token Problem
|
||||
- **Problem**: HTML-Formular versuchte PHP-Code zu verwenden
|
||||
- **Lösung**: CSRF-Token durch Honeypot-Feld ersetzt
|
||||
- **Status**: ✅ Behoben
|
||||
## Sicherheitsfunktionen
|
||||
|
||||
### 3. JavaScript-Merge-Konflikt
|
||||
- **Problem**: Merge-Konflikt in contact.js
|
||||
- **Lösung**: Konflikt aufgelöst
|
||||
- **Status**: ✅ Behoben
|
||||
- CSRF-Token-Prüfung
|
||||
- Rate Limiting pro IP
|
||||
- Honeypot gegen Bots
|
||||
- E-Mail-Validierung und Input-Sanitization
|
||||
|
||||
## ⚠️ Noch zu behebende Probleme
|
||||
## Konfiguration für Produktivbetrieb
|
||||
|
||||
### 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
|
||||
Datei: `backend/config/mail-config.php`
|
||||
|
||||
### 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)
|
||||
Zu prüfen:
|
||||
- `SMTP_FROM_EMAIL` ist eine gültige Absenderadresse
|
||||
- `SMTP_TO_EMAIL` ist das richtige Zielpostfach
|
||||
- `mail()` ist beim Hoster aktiv
|
||||
|
||||
## 📧 E-Mail-Funktionalität
|
||||
## Testempfehlung
|
||||
|
||||
### Aktuelle Konfiguration
|
||||
- **SMTP-Host**: smtp.gmail.com
|
||||
- **Port**: 587
|
||||
- **Verschlüsselung**: TLS
|
||||
- **Fallback**: Native PHP mail() Funktion
|
||||
1. `scripts/test-email.php` ausführen
|
||||
2. Kontaktformular über `contact.php` absenden
|
||||
3. Empfang und Darstellung der E-Mail prüfen
|
||||
|
||||
### Sicherheitsfeatures
|
||||
- ✅ Rate Limiting (5 Anfragen/Stunde)
|
||||
- ✅ Honeypot-Schutz
|
||||
- ✅ Input-Sanitization
|
||||
- ✅ E-Mail-Validierung
|
||||
- ✅ Anti-Spam-Headers
|
||||
## Hinweis
|
||||
|
||||
### E-Mail-Templates
|
||||
- ✅ HTML-Template mit HexaHost-Design
|
||||
- ✅ Text-Version als Fallback
|
||||
- ✅ Responsive Design
|
||||
- ✅ Strukturierte Darstellung aller Daten
|
||||
Falls der Versand nicht funktioniert, liegt die Ursache in der Regel an der Server-Mailkonfiguration (MTA/`mail()`), nicht am Formular-Code.
|
||||
|
||||
## 🧪 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.
|
||||
@@ -2,172 +2,54 @@
|
||||
|
||||
## Übersicht
|
||||
|
||||
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.
|
||||
Das Kontaktformular nutzt den nativen PHP-Mailversand über `mail()`.
|
||||
Es wird keine zusätzliche Bibliothek und keine Composer-Installation benötigt.
|
||||
|
||||
## Aktuelle Probleme
|
||||
## Erforderliche Konfiguration
|
||||
|
||||
### 1. ✅ Behoben: Merge-Konflikt in contact-handler.php
|
||||
- Der Git-Merge-Konflikt wurde aufgelöst
|
||||
- Die Datei ist jetzt funktionsfähig
|
||||
Datei: `backend/config/mail-config.php`
|
||||
|
||||
### 2. ⚠️ Zu beheben: SMTP-Konfiguration
|
||||
- Die SMTP-Einstellungen sind noch auf Testwerte gesetzt
|
||||
- Sie müssen mit echten SMTP-Daten konfiguriert werden
|
||||
Mindestens diese Werte müssen korrekt gesetzt sein:
|
||||
|
||||
### 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
|
||||
'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',
|
||||
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
|
||||
define('SMTP_TO_EMAIL', 'info@hexahost.de');
|
||||
```
|
||||
|
||||
### Option 2: Eigener Mail-Server
|
||||
## Voraussetzungen auf dem Server
|
||||
|
||||
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',
|
||||
```
|
||||
- `mail()` muss in der PHP-Umgebung aktiviert sein
|
||||
- Ein Mail Transfer Agent (MTA) bzw. Mailversand beim Hoster muss funktionieren
|
||||
|
||||
### Option 3: Andere E-Mail-Provider
|
||||
## Test der E-Mail-Funktion
|
||||
|
||||
#### 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)**
|
||||
1. Per Script testen:
|
||||
- `scripts/test-email.php`
|
||||
2. Kontaktformular testen:
|
||||
- Seite `contact.php` öffnen
|
||||
- Formular absenden
|
||||
- Empfang im Zielpostfach prüfen
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme:
|
||||
### Meldung: "Mail function not available"
|
||||
- `mail()` ist auf dem Server deaktiviert
|
||||
- Hoster kontaktieren und Mailfunktion aktivieren lassen
|
||||
|
||||
1. **"SMTP connect() failed"**
|
||||
- Überprüfen Sie Host und Port
|
||||
- Prüfen Sie Firewall-Einstellungen
|
||||
### Nachricht kommt nicht an
|
||||
- Spam-Ordner prüfen
|
||||
- Absenderadresse (`SMTP_FROM_EMAIL`) auf gültige Domain setzen
|
||||
- PHP-Error-Log prüfen
|
||||
|
||||
2. **"Authentication failed"**
|
||||
- Überprüfen Sie Benutzername und Passwort
|
||||
- Bei Gmail: App-Passwort verwenden
|
||||
### Versand funktioniert lokal nicht
|
||||
- Unter Windows/Lokalumgebung ist oft kein SMTP in `php.ini` konfiguriert
|
||||
- Auf dem echten Webserver testen
|
||||
|
||||
3. **"Connection timeout"**
|
||||
- Prüfen Sie Internetverbindung
|
||||
- Überprüfen Sie SMTP-Host
|
||||
## Sicherheit
|
||||
|
||||
4. **"Mail function not available"**
|
||||
- PHP mail() Funktion ist deaktiviert
|
||||
- Kontaktieren Sie Ihren Hosting-Provider
|
||||
Das Kontaktformular beinhaltet bereits:
|
||||
- CSRF-Schutz
|
||||
- Rate Limiting
|
||||
- Honeypot-Feld
|
||||
- Serverseitige Validierung und Sanitization
|
||||
|
||||
## 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
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
# Sicherheitsheader
|
||||
<IfModule mod_headers.c>
|
||||
Header always set X-Content-Type-Options nosniff
|
||||
Header always set X-Frame-Options DENY
|
||||
# X-Frame-Options entfernt, da sonst Tag Assistant (Iframe) nicht funktionieren kann.
|
||||
# Absicherung erfolgt stattdessen granular über CSP frame-ancestors.
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
||||
|
||||
# Content Security Policy - Schutz vor XSS und Code-Injection
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://cdn.hexahost.de data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://cdn.hexahost.de https://www.google-analytics.com https://www.googletagmanager.com data:; connect-src 'self' https://www.googletagmanager.com https://www.google.com https://www.google-analytics.com https://region1.google-analytics.com https://stats.g.doubleclick.net; frame-ancestors 'self' https://tagassistant.google.com; base-uri 'self'; form-action 'self'"
|
||||
|
||||
# Strict-Transport-Security (HSTS) - Erzwingt HTTPS
|
||||
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
@@ -51,15 +52,6 @@
|
||||
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>
|
||||
@@ -76,11 +68,6 @@
|
||||
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
|
||||
|
||||
@@ -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 Deutschland anzubieten. Als regionales
|
||||
und preiswertes Hosting und IT-Lösungen direkt aus Bayern anzubieten. Als regionales
|
||||
Unternehmen aus Niederbayern verstehe ich die Bedürfnisse meiner Kunden
|
||||
und bieten persönlichen Support.
|
||||
und biete persönlichen Support.
|
||||
</p>
|
||||
<p>
|
||||
Meine Expertise liegt in der Bereitstellung moderner Hosting- und IT-Lösungen
|
||||
|
||||
@@ -15,3 +15,156 @@
|
||||
justify-content: center;
|
||||
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-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;
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.it-services-page .values .value-item {
|
||||
flex-basis: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
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
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
5
public/config/mail-config.php
Normal file
5
public/config/mail-config.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* Kompatibilitäts-Wrapper – leitet auf die zentrale Backend-Konfiguration um.
|
||||
*/
|
||||
require_once __DIR__ . '/../../backend/config/mail-config.php';
|
||||
@@ -1,58 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaHost.de Contact Form Handler
|
||||
* E-Mail-Verarbeitung mit SMTP-Integration und Spam-Schutz
|
||||
* E-Mail-Verarbeitung mit nativer PHP-mail()-Funktion und Spam-Schutz
|
||||
*/
|
||||
|
||||
// Session starten für CSRF-Validierung
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
require_once __DIR__ . '/../backend/config/mail-config.php';
|
||||
require_once __DIR__ . '/../backend/config/contact-config.php';
|
||||
|
||||
// Konfiguration laden
|
||||
require_once 'config/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();
|
||||
|
||||
// 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 Domain erlauben)
|
||||
$allowed_origins = [
|
||||
'https://hexahost.de',
|
||||
'https://www.hexahost.de',
|
||||
'http://localhost', // Für Entwicklung
|
||||
'http://127.0.0.1' // Für Entwicklung
|
||||
'https://dev.hexahost.de',
|
||||
'http://localhost',
|
||||
'http://127.0.0.1',
|
||||
];
|
||||
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if (in_array($origin, $allowed_origins)) {
|
||||
if (in_array($origin, $allowed_origins, true)) {
|
||||
header('Access-Control-Allow-Origin: ' . $origin);
|
||||
}
|
||||
|
||||
@@ -60,178 +28,97 @@ 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' => []];
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
if (count($data['requests']) >= $config['max_requests_per_hour']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Füge aktuellen Request hinzu
|
||||
$data = isset($data) ? $data : ['requests' => []];
|
||||
$data['requests'][] = $current_time;
|
||||
file_put_contents($cache_file, json_encode($data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// E-Mail-Validierung
|
||||
function validateEmail($email) {
|
||||
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
// Input-Sanitization
|
||||
function sanitizeInput($input) {
|
||||
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// PHPMailer laden (falls verfügbar)
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
// Fallback: Native PHP mail() Funktion
|
||||
return sendEmailNative($data);
|
||||
$handle = @fopen($cache_file, 'c+');
|
||||
if ($handle === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
if (!flock($handle, LOCK_EX)) {
|
||||
return 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';
|
||||
$contents = stream_get_contents($handle);
|
||||
if ($contents !== false && $contents !== '') {
|
||||
$decoded = json_decode($contents, true);
|
||||
if (is_array($decoded) && isset($decoded['requests'])) {
|
||||
$data = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
// Absender
|
||||
$mail->setFrom($config['from_email'], $config['from_name']);
|
||||
$mail->addReplyTo($data['email'], $data['firstName'] . ' ' . $data['lastName']);
|
||||
$data['requests'] = array_values(array_filter(
|
||||
$data['requests'],
|
||||
static fn($timestamp) => ($current_time - (int) $timestamp) < 3600
|
||||
));
|
||||
|
||||
// Empfänger
|
||||
$mail->addAddress($config['to_email'], $config['to_name']);
|
||||
if (count($data['requests']) >= $config['max_requests_per_hour']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
$data['requests'][] = $current_time;
|
||||
ftruncate($handle, 0);
|
||||
rewind($handle);
|
||||
fwrite($handle, json_encode($data));
|
||||
} finally {
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: Native PHP mail() Funktion
|
||||
function sendEmailNative($data) {
|
||||
function checkHoneypot($data) {
|
||||
global $config;
|
||||
$honeypot_field = $config['honeypot_field'];
|
||||
return empty($data[$honeypot_field]);
|
||||
}
|
||||
|
||||
function sanitizeFormField($input) {
|
||||
return strip_tags(trim((string) $input));
|
||||
}
|
||||
|
||||
function getSubjectLabel($subjectKey) {
|
||||
$map = getContactSubjectMap();
|
||||
return $map[$subjectKey] ?? 'Neue Kontaktanfrage';
|
||||
}
|
||||
|
||||
function sendEmail($data) {
|
||||
global $config;
|
||||
|
||||
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
|
||||
$subject = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
|
||||
$subject = '[HexaHost.de] ' . $subject;
|
||||
$subject = '[HexaHost.de] ' . getSubjectLabel($data['subject']);
|
||||
$replyName = sanitizeHeaderValue($data['firstName'] . ' ' . $data['lastName']);
|
||||
$replyEmail = sanitizeHeaderValue($data['email']);
|
||||
|
||||
// Headers für Spam-Schutz
|
||||
$headers = [
|
||||
'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>',
|
||||
'Reply-To: ' . $data['firstName'] . ' ' . $data['lastName'] . ' <' . $data['email'] . '>',
|
||||
'Reply-To: ' . $replyName . ' <' . $replyEmail . '>',
|
||||
'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',
|
||||
];
|
||||
|
||||
$message = generateEmailHTML($data);
|
||||
|
||||
return mail($config['to_email'], $subject, $message, implode("\r\n", $headers));
|
||||
// Native PHP Mailversand ohne externe Libraries
|
||||
return mail($config['to_email'], $subject, generateEmailHTML($data), implode("\r\n", $headers));
|
||||
}
|
||||
|
||||
// HTML E-Mail-Template
|
||||
function generateEmailHTML($data) {
|
||||
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
|
||||
$subject_text = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
|
||||
$subject_text = htmlspecialchars(getSubjectLabel($data['subject']), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$html = '
|
||||
<!DOCTYPE html>
|
||||
@@ -267,19 +154,19 @@ function generateEmailHTML($data) {
|
||||
|
||||
<div class="field">
|
||||
<div class="label">Name:</div>
|
||||
<div class="value">' . $data['firstName'] . ' ' . $data['lastName'] . '</div>
|
||||
<div class="value">' . htmlspecialchars($data['firstName'] . ' ' . $data['lastName'], ENT_QUOTES, 'UTF-8') . '</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">E-Mail:</div>
|
||||
<div class="value">' . $data['email'] . '</div>
|
||||
<div class="value">' . htmlspecialchars($data['email'], ENT_QUOTES, 'UTF-8') . '</div>
|
||||
</div>';
|
||||
|
||||
if (!empty($data['phone'])) {
|
||||
$html .= '
|
||||
<div class="field">
|
||||
<div class="label">Telefon:</div>
|
||||
<div class="value">' . $data['phone'] . '</div>
|
||||
<div class="value">' . htmlspecialchars($data['phone'], ENT_QUOTES, 'UTF-8') . '</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
@@ -287,19 +174,19 @@ function generateEmailHTML($data) {
|
||||
$html .= '
|
||||
<div class="field">
|
||||
<div class="label">Unternehmen:</div>
|
||||
<div class="value">' . $data['company'] . '</div>
|
||||
<div class="value">' . htmlspecialchars($data['company'], ENT_QUOTES, 'UTF-8') . '</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
$html .= '
|
||||
<div class="field">
|
||||
<div class="label">Nachricht:</div>
|
||||
<div class="message">' . nl2br($data['message']) . '</div>
|
||||
<div class="message">' . nl2br(htmlspecialchars($data['message'], ENT_QUOTES, 'UTF-8')) . '</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">IP-Adresse:</div>
|
||||
<div class="value">' . htmlspecialchars(getClientIP()) . '</div>
|
||||
<div class="value">' . htmlspecialchars(getClientIP(), ENT_QUOTES, 'UTF-8') . '</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -319,14 +206,10 @@ 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: " . $subject_text . "\n";
|
||||
$text .= "Betreff: " . getSubjectLabel($data['subject']) . "\n";
|
||||
$text .= "Name: " . $data['firstName'] . " " . $data['lastName'] . "\n";
|
||||
$text .= "E-Mail: " . $data['email'] . "\n";
|
||||
|
||||
@@ -341,10 +224,8 @@ 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";
|
||||
@@ -352,40 +233,33 @@ function generateEmailText($data) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
// Hauptverarbeitung
|
||||
try {
|
||||
// CSRF-Token validieren
|
||||
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 (!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;
|
||||
}
|
||||
}
|
||||
|
||||
// Rate Limiting Check
|
||||
$client_ip = getClientIP();
|
||||
if (!checkRateLimit($client_ip)) {
|
||||
if (!checkRateLimit(getClientIP())) {
|
||||
http_response_code(429);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.'
|
||||
'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.'
|
||||
]);
|
||||
echo json_encode(['success' => false, 'message' => 'Ungültige Anfrage.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Pflichtfelder prüfen
|
||||
$required_fields = ['firstName', 'lastName', 'email', 'subject', 'message', 'privacy'];
|
||||
$missing_fields = [];
|
||||
|
||||
@@ -400,53 +274,87 @@ try {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Bitte füllen Sie alle Pflichtfelder aus.',
|
||||
'missing_fields' => $missing_fields
|
||||
'missing_fields' => $missing_fields,
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// E-Mail-Validierung
|
||||
if (!validateEmail($_POST['email'])) {
|
||||
$subjectKey = trim((string) $_POST['subject']);
|
||||
if (!isAllowedContactSubject($subjectKey)) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein.'
|
||||
'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.',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Daten sanitieren
|
||||
$data = [
|
||||
'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
|
||||
'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']),
|
||||
];
|
||||
|
||||
// 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.',
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
require_once __DIR__ . '/../backend/config/contact-config.php';
|
||||
|
||||
$preselected_subject = getPreselectedContactSubject();
|
||||
|
||||
// Page configuration
|
||||
$page_title = 'Kontakt - HexaHost.de | Hosting aus Niederbayern';
|
||||
@@ -129,21 +132,9 @@ 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>
|
||||
<option value="allgemeine-anfrage">Allgemeine Anfrage</option>
|
||||
<option value="vpc-anfrage">Virtual Private Container</option>
|
||||
<option value="vps-anfrage">Virtual Private Server</option>
|
||||
<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>
|
||||
<?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; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -54,7 +54,7 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</p>
|
||||
</div>
|
||||
<div class="products-grid">
|
||||
<div class="product-card glass-card">
|
||||
<div class="product-card glass-card"<?php echo productHiddenAttr('vpc'); ?>>
|
||||
<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 +74,7 @@ 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">
|
||||
<div class="product-card glass-card"<?php echo productHiddenAttr('vps'); ?>>
|
||||
<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 +92,7 @@ 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">
|
||||
<div class="product-card glass-card"<?php echo productHiddenAttr('mail-gateway'); ?>>
|
||||
<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"/>
|
||||
|
||||
@@ -10,7 +10,7 @@ $current_page = 'it-dienstleistungen';
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
<main id="main-content">
|
||||
<main id="main-content" class="it-services-page">
|
||||
<!-- Services Hero -->
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
|
||||
@@ -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="contact.php?product=mail-gateway" class="btn btn-primary">Jetzt bestellen</a>
|
||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,43 +2,43 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://hexahost.de/</loc>
|
||||
<lastmod>2024-01-01</lastmod>
|
||||
<lastmod>2026-05-28</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://hexahost.de/vpc.html</loc>
|
||||
<lastmod>2024-01-01</lastmod>
|
||||
<lastmod>2026-05-28</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://hexahost.de/vps.html</loc>
|
||||
<lastmod>2024-01-01</lastmod>
|
||||
<lastmod>2026-05-28</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://hexahost.de/mail-gateway.html</loc>
|
||||
<lastmod>2024-01-01</lastmod>
|
||||
<lastmod>2026-05-28</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://hexahost.de/webhosting.html</loc>
|
||||
<lastmod>2024-01-01</lastmod>
|
||||
<lastmod>2026-05-28</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://hexahost.de/about.html</loc>
|
||||
<lastmod>2024-01-01</lastmod>
|
||||
<lastmod>2026-05-28</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://hexahost.de/contact.html</loc>
|
||||
<lastmod>2024-01-01</lastmod>
|
||||
<lastmod>2026-05-28</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
|
||||
@@ -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="contact.php?product=vpc" class="btn btn-primary">Jetzt bestellen</a>
|
||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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="contact.php?product=vps" class="btn btn-primary">Jetzt bestellen</a>
|
||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
<polyline points="10,9 9,9 8,9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3>cPanel/Webmin</h3>
|
||||
<h3>Plesk</h3>
|
||||
<p>Benutzerfreundliche Verwaltungsoberfläche für einfache Website-Verwaltung und E-Mail-Konfiguration.</p>
|
||||
</div>
|
||||
<div class="detail-card glass-card">
|
||||
@@ -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="contact.php?product=webhosting" class="btn btn-primary">Jetzt bestellen</a>
|
||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
BIN
scripts/__pycache__/obfuscate_release.cpython-314.pyc
Normal file
BIN
scripts/__pycache__/obfuscate_release.cpython-314.pyc
Normal file
Binary file not shown.
463
scripts/obfuscate_release.py
Normal file
463
scripts/obfuscate_release.py
Normal file
@@ -0,0 +1,463 @@
|
||||
#!/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())
|
||||
17
scripts/setup-git-hooks.ps1
Normal file
17
scripts/setup-git-hooks.ps1
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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
|
||||
}
|
||||
@@ -1,72 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaHost.de E-Mail Test
|
||||
* Testet die E-Mail-Funktionalität ohne PHPMailer
|
||||
* HexaHost.de E-Mail Test (nur CLI oder lokale Entwicklung)
|
||||
*/
|
||||
|
||||
// Konfiguration laden
|
||||
require_once 'config.php';
|
||||
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';
|
||||
|
||||
// Test-E-Mail senden
|
||||
function testEmail() {
|
||||
$config = getHexaHostConfig();
|
||||
|
||||
// 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.'
|
||||
];
|
||||
|
||||
// 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'
|
||||
'X-Mailer: HexaHost Test Email',
|
||||
];
|
||||
|
||||
// E-Mail senden
|
||||
$result = mail($config['to_email'], $subject, $message, implode("\r\n", $headers));
|
||||
|
||||
return $result;
|
||||
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;
|
||||
}
|
||||
|
||||
// Test ausführen
|
||||
if (isset($_GET['test'])) {
|
||||
$result = testEmail();
|
||||
|
||||
if ($result) {
|
||||
echo "✅ Test-E-Mail wurde erfolgreich gesendet!";
|
||||
} else {
|
||||
echo "❌ Fehler beim Senden der Test-E-Mail.";
|
||||
}
|
||||
echo testEmail()
|
||||
? 'Test-E-Mail wurde gesendet.'
|
||||
: 'Fehler beim Senden der Test-E-Mail.';
|
||||
} else {
|
||||
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>";
|
||||
echo '<h1>HexaHost.de E-Mail Test</h1>';
|
||||
echo '<p><a href="?test=1">Test-E-Mail senden</a></p>';
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user