mirror of
https://git.hexahost.dev/smueller/HexaHost-Frontend.git
synced 2026-06-02 09:18:43 +00:00
Compare commits
55 Commits
d34dbbb079
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d54cea5ec | ||
|
|
fdd0367281 | ||
|
|
ded8778b6c | ||
|
|
7a03f5aa1b | ||
|
|
f7ea36f4f2 | ||
|
|
99f0056106 | ||
|
|
e9d5b55459 | ||
|
|
8f985da61f | ||
|
|
e91a9ed9c3 | ||
|
|
76aceddcca | ||
|
|
d7851763f7 | ||
|
|
1c0a3ff468 | ||
|
|
6c9114e0a7 | ||
|
|
4b9940c18b | ||
|
|
24a852aab5 | ||
|
|
219f1d2fcf | ||
|
|
06a932a048 | ||
|
|
f4947d5e25 | ||
|
|
f097da7eb1 | ||
|
|
b4b1dde484 | ||
|
|
481d223747 | ||
|
|
45a7067878 | ||
|
|
4787d7b770 | ||
|
|
a0aa8b12ca | ||
|
|
b113bdeaa2 | ||
|
|
5d2be60dfa | ||
|
|
62d0076799 | ||
|
|
e920fdfc8e | ||
|
|
5d953fda7b | ||
|
|
6ca4786955 | ||
|
|
b9bd339607 | ||
|
|
b893272d64 | ||
|
|
3dd707ab93 | ||
|
|
67fbc68d45 | ||
|
|
cc1a48943a | ||
|
|
dfc781f3ed | ||
|
|
d44aaa197b | ||
|
|
ebf6f82bb6 | ||
|
|
d0e5baa443 | ||
|
|
8afba16905 | ||
|
|
96a5977283 | ||
|
|
ec8686761c | ||
|
|
d3da589a1d | ||
|
|
b6e268855e | ||
|
|
d02377c735 | ||
|
|
d62d6b576d | ||
|
|
2c0138f55d | ||
|
|
2074707c9d | ||
|
|
55f9fdd957 | ||
|
|
ab81d1c49f | ||
|
|
b40ad53d9c | ||
|
|
e5402189ea | ||
|
|
e544720900 | ||
| a5bba86db0 | |||
|
|
5f5be4a4cb |
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>
|
||||
@@ -1,53 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaDNS - DNS Lookup API
|
||||
*
|
||||
* Führt echte DNS-Abfragen durch und gibt die Ergebnisse als JSON zurück.
|
||||
*
|
||||
* Verwendung: GET /api/dns-lookup.php?domain=example.com
|
||||
*/
|
||||
|
||||
// CORS Headers für Frontend-Zugriff
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
// Preflight request handling
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
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 (!checkApiRateLimit('dns-lookup')) {
|
||||
rejectApiRateLimit();
|
||||
}
|
||||
|
||||
if (empty($domain)) {
|
||||
$domain = getValidatedDomainParam();
|
||||
|
||||
if ($domain === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Domain-Parameter fehlt']);
|
||||
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Domain validieren (einfache Prüfung)
|
||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// DNS-Abfrage durchführen
|
||||
$startTime = microtime(true);
|
||||
$result = performDnsLookup($domain);
|
||||
$queryTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
// Ergebnis zurückgeben
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'domain' => $domain,
|
||||
@@ -56,9 +53,9 @@ echo json_encode([
|
||||
'records' => $result
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
/**
|
||||
* Führt DNS-Lookup für verschiedene Record-Typen durch
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function performDnsLookup(string $domain): array {
|
||||
$records = [
|
||||
'A' => [],
|
||||
@@ -70,7 +67,7 @@ function performDnsLookup(string $domain): array {
|
||||
'SOA' => []
|
||||
];
|
||||
|
||||
// A-Records (IPv4)
|
||||
|
||||
$aRecords = @dns_get_record($domain, DNS_A);
|
||||
if ($aRecords) {
|
||||
foreach ($aRecords as $record) {
|
||||
@@ -81,7 +78,7 @@ function performDnsLookup(string $domain): array {
|
||||
}
|
||||
}
|
||||
|
||||
// AAAA-Records (IPv6)
|
||||
|
||||
$aaaaRecords = @dns_get_record($domain, DNS_AAAA);
|
||||
if ($aaaaRecords) {
|
||||
foreach ($aaaaRecords as $record) {
|
||||
@@ -92,7 +89,7 @@ function performDnsLookup(string $domain): array {
|
||||
}
|
||||
}
|
||||
|
||||
// MX-Records (Mail)
|
||||
|
||||
$mxRecords = @dns_get_record($domain, DNS_MX);
|
||||
if ($mxRecords) {
|
||||
foreach ($mxRecords as $record) {
|
||||
@@ -102,11 +99,11 @@ function performDnsLookup(string $domain): array {
|
||||
'ttl' => $record['ttl']
|
||||
];
|
||||
}
|
||||
// Nach Priorität sortieren
|
||||
|
||||
usort($records['MX'], fn($a, $b) => $a['priority'] <=> $b['priority']);
|
||||
}
|
||||
|
||||
// NS-Records (Nameserver)
|
||||
|
||||
$nsRecords = @dns_get_record($domain, DNS_NS);
|
||||
if ($nsRecords) {
|
||||
foreach ($nsRecords as $record) {
|
||||
@@ -117,7 +114,7 @@ function performDnsLookup(string $domain): array {
|
||||
}
|
||||
}
|
||||
|
||||
// TXT-Records
|
||||
|
||||
$txtRecords = @dns_get_record($domain, DNS_TXT);
|
||||
if ($txtRecords) {
|
||||
foreach ($txtRecords as $record) {
|
||||
@@ -128,7 +125,7 @@ function performDnsLookup(string $domain): array {
|
||||
}
|
||||
}
|
||||
|
||||
// CNAME-Records
|
||||
|
||||
$cnameRecords = @dns_get_record($domain, DNS_CNAME);
|
||||
if ($cnameRecords) {
|
||||
foreach ($cnameRecords as $record) {
|
||||
@@ -139,7 +136,7 @@ function performDnsLookup(string $domain): array {
|
||||
}
|
||||
}
|
||||
|
||||
// SOA-Record (Start of Authority)
|
||||
|
||||
$soaRecords = @dns_get_record($domain, DNS_SOA);
|
||||
if ($soaRecords) {
|
||||
foreach ($soaRecords as $record) {
|
||||
@@ -156,6 +153,6 @@ function performDnsLookup(string $domain): array {
|
||||
}
|
||||
}
|
||||
|
||||
// Leere Arrays entfernen
|
||||
|
||||
return array_filter($records, fn($arr) => !empty($arr));
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaDNS - DNS Propagation Check API
|
||||
*
|
||||
* Prüft DNS-Records bei verschiedenen öffentlichen DNS-Servern
|
||||
*
|
||||
* 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: *');
|
||||
@@ -17,7 +19,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Öffentliche DNS-Server für Propagation-Check
|
||||
if (!checkApiRateLimit('dns-propagation')) {
|
||||
rejectApiRateLimit();
|
||||
}
|
||||
|
||||
|
||||
$dnsServers = [
|
||||
['name' => 'Google', 'ip' => '8.8.8.8', 'location' => 'Global'],
|
||||
['name' => 'Google Secondary', 'ip' => '8.8.4.4', 'location' => 'Global'],
|
||||
@@ -29,22 +35,16 @@ $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']);
|
||||
echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Ungültiges Domain-Format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Erlaubte Record-Typen
|
||||
$allowedTypes = ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME'];
|
||||
if (!in_array($type, $allowedTypes)) {
|
||||
$type = 'A';
|
||||
@@ -65,7 +65,7 @@ foreach ($dnsServers as $server) {
|
||||
|
||||
$queryStart = microtime(true);
|
||||
|
||||
// DNS-Abfrage mit spezifischem Server via dig (falls verfügbar) oder dns_get_record
|
||||
|
||||
$records = queryDnsServer($domain, $type, $server['ip']);
|
||||
|
||||
$serverResult['response_time'] = round((microtime(true) - $queryStart) * 1000, 2);
|
||||
@@ -80,7 +80,7 @@ foreach ($dnsServers as $server) {
|
||||
|
||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
// Propagation-Status berechnen
|
||||
|
||||
$propagationStatus = calculatePropagationStatus($results);
|
||||
|
||||
echo json_encode([
|
||||
@@ -93,14 +93,19 @@ echo json_encode([
|
||||
'servers' => $results
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
/**
|
||||
* DNS-Abfrage bei spezifischem Server
|
||||
*/
|
||||
|
||||
|
||||
|
||||
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)));
|
||||
@@ -110,7 +115,7 @@ function queryDnsServer(string $domain, string $type, string $server): array {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Fallback auf PHP dns_get_record (verwendet System-DNS)
|
||||
|
||||
$dnsType = constant('DNS_' . $type);
|
||||
$result = @dns_get_record($domain, $dnsType);
|
||||
|
||||
@@ -140,9 +145,9 @@ function queryDnsServer(string $domain, string $type, string $server): array {
|
||||
return array_filter($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Propagation-Status
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function calculatePropagationStatus(array $results): array {
|
||||
$totalServers = count($results);
|
||||
$serversWithRecords = 0;
|
||||
@@ -157,10 +162,10 @@ function calculatePropagationStatus(array $results): array {
|
||||
}
|
||||
}
|
||||
|
||||
// Einzigartige Records
|
||||
|
||||
$uniqueRecords = array_unique($allRecords);
|
||||
|
||||
// Konsistenz prüfen (haben alle Server die gleichen Records?)
|
||||
|
||||
$isConsistent = count($uniqueRecords) <= 1 || $serversWithRecords === 0;
|
||||
|
||||
$percentage = $totalServers > 0 ? round(($serversWithRecords / $totalServers) * 100) : 0;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaDNS - Ping/Verfügbarkeitstest API
|
||||
*
|
||||
* Prüft die Erreichbarkeit einer Domain
|
||||
*
|
||||
* 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: *');
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -47,9 +43,9 @@ echo json_encode([
|
||||
'results' => $results
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
/**
|
||||
* Führt verschiedene Erreichbarkeitstests durch
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function performConnectivityCheck(string $domain): array {
|
||||
$results = [
|
||||
'dns_resolution' => checkDnsResolution($domain),
|
||||
@@ -59,7 +55,7 @@ function performConnectivityCheck(string $domain): array {
|
||||
'overall_status' => 'offline'
|
||||
];
|
||||
|
||||
// Overall-Status bestimmen
|
||||
|
||||
if ($results['https']['reachable'] || $results['http']['reachable']) {
|
||||
$results['overall_status'] = 'online';
|
||||
} elseif ($results['icmp_ping']['reachable']) {
|
||||
@@ -71,9 +67,9 @@ function performConnectivityCheck(string $domain): array {
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* DNS-Auflösung prüfen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function checkDnsResolution(string $domain): array {
|
||||
$start = microtime(true);
|
||||
$ip = gethostbyname($domain);
|
||||
@@ -88,9 +84,9 @@ function checkDnsResolution(string $domain): array {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ICMP Ping (falls verfügbar)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function checkIcmpPing(string $domain): array {
|
||||
$result = [
|
||||
'reachable' => false,
|
||||
@@ -98,17 +94,18 @@ function checkIcmpPing(string $domain): array {
|
||||
'packet_loss' => null
|
||||
];
|
||||
|
||||
// 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
|
||||
|
||||
if (preg_match('/(\d+)% packet loss/', $pingResult, $lossMatch)) {
|
||||
$result['packet_loss'] = (int)$lossMatch[1];
|
||||
$result['reachable'] = $result['packet_loss'] < 100;
|
||||
}
|
||||
|
||||
// Durchschnittliche Zeit extrahieren
|
||||
|
||||
if (preg_match('/avg.*?=.*?[\d.]+\/([\d.]+)\//', $pingResult, $timeMatch)) {
|
||||
$result['response_time_ms'] = (float)$timeMatch[1];
|
||||
} elseif (preg_match('/time[=<]([\d.]+)\s*ms/', $pingResult, $timeMatch)) {
|
||||
@@ -119,9 +116,9 @@ function checkIcmpPing(string $domain): array {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP(S)-Verbindung prüfen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function checkHttpConnection(string $domain, bool $https = false): array {
|
||||
$protocol = $https ? 'https' : 'http';
|
||||
$port = $https ? 443 : 80;
|
||||
@@ -137,7 +134,7 @@ function checkHttpConnection(string $domain, bool $https = false): array {
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
// cURL verwenden
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
@@ -159,13 +156,13 @@ function checkHttpConnection(string $domain, bool $https = false): array {
|
||||
$result['status_code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$result['reachable'] = $result['status_code'] > 0;
|
||||
|
||||
// Redirect-URL
|
||||
|
||||
$redirectUrl = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
|
||||
if (!empty($redirectUrl)) {
|
||||
$result['redirect_url'] = $redirectUrl;
|
||||
}
|
||||
|
||||
// Server-Header
|
||||
|
||||
if (preg_match('/Server:\s*([^\r\n]+)/i', $response, $serverMatch)) {
|
||||
$result['server'] = trim($serverMatch[1]);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaDNS - Reverse DNS Lookup API
|
||||
*
|
||||
* Löst eine IP-Adresse zu einem Hostnamen auf
|
||||
*
|
||||
* 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: *');
|
||||
@@ -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)) {
|
||||
@@ -25,7 +31,7 @@ if (empty($ip)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// IPv4 oder IPv6 validieren
|
||||
|
||||
$isIPv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||
$isIPv6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||
|
||||
@@ -48,9 +54,9 @@ echo json_encode([
|
||||
'result' => $result
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
/**
|
||||
* Führt Reverse DNS Lookup durch
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function performReverseLookup(string $ip, string $version): array {
|
||||
$result = [
|
||||
'hostname' => null,
|
||||
@@ -58,44 +64,44 @@ function performReverseLookup(string $ip, string $version): array {
|
||||
'additional_info' => []
|
||||
];
|
||||
|
||||
// PHP gethostbyaddr
|
||||
|
||||
$hostname = @gethostbyaddr($ip);
|
||||
|
||||
if ($hostname && $hostname !== $ip) {
|
||||
$result['hostname'] = $hostname;
|
||||
|
||||
// Verifizieren durch Forward-Lookup
|
||||
|
||||
$forwardIp = gethostbyname($hostname);
|
||||
$result['forward_verified'] = ($forwardIp === $ip);
|
||||
|
||||
// Zusätzliche Infos über den Host sammeln
|
||||
|
||||
$result['additional_info'] = getHostInfo($hostname);
|
||||
} else {
|
||||
$result['error'] = 'Kein PTR-Record gefunden';
|
||||
}
|
||||
|
||||
// PTR-Record direkt abfragen
|
||||
|
||||
$ptrRecord = getPtrRecord($ip, $version);
|
||||
if ($ptrRecord) {
|
||||
$result['ptr_record'] = $ptrRecord;
|
||||
}
|
||||
|
||||
// IP-Info (GeoIP wenn verfügbar, sonst Basic-Infos)
|
||||
|
||||
$result['ip_info'] = getIpInfo($ip);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* PTR-Record direkt abfragen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getPtrRecord(string $ip, string $version): ?string {
|
||||
if ($version === 'IPv4') {
|
||||
// IPv4: Reverse die Oktette
|
||||
|
||||
$parts = array_reverse(explode('.', $ip));
|
||||
$ptrDomain = implode('.', $parts) . '.in-addr.arpa';
|
||||
} else {
|
||||
// IPv6: Komplexer - jedes Nibble umkehren
|
||||
|
||||
$expanded = expandIPv6($ip);
|
||||
$nibbles = str_replace(':', '', $expanded);
|
||||
$reversed = implode('.', array_reverse(str_split($nibbles)));
|
||||
@@ -111,11 +117,11 @@ function getPtrRecord(string $ip, string $version): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expandiert eine IPv6-Adresse
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function expandIPv6(string $ip): string {
|
||||
// Ersetze :: mit der richtigen Anzahl von 0000
|
||||
|
||||
if (strpos($ip, '::') !== false) {
|
||||
$parts = explode('::', $ip);
|
||||
$left = $parts[0] ? explode(':', $parts[0]) : [];
|
||||
@@ -127,7 +133,7 @@ function expandIPv6(string $ip): string {
|
||||
$all = explode(':', $ip);
|
||||
}
|
||||
|
||||
// Jedes Segment auf 4 Zeichen auffüllen
|
||||
|
||||
$all = array_map(function($segment) {
|
||||
return str_pad($segment, 4, '0', STR_PAD_LEFT);
|
||||
}, $all);
|
||||
@@ -135,19 +141,19 @@ function expandIPv6(string $ip): string {
|
||||
return implode(':', $all);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sammelt Infos über einen Hostnamen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getHostInfo(string $hostname): array {
|
||||
$info = [];
|
||||
|
||||
// Domain-Teile analysieren
|
||||
|
||||
$parts = explode('.', $hostname);
|
||||
$tld = end($parts);
|
||||
|
||||
$info['tld'] = $tld;
|
||||
|
||||
// Bekannte Hosting-Provider erkennen
|
||||
|
||||
$providerPatterns = [
|
||||
'amazonaws.com' => 'Amazon AWS',
|
||||
'googleusercontent.com' => 'Google Cloud',
|
||||
@@ -177,15 +183,15 @@ function getHostInfo(string $hostname): array {
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basis-Infos zur IP
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getIpInfo(string $ip): array {
|
||||
$info = [
|
||||
'type' => 'unknown'
|
||||
];
|
||||
|
||||
// Private IP-Bereiche prüfen
|
||||
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
if (preg_match('/^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/', $ip)) {
|
||||
$info['type'] = 'private';
|
||||
@@ -195,7 +201,7 @@ function getIpInfo(string $ip): array {
|
||||
$info['type'] = 'public';
|
||||
}
|
||||
} else {
|
||||
// IPv6
|
||||
|
||||
if (preg_match('/^(fc|fd)/i', $ip)) {
|
||||
$info['type'] = 'private';
|
||||
} elseif (preg_match('/^::1$/', $ip) || preg_match('/^fe80:/i', $ip)) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaDNS - SSL Certificate Check API
|
||||
*
|
||||
* Prüft SSL-Zertifikat-Informationen einer Domain
|
||||
*
|
||||
* 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: *');
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -48,9 +43,9 @@ echo json_encode([
|
||||
'ssl' => $sslData
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
/**
|
||||
* Prüft SSL-Zertifikat einer Domain
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function checkSslCertificate(string $domain): array {
|
||||
$result = [
|
||||
'success' => false,
|
||||
@@ -60,7 +55,7 @@ function checkSslCertificate(string $domain): array {
|
||||
'certificate' => null
|
||||
];
|
||||
|
||||
// Stream Context für SSL
|
||||
|
||||
$context = stream_context_create([
|
||||
'ssl' => [
|
||||
'capture_peer_cert' => true,
|
||||
@@ -69,18 +64,18 @@ function checkSslCertificate(string $domain): array {
|
||||
]
|
||||
]);
|
||||
|
||||
// Verbindung herstellen
|
||||
|
||||
$socket = @stream_socket_client(
|
||||
"ssl://{$domain}:443",
|
||||
$errno,
|
||||
$errstr,
|
||||
10, // Timeout
|
||||
10,
|
||||
STREAM_CLIENT_CONNECT,
|
||||
$context
|
||||
);
|
||||
|
||||
if (!$socket) {
|
||||
// Versuche ohne SSL (um zu prüfen ob Server erreichbar)
|
||||
|
||||
$httpSocket = @fsockopen($domain, 80, $errno, $errstr, 5);
|
||||
if ($httpSocket) {
|
||||
fclose($httpSocket);
|
||||
@@ -94,7 +89,7 @@ function checkSslCertificate(string $domain): array {
|
||||
$result['has_ssl'] = true;
|
||||
$result['success'] = true;
|
||||
|
||||
// Zertifikat extrahieren
|
||||
|
||||
$params = stream_context_get_params($socket);
|
||||
$cert = $params['options']['ssl']['peer_certificate'] ?? null;
|
||||
|
||||
@@ -110,10 +105,10 @@ function checkSslCertificate(string $domain): array {
|
||||
$isNotYetValid = $now < $validFrom;
|
||||
$result['is_valid'] = !$isExpired && !$isNotYetValid;
|
||||
|
||||
// Tage bis Ablauf
|
||||
|
||||
$daysUntilExpiry = floor(($validTo - $now) / 86400);
|
||||
|
||||
// Subject Alternative Names (SANs)
|
||||
|
||||
$sans = [];
|
||||
if (isset($certInfo['extensions']['subjectAltName'])) {
|
||||
$sanStr = $certInfo['extensions']['subjectAltName'];
|
||||
@@ -121,7 +116,7 @@ function checkSslCertificate(string $domain): array {
|
||||
$sans = $matches[1] ?? [];
|
||||
}
|
||||
|
||||
// Issuer aufbereiten
|
||||
|
||||
$issuer = [];
|
||||
if (isset($certInfo['issuer'])) {
|
||||
if (isset($certInfo['issuer']['O'])) $issuer['organization'] = $certInfo['issuer']['O'];
|
||||
@@ -129,7 +124,7 @@ function checkSslCertificate(string $domain): array {
|
||||
if (isset($certInfo['issuer']['C'])) $issuer['country'] = $certInfo['issuer']['C'];
|
||||
}
|
||||
|
||||
// Subject aufbereiten
|
||||
|
||||
$subject = [];
|
||||
if (isset($certInfo['subject'])) {
|
||||
if (isset($certInfo['subject']['CN'])) $subject['common_name'] = $certInfo['subject']['CN'];
|
||||
@@ -149,7 +144,7 @@ function checkSslCertificate(string $domain): array {
|
||||
'version' => $certInfo['version'] ?? null,
|
||||
];
|
||||
|
||||
// Warnung wenn bald ablaufend
|
||||
|
||||
if ($daysUntilExpiry <= 30 && $daysUntilExpiry > 0) {
|
||||
$result['warning'] = "Zertifikat läuft in {$daysUntilExpiry} Tagen ab!";
|
||||
} elseif ($isExpired) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaDNS - WHOIS Lookup API
|
||||
*
|
||||
* Ruft WHOIS-Informationen für eine Domain ab
|
||||
*
|
||||
* 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: *');
|
||||
@@ -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);
|
||||
@@ -25,7 +31,7 @@ if (empty($domain)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Nur Root-Domain extrahieren
|
||||
|
||||
$domain = extractRootDomain($domain);
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-]*\.[a-zA-Z]{2,}$/', $domain)) {
|
||||
@@ -52,9 +58,9 @@ echo json_encode([
|
||||
'whois' => $whoisData
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
/**
|
||||
* Extrahiert die Root-Domain (ohne Subdomain)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function extractRootDomain(string $domain): string {
|
||||
$domain = strtolower($domain);
|
||||
$domain = preg_replace('/^(https?:\/\/)?(www\.)?/', '', $domain);
|
||||
@@ -62,22 +68,22 @@ function extractRootDomain(string $domain): string {
|
||||
|
||||
$parts = explode('.', $domain);
|
||||
if (count($parts) > 2) {
|
||||
// Einfache Logik: nimm die letzten 2 Teile
|
||||
// (funktioniert nicht perfekt für .co.uk etc., aber gut genug)
|
||||
|
||||
|
||||
return implode('.', array_slice($parts, -2));
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt WHOIS-Lookup durch
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function performWhoisLookup(string $domain): ?array {
|
||||
// Primär: Socket-basierte Abfrage (funktioniert ohne shell_exec)
|
||||
|
||||
$whoisRaw = whoisViaSocket($domain);
|
||||
|
||||
// Fallback: Shell-Kommando (sicher escaped)
|
||||
|
||||
if (empty($whoisRaw) && function_exists('shell_exec')) {
|
||||
$escapedDomain = escapeshellarg($domain);
|
||||
$whoisRaw = @shell_exec("whois {$escapedDomain} 2>/dev/null");
|
||||
@@ -87,13 +93,13 @@ function performWhoisLookup(string $domain): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse WHOIS-Daten
|
||||
|
||||
return parseWhoisData($whoisRaw, $domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* WHOIS-Abfrage über Socket (unabhängig von shell_exec)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function whoisViaSocket(string $domain): ?string {
|
||||
$whoisServer = getWhoisServer($domain);
|
||||
|
||||
@@ -103,7 +109,7 @@ function whoisViaSocket(string $domain): ?string {
|
||||
|
||||
$result = queryWhoisServer($whoisServer, $domain);
|
||||
|
||||
// Prüfe auf Weiterleitungen zu anderen WHOIS-Servern
|
||||
|
||||
if ($result && preg_match('/Registrar WHOIS Server:\s*(\S+)/i', $result, $matches)) {
|
||||
$referralServer = trim($matches[1]);
|
||||
if ($referralServer && $referralServer !== $whoisServer) {
|
||||
@@ -117,9 +123,9 @@ function whoisViaSocket(string $domain): ?string {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abfrage an einen spezifischen WHOIS-Server
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function queryWhoisServer(string $server, string $domain): ?string {
|
||||
$port = 43;
|
||||
$timeout = 10;
|
||||
@@ -130,13 +136,13 @@ function queryWhoisServer(string $server, string $domain): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setze Stream-Timeout
|
||||
|
||||
stream_set_timeout($socket, $timeout);
|
||||
|
||||
// Sende Anfrage
|
||||
|
||||
fwrite($socket, $domain . "\r\n");
|
||||
|
||||
// Lese Antwort
|
||||
|
||||
$response = '';
|
||||
while (!feof($socket)) {
|
||||
$response .= fread($socket, 8192);
|
||||
@@ -147,16 +153,16 @@ function queryWhoisServer(string $server, string $domain): ?string {
|
||||
return !empty($response) ? $response : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt den zuständigen WHOIS-Server für eine TLD
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getWhoisServer(string $domain): ?string {
|
||||
$parts = explode('.', $domain);
|
||||
$tld = strtolower(end($parts));
|
||||
|
||||
// Bekannte WHOIS-Server nach TLD
|
||||
|
||||
$whoisServers = [
|
||||
// Generische TLDs
|
||||
|
||||
'com' => 'whois.verisign-grs.com',
|
||||
'net' => 'whois.verisign-grs.com',
|
||||
'org' => 'whois.pir.org',
|
||||
@@ -180,7 +186,7 @@ function getWhoisServer(string $domain): ?string {
|
||||
'travel' => 'whois.nic.travel',
|
||||
'xxx' => 'whois.nic.xxx',
|
||||
|
||||
// Neue gTLDs
|
||||
|
||||
'app' => 'whois.nic.google',
|
||||
'dev' => 'whois.nic.google',
|
||||
'page' => 'whois.nic.google',
|
||||
@@ -199,7 +205,7 @@ function getWhoisServer(string $domain): ?string {
|
||||
'cc' => 'ccwhois.verisign-grs.com',
|
||||
'ws' => 'whois.website.ws',
|
||||
|
||||
// Europäische ccTLDs
|
||||
|
||||
'de' => 'whois.denic.de',
|
||||
'at' => 'whois.nic.at',
|
||||
'ch' => 'whois.nic.ch',
|
||||
@@ -229,7 +235,7 @@ function getWhoisServer(string $domain): ?string {
|
||||
'eu' => 'whois.eu',
|
||||
'lu' => 'whois.dns.lu',
|
||||
|
||||
// Andere ccTLDs
|
||||
|
||||
'ru' => 'whois.tcinet.ru',
|
||||
'ua' => 'whois.ua',
|
||||
'us' => 'whois.nic.us',
|
||||
@@ -249,7 +255,7 @@ function getWhoisServer(string $domain): ?string {
|
||||
'za' => 'whois.registry.net.za',
|
||||
];
|
||||
|
||||
// Spezielle Behandlung für .co.uk, .com.au etc.
|
||||
|
||||
if (count($parts) >= 2) {
|
||||
$sld = $parts[count($parts) - 2];
|
||||
$combinedTld = $sld . '.' . $tld;
|
||||
@@ -273,9 +279,9 @@ function getWhoisServer(string $domain): ?string {
|
||||
return $whoisServers[$tld] ?? 'whois.iana.org';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsed WHOIS-Rohdaten in strukturiertes Format
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function parseWhoisData(string $raw, string $domain): array {
|
||||
$data = [
|
||||
'raw' => $raw,
|
||||
@@ -300,50 +306,50 @@ function parseWhoisData(string $raw, string $domain): array {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key: Value Format
|
||||
|
||||
if (strpos($line, ':') !== false) {
|
||||
list($key, $value) = array_map('trim', explode(':', $line, 2));
|
||||
$keyLower = strtolower($key);
|
||||
|
||||
// Registrar
|
||||
|
||||
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'abuse') === false && strpos($keyLower, 'url') === false) {
|
||||
if (empty($data['parsed']['registrar'])) {
|
||||
$data['parsed']['registrar'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Registrar URL
|
||||
|
||||
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'url') !== false) {
|
||||
$data['parsed']['registrar_url'] = $value;
|
||||
}
|
||||
|
||||
// Erstellungsdatum
|
||||
|
||||
if (preg_match('/(creation|created|registered)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
||||
if (empty($data['parsed']['creation_date'])) {
|
||||
$data['parsed']['creation_date'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Ablaufdatum
|
||||
|
||||
if (preg_match('/(expir|paid-till)/i', $keyLower)) {
|
||||
if (empty($data['parsed']['expiration_date'])) {
|
||||
$data['parsed']['expiration_date'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Aktualisierungsdatum
|
||||
|
||||
if (preg_match('/(updated|modified|changed)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
||||
if (empty($data['parsed']['updated_date'])) {
|
||||
$data['parsed']['updated_date'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Status
|
||||
|
||||
if (preg_match('/(status|state)/i', $keyLower) && !empty($value)) {
|
||||
$data['parsed']['status'][] = $value;
|
||||
}
|
||||
|
||||
// Nameserver
|
||||
|
||||
if (preg_match('/^(name.?server|nserver)/i', $keyLower) && !empty($value)) {
|
||||
$ns = strtolower(explode(' ', $value)[0]);
|
||||
if (!in_array($ns, $data['parsed']['nameservers'])) {
|
||||
@@ -351,14 +357,14 @@ function parseWhoisData(string $raw, string $domain): array {
|
||||
}
|
||||
}
|
||||
|
||||
// DNSSEC
|
||||
|
||||
if (strpos($keyLower, 'dnssec') !== false) {
|
||||
$data['parsed']['dnssec'] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status einzigartig machen
|
||||
|
||||
$data['parsed']['status'] = array_unique($data['parsed']['status']);
|
||||
|
||||
return $data;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaHost.de Konfiguration
|
||||
*
|
||||
* HINWEIS: Diese Datei ist veraltet!
|
||||
*
|
||||
* Die Konfiguration wurde nach mail-config.php verschoben.
|
||||
* Bitte verwenden Sie stattdessen:
|
||||
*
|
||||
* require_once 'config/mail-config.php';
|
||||
*
|
||||
* Diese Datei wird nur aus Kompatibilitätsgründen beibehalten.
|
||||
*/
|
||||
|
||||
// Lade die neue Konfiguration
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
require_once __DIR__ . '/mail-config.php';
|
||||
?>
|
||||
|
||||
64
backend/config/contact-config.php
Normal file
64
backend/config/contact-config.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function isAllowedContactSubject(string $subjectKey): bool {
|
||||
return array_key_exists($subjectKey, getContactSubjectMap());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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 '';
|
||||
}
|
||||
@@ -1,67 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// 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_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
|
||||
|
||||
// Spam-Schutz Einstellungen
|
||||
define('ENABLE_SPAM_PROTECTION', true); // Spam-Schutz aktivieren
|
||||
define('MAX_MESSAGE_LENGTH', 5000); // Max. Nachrichtenlänge
|
||||
define('MIN_MESSAGE_LENGTH', 10); // Min. Nachrichtenlänge
|
||||
|
||||
// Debug-Einstellungen (nur für Entwicklung)
|
||||
define('DEBUG_MODE', false); // Debug-Modus (true/false)
|
||||
define('LOG_EMAILS', true); // E-Mails loggen (true/false)
|
||||
|
||||
// Zusätzliche Sicherheitsheader
|
||||
|
||||
|
||||
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
|
||||
define('SMTP_TO_EMAIL', 'info@hexahost.de');
|
||||
|
||||
|
||||
define('ENABLE_CSRF_PROTECTION', true);
|
||||
define('ENABLE_RATE_LIMITING', true);
|
||||
define('MAX_REQUESTS_PER_HOUR', 5);
|
||||
|
||||
|
||||
define('ENABLE_SPAM_PROTECTION', true);
|
||||
define('MAX_MESSAGE_LENGTH', 5000);
|
||||
define('MIN_MESSAGE_LENGTH', 10);
|
||||
|
||||
|
||||
define('DEBUG_MODE', false);
|
||||
define('LOG_EMAILS', true);
|
||||
|
||||
|
||||
define('ADDITIONAL_HEADERS', [
|
||||
'X-Mailer' => 'HexaHost.de Contact Form',
|
||||
'X-Priority' => '3',
|
||||
@@ -72,27 +35,22 @@ define('ADDITIONAL_HEADERS', [
|
||||
'Precedence' => 'bulk'
|
||||
]);
|
||||
|
||||
// Erlaubte Domains für E-Mail-Adressen (optional)
|
||||
|
||||
define('ALLOWED_EMAIL_DOMAINS', [
|
||||
// Leer lassen für alle Domains zu erlauben
|
||||
// 'gmail.com',
|
||||
// 'outlook.com',
|
||||
// 'web.de',
|
||||
// 'gmx.de'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
]);
|
||||
|
||||
// Blacklist für E-Mail-Adressen (optional)
|
||||
|
||||
define('BLACKLISTED_EMAILS', [
|
||||
// 'spam@example.com',
|
||||
// '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');
|
||||
}
|
||||
@@ -101,7 +59,7 @@ if (!filter_var(SMTP_TO_EMAIL, FILTER_VALIDATE_EMAIL)) {
|
||||
die('Ungültige SMTP_TO_EMAIL Adresse');
|
||||
}
|
||||
|
||||
// Logging-Funktion
|
||||
|
||||
function logEmail($type, $data) {
|
||||
if (!LOG_EMAILS) return;
|
||||
|
||||
@@ -118,18 +76,18 @@ function logEmail($type, $data) {
|
||||
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
// Hilfsfunktion für E-Mail-Validierung
|
||||
|
||||
function isValidEmail($email) {
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prüfe Blacklist
|
||||
|
||||
if (in_array($email, BLACKLISTED_EMAILS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prüfe Domain-Whitelist (falls gesetzt)
|
||||
|
||||
if (!empty(ALLOWED_EMAIL_DOMAINS)) {
|
||||
$domain = substr(strrchr($email, "@"), 1);
|
||||
if (!in_array($domain, ALLOWED_EMAIL_DOMAINS)) {
|
||||
@@ -140,51 +98,29 @@ 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
|
||||
*
|
||||
* @param string|null $key Optional: einzelner Schlüssel
|
||||
* @return mixed Konfigurationsarray oder einzelner Wert
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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',
|
||||
'to_email' => SMTP_TO_EMAIL,
|
||||
'to_name' => 'HexaHost Support',
|
||||
|
||||
// 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,
|
||||
'log_errors' => LOG_EMAILS,
|
||||
];
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* HexaHost.de Produkt-Konfiguration
|
||||
*
|
||||
* Hier können Sie alle Preise und Produktinformationen zentral verwalten.
|
||||
* Nach Änderungen: npm run build && npm run deploy
|
||||
*
|
||||
* Verwendung in PHP-Seiten:
|
||||
* require_once 'config/products-config.php';
|
||||
* $packages = getProductPackages('vpc');
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// VIRTUAL PRIVATE CONTAINER (VPC)
|
||||
// ============================================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$PRODUCTS['vpc'] = [
|
||||
'name' => 'Virtual Private Container',
|
||||
'short_name' => 'VPC',
|
||||
@@ -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'],
|
||||
@@ -112,9 +117,9 @@ $PRODUCTS['vpc'] = [
|
||||
],
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// VIRTUAL PRIVATE SERVER (VPS)
|
||||
// ============================================================================
|
||||
|
||||
|
||||
|
||||
$PRODUCTS['vps'] = [
|
||||
'name' => 'Virtual Private Server',
|
||||
'short_name' => 'VPS',
|
||||
@@ -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'],
|
||||
@@ -214,9 +223,9 @@ $PRODUCTS['vps'] = [
|
||||
],
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// MAIL GATEWAY
|
||||
// ============================================================================
|
||||
|
||||
|
||||
|
||||
$PRODUCTS['mail-gateway'] = [
|
||||
'name' => 'Mail Gateway',
|
||||
'short_name' => 'Mail',
|
||||
@@ -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'],
|
||||
@@ -316,101 +329,113 @@ $PRODUCTS['mail-gateway'] = [
|
||||
],
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// WEBHOSTING
|
||||
// ============================================================================
|
||||
|
||||
|
||||
|
||||
$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,68 +443,133 @@ $PRODUCTS['webhosting'] = [
|
||||
],
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// HILFSFUNKTIONEN
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Alle Produkte abrufen
|
||||
*/
|
||||
$PRODUCT_VISIBILITY = [
|
||||
'vpc' => false,
|
||||
'vps' => false,
|
||||
'mail-gateway' => false,
|
||||
'webhosting' => true,
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function isProductVisible(string $productId): bool {
|
||||
global $PRODUCT_VISIBILITY;
|
||||
return $PRODUCT_VISIBILITY[$productId] ?? true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function productHiddenAttr(string $productId): string {
|
||||
return isProductVisible($productId) ? '' : ' hidden';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function getVisibleProductPageIds(): array {
|
||||
global $PRODUCT_VISIBILITY;
|
||||
return array_keys(array_filter($PRODUCT_VISIBILITY, static fn(bool $visible): bool => $visible));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function getAllProducts() {
|
||||
global $PRODUCTS;
|
||||
return $PRODUCTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein Produkt abrufen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getProduct($productId) {
|
||||
global $PRODUCTS;
|
||||
return $PRODUCTS[$productId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle Pakete eines Produkts abrufen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getProductPackages($productId) {
|
||||
global $PRODUCTS;
|
||||
return $PRODUCTS[$productId]['packages'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein bestimmtes Paket abrufen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getPackage($productId, $packageId) {
|
||||
global $PRODUCTS;
|
||||
return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preis eines Pakets abrufen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getPackagePrice($productId, $packageId) {
|
||||
$package = getPackage($productId, $packageId);
|
||||
return $package['price'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimalen Preis eines Produkts abrufen
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function getMinPrice($productId) {
|
||||
global $PRODUCTS;
|
||||
return $PRODUCTS[$productId]['min_price'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preis formatiert ausgeben
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function formatPrice($price, $withCurrency = true) {
|
||||
return $withCurrency ? $price . '€' : $price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert HTML für eine Paket-Karte
|
||||
*/
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function renderPackageCard($productId, $packageId, $package) {
|
||||
$featuredClass = $package['featured'] ? ' featured' : '';
|
||||
$featuredBadge = $package['featured'] ? '<div class="featured-badge">Beliebt</div>' : '';
|
||||
@@ -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,14 +612,13 @@ function renderPackageCard($productId, $packageId, $package) {
|
||||
$package['price'],
|
||||
$specsHtml,
|
||||
$featuresHtml,
|
||||
$productId,
|
||||
$packageId
|
||||
htmlspecialchars(getOrderUrl($productId, $packageId), ENT_QUOTES, 'UTF-8')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert HTML für alle Pakete eines Produkts
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function renderAllPackages($productId) {
|
||||
$packages = getProductPackages($productId);
|
||||
$html = '';
|
||||
|
||||
112
backend/includes/api-helpers.php
Normal file
112
backend/includes/api-helpers.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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">
|
||||
@@ -126,7 +133,7 @@
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
|
||||
// Standard: keine Analyse/Marketing-Cookies bis zur Einwilligung
|
||||
|
||||
gtag('consent', 'default', {
|
||||
analytics_storage: 'denied',
|
||||
ad_storage: 'denied',
|
||||
@@ -139,7 +146,7 @@
|
||||
anonymize_ip: true
|
||||
});
|
||||
|
||||
// Übergibt Consent-Änderungen aus dem eigenen Cookie-Banner an GA
|
||||
|
||||
window.addEventListener('cookieConsentUpdated', function (event) {
|
||||
var payload = event && event.detail ? event.detail : {};
|
||||
var consent = payload.consent ? payload.consent : payload;
|
||||
@@ -156,11 +163,11 @@
|
||||
</script>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-EF0E9VPMTD"></script>
|
||||
|
||||
<script src="/assets/js/main.js" defer></script>
|
||||
<script src="/assets/js/cookie-consent.js" defer></script>
|
||||
<script src="/assets/js/main.9189c38109cf.js" defer></script>
|
||||
<script src="/assets/js/cookie-consent.91c79812d22c.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>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper functions for HexaHost.de
|
||||
*/
|
||||
|
||||
// Sichere Session-Konfiguration
|
||||
|
||||
|
||||
|
||||
require_once __DIR__ . '/../config/products-config.php';
|
||||
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
// Session-Cookie-Sicherheit
|
||||
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0);
|
||||
ini_set('session.cookie_samesite', 'Strict');
|
||||
@@ -14,14 +16,14 @@ if (session_status() === PHP_SESSION_NONE) {
|
||||
|
||||
session_start();
|
||||
|
||||
// Session-ID regenerieren bei Login/wichtigen Aktionen (Schutz vor Session Fixation)
|
||||
|
||||
if (!isset($_SESSION['initiated'])) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['initiated'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// PHP Error Display in Produktion deaktivieren
|
||||
|
||||
if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('display_startup_errors', 0);
|
||||
@@ -29,18 +31,18 @@ if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
|
||||
ini_set('log_errors', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set page configuration and include header
|
||||
*
|
||||
* @param string $title The page title
|
||||
* @param string $description The page description
|
||||
* @param string $page The current page identifier
|
||||
* @param array $scripts Additional scripts to include
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
|
||||
global $page_title, $page_description, $current_page, $additional_scripts;
|
||||
|
||||
// Set page configuration from parameters
|
||||
|
||||
$page_title = !empty($title)
|
||||
? $title
|
||||
: 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
|
||||
@@ -55,28 +57,28 @@ function includeHeader($title = '', $description = '', $page = '', $scripts = []
|
||||
include __DIR__ . '/header.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Include footer
|
||||
*/
|
||||
|
||||
|
||||
|
||||
function includeFooter() {
|
||||
include __DIR__ . '/footer.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate breadcrumb navigation
|
||||
*
|
||||
* @param array $breadcrumbs Array of breadcrumb items [['title' => 'Home', 'url' => 'index.html'], ...]
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function generateBreadcrumbs($breadcrumbs) {
|
||||
echo '<div class="breadcrumb">';
|
||||
$last_index = count($breadcrumbs) - 1;
|
||||
|
||||
foreach ($breadcrumbs as $index => $item) {
|
||||
if ($index === $last_index) {
|
||||
// Last item (current page)
|
||||
|
||||
echo '<span>' . htmlspecialchars($item['title']) . '</span>';
|
||||
} else {
|
||||
// Link to other pages
|
||||
|
||||
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||
echo '<span>/</span>';
|
||||
}
|
||||
@@ -84,15 +86,67 @@ function generateBreadcrumbs($breadcrumbs) {
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSRF token for form security
|
||||
*
|
||||
* @return string CSRF token
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function generateCSRFToken() {
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function sanitizeHeaderValue(string $value): string {
|
||||
return str_replace(["\r", "\n", "\0"], '', trim($value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
?>
|
||||
@@ -13,7 +13,7 @@
|
||||
<link rel="preconnect" href="https://cdn.hexahost.de" crossorigin>
|
||||
|
||||
<!-- Performance: Preload kritischer Ressourcen -->
|
||||
<link rel="preload" href="/assets/css/style.css" as="style">
|
||||
<link rel="preload" href="/assets/css/style.d01979e8c871.css" as="style">
|
||||
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" as="style">
|
||||
|
||||
<title><?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern'; ?></title>
|
||||
@@ -32,8 +32,8 @@
|
||||
<meta property="og:locale" content="de_DE">
|
||||
|
||||
<!-- Main Stylesheets -->
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
<link rel="stylesheet" href="/assets/css/custom.css">
|
||||
<link rel="stylesheet" href="/assets/css/style.d01979e8c871.css">
|
||||
<link rel="stylesheet" href="/assets/css/custom.d35eb3499212.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Russo+One&family=Source+Sans+Pro:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
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' 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 data:; connect-src 'self' 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'"
|
||||
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"
|
||||
@@ -52,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>
|
||||
@@ -77,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
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = '404 - Seite nicht gefunden | HexaHost.de';
|
||||
$page_description = 'Die angeforderte Seite wurde nicht gefunden.';
|
||||
$current_page = '404';
|
||||
|
||||
// Set 404 header
|
||||
|
||||
http_response_code(404);
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -45,7 +45,7 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
.error-code {
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #ff51f9, #a348ff);
|
||||
background: linear-gradient(135deg,
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
@@ -57,7 +57,7 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.error-content p {
|
||||
color: #888;
|
||||
color:
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.error-actions {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = '500 - Serverfehler | HexaHost.de';
|
||||
$page_description = 'Ein interner Serverfehler ist aufgetreten.';
|
||||
$current_page = '500';
|
||||
|
||||
// Set 500 header
|
||||
|
||||
http_response_code(500);
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -45,7 +45,7 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
.error-code {
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #ff51f9, #a348ff);
|
||||
background: linear-gradient(135deg,
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
@@ -57,7 +57,7 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.error-content p {
|
||||
color: #888;
|
||||
color:
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.error-actions {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = 'Über mich - HexaHost.de | Hosting aus Niederbayern';
|
||||
$page_description = 'Erfahren Sie mehr über HexaHost.de - Ihr zuverlässiger Hosting-Partner aus Niederbayern. Moderne Technologie mit persönlichem Service.';
|
||||
$current_page = 'about';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -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
|
||||
@@ -245,6 +245,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = 'Allgemeine Geschäftsbedingungen - HexaHost.de | AGB';
|
||||
$page_description = 'Allgemeine Geschäftsbedingungen (AGB) von HexaHost.de für Hosting-Dienstleistungen.';
|
||||
$current_page = 'agb';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -527,6 +527,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
.btn-tertiary {
|
||||
color: var(--text-primary);
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-tertiary:hover {
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
background: rgba(255, 81, 249, 0.08);
|
||||
}
|
||||
|
||||
.it-services-actions {
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
1
public/assets/css/custom.d35eb3499212.css
Normal file
1
public/assets/css/custom.d35eb3499212.css
Normal file
@@ -0,0 +1 @@
|
||||
.btn-tertiary{color:var(--text-primary);background:transparent;border:1px solid rgba(255,255,255,0.25);transition:all 0.3s ease;}.btn-tertiary:hover{border-color:var(--primary-color);color:var(--primary-color);background:rgba(255,81,249,0.08);}.it-services-actions{justify-content:center;margin-top:2rem;}.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;}.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;}
|
||||
File diff suppressed because one or more lines are too long
1
public/assets/css/style.d01979e8c871.css
Normal file
1
public/assets/css/style.d01979e8c871.css
Normal file
File diff suppressed because one or more lines are too long
1
public/assets/js/contact.c2399194863d.js
Normal file
1
public/assets/js/contact.c2399194863d.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/assets/js/cookie-consent.91c79812d22c.js
Normal file
1
public/assets/js/cookie-consent.91c79812d22c.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/assets/js/main.9189c38109cf.js
Normal file
1
public/assets/js/main.9189c38109cf.js
Normal file
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
|
||||
|
||||
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
// Session starten für CSRF-Validierung
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
require_once __DIR__ . '/../backend/config/mail-config.php';
|
||||
require_once __DIR__ . '/../backend/config/contact-config.php';
|
||||
|
||||
$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));
|
||||
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,13 +1,16 @@
|
||||
<?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';
|
||||
$page_description = 'Kontaktieren Sie HexaHost.de - Ihr Hosting-Partner aus Niederbayern. Persönlicher Support und kompetente Beratung.';
|
||||
$current_page = 'contact';
|
||||
$additional_scripts = ['assets/js/contact.js'];
|
||||
$additional_scripts = ['assets/js/contact.c2399194863d.js'];
|
||||
|
||||
|
||||
// Include header
|
||||
includeHeader($page_title, $page_description, $current_page, $additional_scripts);
|
||||
?>
|
||||
|
||||
@@ -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">
|
||||
@@ -261,6 +252,6 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = 'Datenschutzerklärung - HexaHost.de | Datenschutz';
|
||||
$page_description = 'Datenschutzerklärung von HexaHost.de - Informationen zum Schutz Ihrer personenbezogenen Daten.';
|
||||
$current_page = 'datenschutz';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -344,6 +344,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = 'Impressum - HexaHost.de | Rechtliche Angaben';
|
||||
$page_description = 'Impressum und rechtliche Angaben von HexaHost.de - Hosting aus Niederbayern.';
|
||||
$current_page = 'impressum';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -92,7 +92,7 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
<p>
|
||||
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
|
||||
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener noreferrer">
|
||||
https://ec.europa.eu/consumers/odr/
|
||||
https:
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
@@ -180,6 +180,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
|
||||
$page_description = 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.';
|
||||
$current_page = 'home';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -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"/>
|
||||
@@ -265,6 +265,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = 'IT-Dienstleistungen - HexaHost.de | Privat & Gewerblich';
|
||||
$page_description = 'IT-Dienstleistungen von HexaHost.de mit Fokus auf Privatkunden und ergänzend für gewerbliche Anforderungen.';
|
||||
$current_page = 'it-dienstleistungen';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -143,6 +143,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||
|
||||
// Produkt-Daten aus Config laden
|
||||
|
||||
$product = getProduct('mail-gateway');
|
||||
$packages = getProductPackages('mail-gateway');
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = $product['page_title'];
|
||||
$page_description = $product['page_description'];
|
||||
$current_page = 'mail-gateway';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -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>
|
||||
@@ -175,6 +175,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -6,9 +6,9 @@ Disallow: /assets/js/
|
||||
Disallow: /assets/css/
|
||||
|
||||
# Allow CSS and JS files for better SEO
|
||||
Allow: /assets/css/style.css
|
||||
Allow: /assets/js/main.js
|
||||
Allow: /assets/js/contact.js
|
||||
Allow: /assets/css/style.d01979e8c871.css
|
||||
Allow: /assets/js/main.9189c38109cf.js
|
||||
Allow: /assets/js/contact.c2399194863d.js
|
||||
|
||||
# Sitemap location
|
||||
Sitemap: https://hexahost.de/sitemap.xml
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||
|
||||
// Produkt-Daten aus Config laden
|
||||
|
||||
$product = getProduct('vpc');
|
||||
$packages = getProductPackages('vpc');
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = $product['page_title'];
|
||||
$page_description = $product['page_description'];
|
||||
$current_page = 'vpc';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -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>
|
||||
@@ -175,6 +175,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||
|
||||
// Produkt-Daten aus Config laden
|
||||
|
||||
$product = getProduct('vps');
|
||||
$packages = getProductPackages('vps');
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = $product['page_title'];
|
||||
$page_description = $product['page_description'];
|
||||
$current_page = 'vps';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -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>
|
||||
@@ -180,6 +180,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||
|
||||
// Produkt-Daten aus Config laden
|
||||
|
||||
$product = getProduct('webhosting');
|
||||
$packages = getProductPackages('webhosting');
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = $product['page_title'];
|
||||
$page_description = $product['page_description'];
|
||||
$current_page = 'webhosting';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -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>
|
||||
@@ -179,6 +179,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||
|
||||
// Page configuration
|
||||
|
||||
$page_title = 'Widerrufsbelehrung - HexaHost.de';
|
||||
$page_description = 'Widerrufsbelehrung und Muster-Widerrufsformular von HexaHost Inh. Samuel Müller.';
|
||||
$current_page = 'widerruf';
|
||||
|
||||
// Include header
|
||||
|
||||
includeHeader($page_title, $page_description, $current_page);
|
||||
?>
|
||||
|
||||
@@ -131,6 +131,6 @@ includeHeader($page_title, $page_description, $current_page);
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Include footer
|
||||
|
||||
includeFooter();
|
||||
?>
|
||||
|
||||
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