mirror of
https://git.hexahost.dev/smueller/HexaHost-Frontend.git
synced 2026-06-02 11:58:43 +00:00
Compare commits
31 Commits
b893272d64
...
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 |
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
|
# Environment variables
|
||||||
.env
|
.env
|
||||||
.cursorrules
|
.cursorrules
|
||||||
|
.cursor/
|
||||||
.cursorrules.txt
|
.cursorrules.txt
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.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
|
||||||
48
README.md
48
README.md
@@ -166,25 +166,51 @@ Für den Produktivbetrieb `public/` als Webroot konfigurieren.
|
|||||||
|
|
||||||
### Production-Build & Veröffentlichung
|
### Production-Build & Veröffentlichung
|
||||||
|
|
||||||
Der Quellcode bleibt auf `dev`, der veröffentlichte Stand liegt auf `main` (ohne Kommentare, obfuskiertes JS).
|
| Branch | Zweck |
|
||||||
|
|--------|--------|
|
||||||
|
| **`dev`** | Entwicklung (lesbarer Code, Kommentare) |
|
||||||
|
| **`ci`** | Integration (du mergst `dev` hierher) |
|
||||||
|
| **`main`** | Release/Produktion (obfuskiert, gehashte Assets — nur per Pipeline) |
|
||||||
|
|
||||||
**Voraussetzungen:** Node.js 18+ (inkl. npm), PHP 8+ CLI, Git
|
**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
|
```powershell
|
||||||
# Windows
|
# Nach fertigen Änderungen auf dev:
|
||||||
.\scripts\run-build.ps1
|
git checkout ci
|
||||||
.\scripts\publish-to-main.ps1 -Push
|
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
|
```bash
|
||||||
# Linux / macOS
|
python scripts/obfuscate_release.py --root . --hash-assets
|
||||||
chmod +x scripts/*.sh
|
|
||||||
./scripts/run-build.sh
|
|
||||||
./scripts/publish-to-main.sh --push
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Details: `scripts/build/README.md`
|
|
||||||
|
|
||||||
## 🔗 Backend-Integration
|
## 🔗 Backend-Integration
|
||||||
|
|
||||||
Das Backend-Repository enthält folgende wiederverwendbare Komponenten:
|
Das Backend-Repository enthält folgende wiederverwendbare Komponenten:
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<?php
|
<?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
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../includes/api-helpers.php';
|
require_once __DIR__ . '/../includes/api-helpers.php';
|
||||||
|
|
||||||
// CORS Headers für Frontend-Zugriff
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||||
header('Access-Control-Allow-Headers: Content-Type');
|
header('Access-Control-Allow-Headers: Content-Type');
|
||||||
|
|
||||||
// Preflight request handling
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
exit;
|
exit;
|
||||||
@@ -39,12 +39,12 @@ if ($domain === null) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS-Abfrage durchführen
|
|
||||||
$startTime = microtime(true);
|
$startTime = microtime(true);
|
||||||
$result = performDnsLookup($domain);
|
$result = performDnsLookup($domain);
|
||||||
$queryTime = round((microtime(true) - $startTime) * 1000, 2);
|
$queryTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||||
|
|
||||||
// Ergebnis zurückgeben
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
@@ -53,9 +53,9 @@ echo json_encode([
|
|||||||
'records' => $result
|
'records' => $result
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
/**
|
|
||||||
* Führt DNS-Lookup für verschiedene Record-Typen durch
|
|
||||||
*/
|
|
||||||
function performDnsLookup(string $domain): array {
|
function performDnsLookup(string $domain): array {
|
||||||
$records = [
|
$records = [
|
||||||
'A' => [],
|
'A' => [],
|
||||||
@@ -67,7 +67,7 @@ function performDnsLookup(string $domain): array {
|
|||||||
'SOA' => []
|
'SOA' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
// A-Records (IPv4)
|
|
||||||
$aRecords = @dns_get_record($domain, DNS_A);
|
$aRecords = @dns_get_record($domain, DNS_A);
|
||||||
if ($aRecords) {
|
if ($aRecords) {
|
||||||
foreach ($aRecords as $record) {
|
foreach ($aRecords as $record) {
|
||||||
@@ -78,7 +78,7 @@ function performDnsLookup(string $domain): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AAAA-Records (IPv6)
|
|
||||||
$aaaaRecords = @dns_get_record($domain, DNS_AAAA);
|
$aaaaRecords = @dns_get_record($domain, DNS_AAAA);
|
||||||
if ($aaaaRecords) {
|
if ($aaaaRecords) {
|
||||||
foreach ($aaaaRecords as $record) {
|
foreach ($aaaaRecords as $record) {
|
||||||
@@ -89,7 +89,7 @@ function performDnsLookup(string $domain): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MX-Records (Mail)
|
|
||||||
$mxRecords = @dns_get_record($domain, DNS_MX);
|
$mxRecords = @dns_get_record($domain, DNS_MX);
|
||||||
if ($mxRecords) {
|
if ($mxRecords) {
|
||||||
foreach ($mxRecords as $record) {
|
foreach ($mxRecords as $record) {
|
||||||
@@ -99,11 +99,11 @@ function performDnsLookup(string $domain): array {
|
|||||||
'ttl' => $record['ttl']
|
'ttl' => $record['ttl']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// Nach Priorität sortieren
|
|
||||||
usort($records['MX'], fn($a, $b) => $a['priority'] <=> $b['priority']);
|
usort($records['MX'], fn($a, $b) => $a['priority'] <=> $b['priority']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NS-Records (Nameserver)
|
|
||||||
$nsRecords = @dns_get_record($domain, DNS_NS);
|
$nsRecords = @dns_get_record($domain, DNS_NS);
|
||||||
if ($nsRecords) {
|
if ($nsRecords) {
|
||||||
foreach ($nsRecords as $record) {
|
foreach ($nsRecords as $record) {
|
||||||
@@ -114,7 +114,7 @@ function performDnsLookup(string $domain): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TXT-Records
|
|
||||||
$txtRecords = @dns_get_record($domain, DNS_TXT);
|
$txtRecords = @dns_get_record($domain, DNS_TXT);
|
||||||
if ($txtRecords) {
|
if ($txtRecords) {
|
||||||
foreach ($txtRecords as $record) {
|
foreach ($txtRecords as $record) {
|
||||||
@@ -125,7 +125,7 @@ function performDnsLookup(string $domain): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CNAME-Records
|
|
||||||
$cnameRecords = @dns_get_record($domain, DNS_CNAME);
|
$cnameRecords = @dns_get_record($domain, DNS_CNAME);
|
||||||
if ($cnameRecords) {
|
if ($cnameRecords) {
|
||||||
foreach ($cnameRecords as $record) {
|
foreach ($cnameRecords as $record) {
|
||||||
@@ -136,7 +136,7 @@ function performDnsLookup(string $domain): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SOA-Record (Start of Authority)
|
|
||||||
$soaRecords = @dns_get_record($domain, DNS_SOA);
|
$soaRecords = @dns_get_record($domain, DNS_SOA);
|
||||||
if ($soaRecords) {
|
if ($soaRecords) {
|
||||||
foreach ($soaRecords as $record) {
|
foreach ($soaRecords as $record) {
|
||||||
@@ -153,6 +153,6 @@ function performDnsLookup(string $domain): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leere Arrays entfernen
|
|
||||||
return array_filter($records, fn($arr) => !empty($arr));
|
return array_filter($records, fn($arr) => !empty($arr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?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';
|
require_once __DIR__ . '/../includes/api-helpers.php';
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ if (!checkApiRateLimit('dns-propagation')) {
|
|||||||
rejectApiRateLimit();
|
rejectApiRateLimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Öffentliche DNS-Server für Propagation-Check
|
|
||||||
$dnsServers = [
|
$dnsServers = [
|
||||||
['name' => 'Google', 'ip' => '8.8.8.8', 'location' => 'Global'],
|
['name' => 'Google', 'ip' => '8.8.8.8', 'location' => 'Global'],
|
||||||
['name' => 'Google Secondary', 'ip' => '8.8.4.4', 'location' => 'Global'],
|
['name' => 'Google Secondary', 'ip' => '8.8.4.4', 'location' => 'Global'],
|
||||||
@@ -44,7 +44,7 @@ if ($domain === null) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erlaubte Record-Typen
|
|
||||||
$allowedTypes = ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME'];
|
$allowedTypes = ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME'];
|
||||||
if (!in_array($type, $allowedTypes)) {
|
if (!in_array($type, $allowedTypes)) {
|
||||||
$type = 'A';
|
$type = 'A';
|
||||||
@@ -65,7 +65,7 @@ foreach ($dnsServers as $server) {
|
|||||||
|
|
||||||
$queryStart = microtime(true);
|
$queryStart = microtime(true);
|
||||||
|
|
||||||
// DNS-Abfrage mit spezifischem Server via dig (falls verfügbar) oder dns_get_record
|
|
||||||
$records = queryDnsServer($domain, $type, $server['ip']);
|
$records = queryDnsServer($domain, $type, $server['ip']);
|
||||||
|
|
||||||
$serverResult['response_time'] = round((microtime(true) - $queryStart) * 1000, 2);
|
$serverResult['response_time'] = round((microtime(true) - $queryStart) * 1000, 2);
|
||||||
@@ -80,7 +80,7 @@ foreach ($dnsServers as $server) {
|
|||||||
|
|
||||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||||
|
|
||||||
// Propagation-Status berechnen
|
|
||||||
$propagationStatus = calculatePropagationStatus($results);
|
$propagationStatus = calculatePropagationStatus($results);
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
@@ -93,13 +93,13 @@ echo json_encode([
|
|||||||
'servers' => $results
|
'servers' => $results
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
/**
|
|
||||||
* DNS-Abfrage bei spezifischem Server
|
|
||||||
*/
|
|
||||||
function queryDnsServer(string $domain, string $type, string $server): array {
|
function queryDnsServer(string $domain, string $type, string $server): array {
|
||||||
$records = [];
|
$records = [];
|
||||||
|
|
||||||
// Versuche zuerst dig zu verwenden (genauer)
|
|
||||||
$digResult = @shell_exec(
|
$digResult = @shell_exec(
|
||||||
'dig @' . escapeshellarg($server) . ' '
|
'dig @' . escapeshellarg($server) . ' '
|
||||||
. escapeshellarg($domain) . ' '
|
. escapeshellarg($domain) . ' '
|
||||||
@@ -115,7 +115,7 @@ function queryDnsServer(string $domain, string $type, string $server): array {
|
|||||||
return $records;
|
return $records;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback auf PHP dns_get_record (verwendet System-DNS)
|
|
||||||
$dnsType = constant('DNS_' . $type);
|
$dnsType = constant('DNS_' . $type);
|
||||||
$result = @dns_get_record($domain, $dnsType);
|
$result = @dns_get_record($domain, $dnsType);
|
||||||
|
|
||||||
@@ -145,9 +145,9 @@ function queryDnsServer(string $domain, string $type, string $server): array {
|
|||||||
return array_filter($records);
|
return array_filter($records);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Berechnet den Propagation-Status
|
|
||||||
*/
|
|
||||||
function calculatePropagationStatus(array $results): array {
|
function calculatePropagationStatus(array $results): array {
|
||||||
$totalServers = count($results);
|
$totalServers = count($results);
|
||||||
$serversWithRecords = 0;
|
$serversWithRecords = 0;
|
||||||
@@ -162,10 +162,10 @@ function calculatePropagationStatus(array $results): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Einzigartige Records
|
|
||||||
$uniqueRecords = array_unique($allRecords);
|
$uniqueRecords = array_unique($allRecords);
|
||||||
|
|
||||||
// Konsistenz prüfen (haben alle Server die gleichen Records?)
|
|
||||||
$isConsistent = count($uniqueRecords) <= 1 || $serversWithRecords === 0;
|
$isConsistent = count($uniqueRecords) <= 1 || $serversWithRecords === 0;
|
||||||
|
|
||||||
$percentage = $totalServers > 0 ? round(($serversWithRecords / $totalServers) * 100) : 0;
|
$percentage = $totalServers > 0 ? round(($serversWithRecords / $totalServers) * 100) : 0;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?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';
|
require_once __DIR__ . '/../includes/api-helpers.php';
|
||||||
|
|
||||||
@@ -43,9 +43,9 @@ echo json_encode([
|
|||||||
'results' => $results
|
'results' => $results
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
/**
|
|
||||||
* Führt verschiedene Erreichbarkeitstests durch
|
|
||||||
*/
|
|
||||||
function performConnectivityCheck(string $domain): array {
|
function performConnectivityCheck(string $domain): array {
|
||||||
$results = [
|
$results = [
|
||||||
'dns_resolution' => checkDnsResolution($domain),
|
'dns_resolution' => checkDnsResolution($domain),
|
||||||
@@ -55,7 +55,7 @@ function performConnectivityCheck(string $domain): array {
|
|||||||
'overall_status' => 'offline'
|
'overall_status' => 'offline'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Overall-Status bestimmen
|
|
||||||
if ($results['https']['reachable'] || $results['http']['reachable']) {
|
if ($results['https']['reachable'] || $results['http']['reachable']) {
|
||||||
$results['overall_status'] = 'online';
|
$results['overall_status'] = 'online';
|
||||||
} elseif ($results['icmp_ping']['reachable']) {
|
} elseif ($results['icmp_ping']['reachable']) {
|
||||||
@@ -67,9 +67,9 @@ function performConnectivityCheck(string $domain): array {
|
|||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* DNS-Auflösung prüfen
|
|
||||||
*/
|
|
||||||
function checkDnsResolution(string $domain): array {
|
function checkDnsResolution(string $domain): array {
|
||||||
$start = microtime(true);
|
$start = microtime(true);
|
||||||
$ip = gethostbyname($domain);
|
$ip = gethostbyname($domain);
|
||||||
@@ -84,9 +84,9 @@ function checkDnsResolution(string $domain): array {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ICMP Ping (falls verfügbar)
|
|
||||||
*/
|
|
||||||
function checkIcmpPing(string $domain): array {
|
function checkIcmpPing(string $domain): array {
|
||||||
$result = [
|
$result = [
|
||||||
'reachable' => false,
|
'reachable' => false,
|
||||||
@@ -94,18 +94,18 @@ function checkIcmpPing(string $domain): array {
|
|||||||
'packet_loss' => null
|
'packet_loss' => null
|
||||||
];
|
];
|
||||||
|
|
||||||
// Versuche ping-Kommando
|
|
||||||
$safeDomain = escapeshellarg($domain);
|
$safeDomain = escapeshellarg($domain);
|
||||||
$pingResult = @shell_exec("ping -c 3 -W 2 {$safeDomain} 2>/dev/null");
|
$pingResult = @shell_exec("ping -c 3 -W 2 {$safeDomain} 2>/dev/null");
|
||||||
|
|
||||||
if ($pingResult) {
|
if ($pingResult) {
|
||||||
// Prüfe auf erfolgreiche Antworten
|
|
||||||
if (preg_match('/(\d+)% packet loss/', $pingResult, $lossMatch)) {
|
if (preg_match('/(\d+)% packet loss/', $pingResult, $lossMatch)) {
|
||||||
$result['packet_loss'] = (int)$lossMatch[1];
|
$result['packet_loss'] = (int)$lossMatch[1];
|
||||||
$result['reachable'] = $result['packet_loss'] < 100;
|
$result['reachable'] = $result['packet_loss'] < 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Durchschnittliche Zeit extrahieren
|
|
||||||
if (preg_match('/avg.*?=.*?[\d.]+\/([\d.]+)\//', $pingResult, $timeMatch)) {
|
if (preg_match('/avg.*?=.*?[\d.]+\/([\d.]+)\//', $pingResult, $timeMatch)) {
|
||||||
$result['response_time_ms'] = (float)$timeMatch[1];
|
$result['response_time_ms'] = (float)$timeMatch[1];
|
||||||
} elseif (preg_match('/time[=<]([\d.]+)\s*ms/', $pingResult, $timeMatch)) {
|
} elseif (preg_match('/time[=<]([\d.]+)\s*ms/', $pingResult, $timeMatch)) {
|
||||||
@@ -116,9 +116,9 @@ function checkIcmpPing(string $domain): array {
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP(S)-Verbindung prüfen
|
|
||||||
*/
|
|
||||||
function checkHttpConnection(string $domain, bool $https = false): array {
|
function checkHttpConnection(string $domain, bool $https = false): array {
|
||||||
$protocol = $https ? 'https' : 'http';
|
$protocol = $https ? 'https' : 'http';
|
||||||
$port = $https ? 443 : 80;
|
$port = $https ? 443 : 80;
|
||||||
@@ -134,7 +134,7 @@ function checkHttpConnection(string $domain, bool $https = false): array {
|
|||||||
|
|
||||||
$start = microtime(true);
|
$start = microtime(true);
|
||||||
|
|
||||||
// cURL verwenden
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
CURLOPT_URL => $url,
|
CURLOPT_URL => $url,
|
||||||
@@ -156,13 +156,13 @@ function checkHttpConnection(string $domain, bool $https = false): array {
|
|||||||
$result['status_code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$result['status_code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
$result['reachable'] = $result['status_code'] > 0;
|
$result['reachable'] = $result['status_code'] > 0;
|
||||||
|
|
||||||
// Redirect-URL
|
|
||||||
$redirectUrl = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
|
$redirectUrl = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
|
||||||
if (!empty($redirectUrl)) {
|
if (!empty($redirectUrl)) {
|
||||||
$result['redirect_url'] = $redirectUrl;
|
$result['redirect_url'] = $redirectUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server-Header
|
|
||||||
if (preg_match('/Server:\s*([^\r\n]+)/i', $response, $serverMatch)) {
|
if (preg_match('/Server:\s*([^\r\n]+)/i', $response, $serverMatch)) {
|
||||||
$result['server'] = trim($serverMatch[1]);
|
$result['server'] = trim($serverMatch[1]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?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';
|
require_once __DIR__ . '/../includes/api-helpers.php';
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ if (empty($ip)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPv4 oder IPv6 validieren
|
|
||||||
$isIPv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
$isIPv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||||
$isIPv6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
$isIPv6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||||
|
|
||||||
@@ -54,9 +54,9 @@ echo json_encode([
|
|||||||
'result' => $result
|
'result' => $result
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
/**
|
|
||||||
* Führt Reverse DNS Lookup durch
|
|
||||||
*/
|
|
||||||
function performReverseLookup(string $ip, string $version): array {
|
function performReverseLookup(string $ip, string $version): array {
|
||||||
$result = [
|
$result = [
|
||||||
'hostname' => null,
|
'hostname' => null,
|
||||||
@@ -64,44 +64,44 @@ function performReverseLookup(string $ip, string $version): array {
|
|||||||
'additional_info' => []
|
'additional_info' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
// PHP gethostbyaddr
|
|
||||||
$hostname = @gethostbyaddr($ip);
|
$hostname = @gethostbyaddr($ip);
|
||||||
|
|
||||||
if ($hostname && $hostname !== $ip) {
|
if ($hostname && $hostname !== $ip) {
|
||||||
$result['hostname'] = $hostname;
|
$result['hostname'] = $hostname;
|
||||||
|
|
||||||
// Verifizieren durch Forward-Lookup
|
|
||||||
$forwardIp = gethostbyname($hostname);
|
$forwardIp = gethostbyname($hostname);
|
||||||
$result['forward_verified'] = ($forwardIp === $ip);
|
$result['forward_verified'] = ($forwardIp === $ip);
|
||||||
|
|
||||||
// Zusätzliche Infos über den Host sammeln
|
|
||||||
$result['additional_info'] = getHostInfo($hostname);
|
$result['additional_info'] = getHostInfo($hostname);
|
||||||
} else {
|
} else {
|
||||||
$result['error'] = 'Kein PTR-Record gefunden';
|
$result['error'] = 'Kein PTR-Record gefunden';
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTR-Record direkt abfragen
|
|
||||||
$ptrRecord = getPtrRecord($ip, $version);
|
$ptrRecord = getPtrRecord($ip, $version);
|
||||||
if ($ptrRecord) {
|
if ($ptrRecord) {
|
||||||
$result['ptr_record'] = $ptrRecord;
|
$result['ptr_record'] = $ptrRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP-Info (GeoIP wenn verfügbar, sonst Basic-Infos)
|
|
||||||
$result['ip_info'] = getIpInfo($ip);
|
$result['ip_info'] = getIpInfo($ip);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PTR-Record direkt abfragen
|
|
||||||
*/
|
|
||||||
function getPtrRecord(string $ip, string $version): ?string {
|
function getPtrRecord(string $ip, string $version): ?string {
|
||||||
if ($version === 'IPv4') {
|
if ($version === 'IPv4') {
|
||||||
// IPv4: Reverse die Oktette
|
|
||||||
$parts = array_reverse(explode('.', $ip));
|
$parts = array_reverse(explode('.', $ip));
|
||||||
$ptrDomain = implode('.', $parts) . '.in-addr.arpa';
|
$ptrDomain = implode('.', $parts) . '.in-addr.arpa';
|
||||||
} else {
|
} else {
|
||||||
// IPv6: Komplexer - jedes Nibble umkehren
|
|
||||||
$expanded = expandIPv6($ip);
|
$expanded = expandIPv6($ip);
|
||||||
$nibbles = str_replace(':', '', $expanded);
|
$nibbles = str_replace(':', '', $expanded);
|
||||||
$reversed = implode('.', array_reverse(str_split($nibbles)));
|
$reversed = implode('.', array_reverse(str_split($nibbles)));
|
||||||
@@ -117,11 +117,11 @@ function getPtrRecord(string $ip, string $version): ?string {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Expandiert eine IPv6-Adresse
|
|
||||||
*/
|
|
||||||
function expandIPv6(string $ip): string {
|
function expandIPv6(string $ip): string {
|
||||||
// Ersetze :: mit der richtigen Anzahl von 0000
|
|
||||||
if (strpos($ip, '::') !== false) {
|
if (strpos($ip, '::') !== false) {
|
||||||
$parts = explode('::', $ip);
|
$parts = explode('::', $ip);
|
||||||
$left = $parts[0] ? explode(':', $parts[0]) : [];
|
$left = $parts[0] ? explode(':', $parts[0]) : [];
|
||||||
@@ -133,7 +133,7 @@ function expandIPv6(string $ip): string {
|
|||||||
$all = explode(':', $ip);
|
$all = explode(':', $ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jedes Segment auf 4 Zeichen auffüllen
|
|
||||||
$all = array_map(function($segment) {
|
$all = array_map(function($segment) {
|
||||||
return str_pad($segment, 4, '0', STR_PAD_LEFT);
|
return str_pad($segment, 4, '0', STR_PAD_LEFT);
|
||||||
}, $all);
|
}, $all);
|
||||||
@@ -141,19 +141,19 @@ function expandIPv6(string $ip): string {
|
|||||||
return implode(':', $all);
|
return implode(':', $all);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sammelt Infos über einen Hostnamen
|
|
||||||
*/
|
|
||||||
function getHostInfo(string $hostname): array {
|
function getHostInfo(string $hostname): array {
|
||||||
$info = [];
|
$info = [];
|
||||||
|
|
||||||
// Domain-Teile analysieren
|
|
||||||
$parts = explode('.', $hostname);
|
$parts = explode('.', $hostname);
|
||||||
$tld = end($parts);
|
$tld = end($parts);
|
||||||
|
|
||||||
$info['tld'] = $tld;
|
$info['tld'] = $tld;
|
||||||
|
|
||||||
// Bekannte Hosting-Provider erkennen
|
|
||||||
$providerPatterns = [
|
$providerPatterns = [
|
||||||
'amazonaws.com' => 'Amazon AWS',
|
'amazonaws.com' => 'Amazon AWS',
|
||||||
'googleusercontent.com' => 'Google Cloud',
|
'googleusercontent.com' => 'Google Cloud',
|
||||||
@@ -183,15 +183,15 @@ function getHostInfo(string $hostname): array {
|
|||||||
return $info;
|
return $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Basis-Infos zur IP
|
|
||||||
*/
|
|
||||||
function getIpInfo(string $ip): array {
|
function getIpInfo(string $ip): array {
|
||||||
$info = [
|
$info = [
|
||||||
'type' => 'unknown'
|
'type' => 'unknown'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Private IP-Bereiche prüfen
|
|
||||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
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)) {
|
if (preg_match('/^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/', $ip)) {
|
||||||
$info['type'] = 'private';
|
$info['type'] = 'private';
|
||||||
@@ -201,7 +201,7 @@ function getIpInfo(string $ip): array {
|
|||||||
$info['type'] = 'public';
|
$info['type'] = 'public';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// IPv6
|
|
||||||
if (preg_match('/^(fc|fd)/i', $ip)) {
|
if (preg_match('/^(fc|fd)/i', $ip)) {
|
||||||
$info['type'] = 'private';
|
$info['type'] = 'private';
|
||||||
} elseif (preg_match('/^::1$/', $ip) || preg_match('/^fe80:/i', $ip)) {
|
} elseif (preg_match('/^::1$/', $ip) || preg_match('/^fe80:/i', $ip)) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?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';
|
require_once __DIR__ . '/../includes/api-helpers.php';
|
||||||
|
|
||||||
@@ -43,9 +43,9 @@ echo json_encode([
|
|||||||
'ssl' => $sslData
|
'ssl' => $sslData
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft SSL-Zertifikat einer Domain
|
|
||||||
*/
|
|
||||||
function checkSslCertificate(string $domain): array {
|
function checkSslCertificate(string $domain): array {
|
||||||
$result = [
|
$result = [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
@@ -55,7 +55,7 @@ function checkSslCertificate(string $domain): array {
|
|||||||
'certificate' => null
|
'certificate' => null
|
||||||
];
|
];
|
||||||
|
|
||||||
// Stream Context für SSL
|
|
||||||
$context = stream_context_create([
|
$context = stream_context_create([
|
||||||
'ssl' => [
|
'ssl' => [
|
||||||
'capture_peer_cert' => true,
|
'capture_peer_cert' => true,
|
||||||
@@ -64,18 +64,18 @@ function checkSslCertificate(string $domain): array {
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Verbindung herstellen
|
|
||||||
$socket = @stream_socket_client(
|
$socket = @stream_socket_client(
|
||||||
"ssl://{$domain}:443",
|
"ssl://{$domain}:443",
|
||||||
$errno,
|
$errno,
|
||||||
$errstr,
|
$errstr,
|
||||||
10, // Timeout
|
10,
|
||||||
STREAM_CLIENT_CONNECT,
|
STREAM_CLIENT_CONNECT,
|
||||||
$context
|
$context
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!$socket) {
|
if (!$socket) {
|
||||||
// Versuche ohne SSL (um zu prüfen ob Server erreichbar)
|
|
||||||
$httpSocket = @fsockopen($domain, 80, $errno, $errstr, 5);
|
$httpSocket = @fsockopen($domain, 80, $errno, $errstr, 5);
|
||||||
if ($httpSocket) {
|
if ($httpSocket) {
|
||||||
fclose($httpSocket);
|
fclose($httpSocket);
|
||||||
@@ -89,7 +89,7 @@ function checkSslCertificate(string $domain): array {
|
|||||||
$result['has_ssl'] = true;
|
$result['has_ssl'] = true;
|
||||||
$result['success'] = true;
|
$result['success'] = true;
|
||||||
|
|
||||||
// Zertifikat extrahieren
|
|
||||||
$params = stream_context_get_params($socket);
|
$params = stream_context_get_params($socket);
|
||||||
$cert = $params['options']['ssl']['peer_certificate'] ?? null;
|
$cert = $params['options']['ssl']['peer_certificate'] ?? null;
|
||||||
|
|
||||||
@@ -105,10 +105,10 @@ function checkSslCertificate(string $domain): array {
|
|||||||
$isNotYetValid = $now < $validFrom;
|
$isNotYetValid = $now < $validFrom;
|
||||||
$result['is_valid'] = !$isExpired && !$isNotYetValid;
|
$result['is_valid'] = !$isExpired && !$isNotYetValid;
|
||||||
|
|
||||||
// Tage bis Ablauf
|
|
||||||
$daysUntilExpiry = floor(($validTo - $now) / 86400);
|
$daysUntilExpiry = floor(($validTo - $now) / 86400);
|
||||||
|
|
||||||
// Subject Alternative Names (SANs)
|
|
||||||
$sans = [];
|
$sans = [];
|
||||||
if (isset($certInfo['extensions']['subjectAltName'])) {
|
if (isset($certInfo['extensions']['subjectAltName'])) {
|
||||||
$sanStr = $certInfo['extensions']['subjectAltName'];
|
$sanStr = $certInfo['extensions']['subjectAltName'];
|
||||||
@@ -116,7 +116,7 @@ function checkSslCertificate(string $domain): array {
|
|||||||
$sans = $matches[1] ?? [];
|
$sans = $matches[1] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issuer aufbereiten
|
|
||||||
$issuer = [];
|
$issuer = [];
|
||||||
if (isset($certInfo['issuer'])) {
|
if (isset($certInfo['issuer'])) {
|
||||||
if (isset($certInfo['issuer']['O'])) $issuer['organization'] = $certInfo['issuer']['O'];
|
if (isset($certInfo['issuer']['O'])) $issuer['organization'] = $certInfo['issuer']['O'];
|
||||||
@@ -124,7 +124,7 @@ function checkSslCertificate(string $domain): array {
|
|||||||
if (isset($certInfo['issuer']['C'])) $issuer['country'] = $certInfo['issuer']['C'];
|
if (isset($certInfo['issuer']['C'])) $issuer['country'] = $certInfo['issuer']['C'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subject aufbereiten
|
|
||||||
$subject = [];
|
$subject = [];
|
||||||
if (isset($certInfo['subject'])) {
|
if (isset($certInfo['subject'])) {
|
||||||
if (isset($certInfo['subject']['CN'])) $subject['common_name'] = $certInfo['subject']['CN'];
|
if (isset($certInfo['subject']['CN'])) $subject['common_name'] = $certInfo['subject']['CN'];
|
||||||
@@ -144,7 +144,7 @@ function checkSslCertificate(string $domain): array {
|
|||||||
'version' => $certInfo['version'] ?? null,
|
'version' => $certInfo['version'] ?? null,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Warnung wenn bald ablaufend
|
|
||||||
if ($daysUntilExpiry <= 30 && $daysUntilExpiry > 0) {
|
if ($daysUntilExpiry <= 30 && $daysUntilExpiry > 0) {
|
||||||
$result['warning'] = "Zertifikat läuft in {$daysUntilExpiry} Tagen ab!";
|
$result['warning'] = "Zertifikat läuft in {$daysUntilExpiry} Tagen ab!";
|
||||||
} elseif ($isExpired) {
|
} elseif ($isExpired) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?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';
|
require_once __DIR__ . '/../includes/api-helpers.php';
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ if (empty($domain)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nur Root-Domain extrahieren
|
|
||||||
$domain = extractRootDomain($domain);
|
$domain = extractRootDomain($domain);
|
||||||
|
|
||||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-]*\.[a-zA-Z]{2,}$/', $domain)) {
|
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-]*\.[a-zA-Z]{2,}$/', $domain)) {
|
||||||
@@ -58,9 +58,9 @@ echo json_encode([
|
|||||||
'whois' => $whoisData
|
'whois' => $whoisData
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
/**
|
|
||||||
* Extrahiert die Root-Domain (ohne Subdomain)
|
|
||||||
*/
|
|
||||||
function extractRootDomain(string $domain): string {
|
function extractRootDomain(string $domain): string {
|
||||||
$domain = strtolower($domain);
|
$domain = strtolower($domain);
|
||||||
$domain = preg_replace('/^(https?:\/\/)?(www\.)?/', '', $domain);
|
$domain = preg_replace('/^(https?:\/\/)?(www\.)?/', '', $domain);
|
||||||
@@ -68,22 +68,22 @@ function extractRootDomain(string $domain): string {
|
|||||||
|
|
||||||
$parts = explode('.', $domain);
|
$parts = explode('.', $domain);
|
||||||
if (count($parts) > 2) {
|
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 implode('.', array_slice($parts, -2));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $domain;
|
return $domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Führt WHOIS-Lookup durch
|
|
||||||
*/
|
|
||||||
function performWhoisLookup(string $domain): ?array {
|
function performWhoisLookup(string $domain): ?array {
|
||||||
// Primär: Socket-basierte Abfrage (funktioniert ohne shell_exec)
|
|
||||||
$whoisRaw = whoisViaSocket($domain);
|
$whoisRaw = whoisViaSocket($domain);
|
||||||
|
|
||||||
// Fallback: Shell-Kommando (sicher escaped)
|
|
||||||
if (empty($whoisRaw) && function_exists('shell_exec')) {
|
if (empty($whoisRaw) && function_exists('shell_exec')) {
|
||||||
$escapedDomain = escapeshellarg($domain);
|
$escapedDomain = escapeshellarg($domain);
|
||||||
$whoisRaw = @shell_exec("whois {$escapedDomain} 2>/dev/null");
|
$whoisRaw = @shell_exec("whois {$escapedDomain} 2>/dev/null");
|
||||||
@@ -93,13 +93,13 @@ function performWhoisLookup(string $domain): ?array {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse WHOIS-Daten
|
|
||||||
return parseWhoisData($whoisRaw, $domain);
|
return parseWhoisData($whoisRaw, $domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* WHOIS-Abfrage über Socket (unabhängig von shell_exec)
|
|
||||||
*/
|
|
||||||
function whoisViaSocket(string $domain): ?string {
|
function whoisViaSocket(string $domain): ?string {
|
||||||
$whoisServer = getWhoisServer($domain);
|
$whoisServer = getWhoisServer($domain);
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ function whoisViaSocket(string $domain): ?string {
|
|||||||
|
|
||||||
$result = queryWhoisServer($whoisServer, $domain);
|
$result = queryWhoisServer($whoisServer, $domain);
|
||||||
|
|
||||||
// Prüfe auf Weiterleitungen zu anderen WHOIS-Servern
|
|
||||||
if ($result && preg_match('/Registrar WHOIS Server:\s*(\S+)/i', $result, $matches)) {
|
if ($result && preg_match('/Registrar WHOIS Server:\s*(\S+)/i', $result, $matches)) {
|
||||||
$referralServer = trim($matches[1]);
|
$referralServer = trim($matches[1]);
|
||||||
if ($referralServer && $referralServer !== $whoisServer) {
|
if ($referralServer && $referralServer !== $whoisServer) {
|
||||||
@@ -123,9 +123,9 @@ function whoisViaSocket(string $domain): ?string {
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Abfrage an einen spezifischen WHOIS-Server
|
|
||||||
*/
|
|
||||||
function queryWhoisServer(string $server, string $domain): ?string {
|
function queryWhoisServer(string $server, string $domain): ?string {
|
||||||
$port = 43;
|
$port = 43;
|
||||||
$timeout = 10;
|
$timeout = 10;
|
||||||
@@ -136,13 +136,13 @@ function queryWhoisServer(string $server, string $domain): ?string {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setze Stream-Timeout
|
|
||||||
stream_set_timeout($socket, $timeout);
|
stream_set_timeout($socket, $timeout);
|
||||||
|
|
||||||
// Sende Anfrage
|
|
||||||
fwrite($socket, $domain . "\r\n");
|
fwrite($socket, $domain . "\r\n");
|
||||||
|
|
||||||
// Lese Antwort
|
|
||||||
$response = '';
|
$response = '';
|
||||||
while (!feof($socket)) {
|
while (!feof($socket)) {
|
||||||
$response .= fread($socket, 8192);
|
$response .= fread($socket, 8192);
|
||||||
@@ -153,16 +153,16 @@ function queryWhoisServer(string $server, string $domain): ?string {
|
|||||||
return !empty($response) ? $response : null;
|
return !empty($response) ? $response : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ermittelt den zuständigen WHOIS-Server für eine TLD
|
|
||||||
*/
|
|
||||||
function getWhoisServer(string $domain): ?string {
|
function getWhoisServer(string $domain): ?string {
|
||||||
$parts = explode('.', $domain);
|
$parts = explode('.', $domain);
|
||||||
$tld = strtolower(end($parts));
|
$tld = strtolower(end($parts));
|
||||||
|
|
||||||
// Bekannte WHOIS-Server nach TLD
|
|
||||||
$whoisServers = [
|
$whoisServers = [
|
||||||
// Generische TLDs
|
|
||||||
'com' => 'whois.verisign-grs.com',
|
'com' => 'whois.verisign-grs.com',
|
||||||
'net' => 'whois.verisign-grs.com',
|
'net' => 'whois.verisign-grs.com',
|
||||||
'org' => 'whois.pir.org',
|
'org' => 'whois.pir.org',
|
||||||
@@ -186,7 +186,7 @@ function getWhoisServer(string $domain): ?string {
|
|||||||
'travel' => 'whois.nic.travel',
|
'travel' => 'whois.nic.travel',
|
||||||
'xxx' => 'whois.nic.xxx',
|
'xxx' => 'whois.nic.xxx',
|
||||||
|
|
||||||
// Neue gTLDs
|
|
||||||
'app' => 'whois.nic.google',
|
'app' => 'whois.nic.google',
|
||||||
'dev' => 'whois.nic.google',
|
'dev' => 'whois.nic.google',
|
||||||
'page' => 'whois.nic.google',
|
'page' => 'whois.nic.google',
|
||||||
@@ -205,7 +205,7 @@ function getWhoisServer(string $domain): ?string {
|
|||||||
'cc' => 'ccwhois.verisign-grs.com',
|
'cc' => 'ccwhois.verisign-grs.com',
|
||||||
'ws' => 'whois.website.ws',
|
'ws' => 'whois.website.ws',
|
||||||
|
|
||||||
// Europäische ccTLDs
|
|
||||||
'de' => 'whois.denic.de',
|
'de' => 'whois.denic.de',
|
||||||
'at' => 'whois.nic.at',
|
'at' => 'whois.nic.at',
|
||||||
'ch' => 'whois.nic.ch',
|
'ch' => 'whois.nic.ch',
|
||||||
@@ -235,7 +235,7 @@ function getWhoisServer(string $domain): ?string {
|
|||||||
'eu' => 'whois.eu',
|
'eu' => 'whois.eu',
|
||||||
'lu' => 'whois.dns.lu',
|
'lu' => 'whois.dns.lu',
|
||||||
|
|
||||||
// Andere ccTLDs
|
|
||||||
'ru' => 'whois.tcinet.ru',
|
'ru' => 'whois.tcinet.ru',
|
||||||
'ua' => 'whois.ua',
|
'ua' => 'whois.ua',
|
||||||
'us' => 'whois.nic.us',
|
'us' => 'whois.nic.us',
|
||||||
@@ -255,7 +255,7 @@ function getWhoisServer(string $domain): ?string {
|
|||||||
'za' => 'whois.registry.net.za',
|
'za' => 'whois.registry.net.za',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Spezielle Behandlung für .co.uk, .com.au etc.
|
|
||||||
if (count($parts) >= 2) {
|
if (count($parts) >= 2) {
|
||||||
$sld = $parts[count($parts) - 2];
|
$sld = $parts[count($parts) - 2];
|
||||||
$combinedTld = $sld . '.' . $tld;
|
$combinedTld = $sld . '.' . $tld;
|
||||||
@@ -279,9 +279,9 @@ function getWhoisServer(string $domain): ?string {
|
|||||||
return $whoisServers[$tld] ?? 'whois.iana.org';
|
return $whoisServers[$tld] ?? 'whois.iana.org';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parsed WHOIS-Rohdaten in strukturiertes Format
|
|
||||||
*/
|
|
||||||
function parseWhoisData(string $raw, string $domain): array {
|
function parseWhoisData(string $raw, string $domain): array {
|
||||||
$data = [
|
$data = [
|
||||||
'raw' => $raw,
|
'raw' => $raw,
|
||||||
@@ -306,50 +306,50 @@ function parseWhoisData(string $raw, string $domain): array {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key: Value Format
|
|
||||||
if (strpos($line, ':') !== false) {
|
if (strpos($line, ':') !== false) {
|
||||||
list($key, $value) = array_map('trim', explode(':', $line, 2));
|
list($key, $value) = array_map('trim', explode(':', $line, 2));
|
||||||
$keyLower = strtolower($key);
|
$keyLower = strtolower($key);
|
||||||
|
|
||||||
// Registrar
|
|
||||||
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'abuse') === false && strpos($keyLower, 'url') === false) {
|
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'abuse') === false && strpos($keyLower, 'url') === false) {
|
||||||
if (empty($data['parsed']['registrar'])) {
|
if (empty($data['parsed']['registrar'])) {
|
||||||
$data['parsed']['registrar'] = $value;
|
$data['parsed']['registrar'] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registrar URL
|
|
||||||
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'url') !== false) {
|
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'url') !== false) {
|
||||||
$data['parsed']['registrar_url'] = $value;
|
$data['parsed']['registrar_url'] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erstellungsdatum
|
|
||||||
if (preg_match('/(creation|created|registered)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
if (preg_match('/(creation|created|registered)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
||||||
if (empty($data['parsed']['creation_date'])) {
|
if (empty($data['parsed']['creation_date'])) {
|
||||||
$data['parsed']['creation_date'] = $value;
|
$data['parsed']['creation_date'] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ablaufdatum
|
|
||||||
if (preg_match('/(expir|paid-till)/i', $keyLower)) {
|
if (preg_match('/(expir|paid-till)/i', $keyLower)) {
|
||||||
if (empty($data['parsed']['expiration_date'])) {
|
if (empty($data['parsed']['expiration_date'])) {
|
||||||
$data['parsed']['expiration_date'] = $value;
|
$data['parsed']['expiration_date'] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aktualisierungsdatum
|
|
||||||
if (preg_match('/(updated|modified|changed)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
if (preg_match('/(updated|modified|changed)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
||||||
if (empty($data['parsed']['updated_date'])) {
|
if (empty($data['parsed']['updated_date'])) {
|
||||||
$data['parsed']['updated_date'] = $value;
|
$data['parsed']['updated_date'] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status
|
|
||||||
if (preg_match('/(status|state)/i', $keyLower) && !empty($value)) {
|
if (preg_match('/(status|state)/i', $keyLower) && !empty($value)) {
|
||||||
$data['parsed']['status'][] = $value;
|
$data['parsed']['status'][] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nameserver
|
|
||||||
if (preg_match('/^(name.?server|nserver)/i', $keyLower) && !empty($value)) {
|
if (preg_match('/^(name.?server|nserver)/i', $keyLower) && !empty($value)) {
|
||||||
$ns = strtolower(explode(' ', $value)[0]);
|
$ns = strtolower(explode(' ', $value)[0]);
|
||||||
if (!in_array($ns, $data['parsed']['nameservers'])) {
|
if (!in_array($ns, $data['parsed']['nameservers'])) {
|
||||||
@@ -357,14 +357,14 @@ function parseWhoisData(string $raw, string $domain): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSSEC
|
|
||||||
if (strpos($keyLower, 'dnssec') !== false) {
|
if (strpos($keyLower, 'dnssec') !== false) {
|
||||||
$data['parsed']['dnssec'] = $value;
|
$data['parsed']['dnssec'] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status einzigartig machen
|
|
||||||
$data['parsed']['status'] = array_unique($data['parsed']['status']);
|
$data['parsed']['status'] = array_unique($data['parsed']['status']);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
|||||||
@@ -2,5 +2,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
require_once __DIR__ . '/mail-config.php';
|
require_once __DIR__ . '/mail-config.php';
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* Zentrale Betreff-Konfiguration für das Kontaktformular
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string> Betreff-Schlüssel => Anzeigename
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getContactSubjectMap(): array {
|
function getContactSubjectMap(): array {
|
||||||
return [
|
return [
|
||||||
'allgemeine-anfrage' => 'Allgemeine Anfrage',
|
'allgemeine-anfrage' => 'Allgemeine Anfrage',
|
||||||
@@ -26,16 +26,16 @@ function getContactSubjectMap(): array {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $subjectKey
|
|
||||||
*/
|
|
||||||
function isAllowedContactSubject(string $subjectKey): bool {
|
function isAllowedContactSubject(string $subjectKey): bool {
|
||||||
return array_key_exists($subjectKey, getContactSubjectMap());
|
return array_key_exists($subjectKey, getContactSubjectMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Betreff aus ?product= oder ?package= für die Kontaktseite ableiten
|
|
||||||
*/
|
|
||||||
function getPreselectedContactSubject(): string {
|
function getPreselectedContactSubject(): string {
|
||||||
$productMap = [
|
$productMap = [
|
||||||
'vpc' => 'vpc-anfrage',
|
'vpc' => 'vpc-anfrage',
|
||||||
|
|||||||
@@ -2,13 +2,18 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
|
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
|
||||||
define('SMTP_TO_EMAIL', 'info@hexahost.de');
|
define('SMTP_TO_EMAIL', 'info@hexahost.de');
|
||||||
|
|
||||||
|
|
||||||
define('ENABLE_CSRF_PROTECTION', true);
|
define('ENABLE_CSRF_PROTECTION', true);
|
||||||
define('ENABLE_RATE_LIMITING', true);
|
define('ENABLE_RATE_LIMITING', true);
|
||||||
define('MAX_REQUESTS_PER_HOUR', 10);
|
define('MAX_REQUESTS_PER_HOUR', 5);
|
||||||
|
|
||||||
|
|
||||||
define('ENABLE_SPAM_PROTECTION', true);
|
define('ENABLE_SPAM_PROTECTION', true);
|
||||||
@@ -31,34 +36,21 @@ define('ADDITIONAL_HEADERS', [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
define('ALLOWED_EMAIL_DOMAINS', [
|
define('ALLOWED_EMAIL_DOMAINS', [
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
define('BLACKLISTED_EMAILS', [
|
define('BLACKLISTED_EMAILS', [
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
if (!defined('SMTP_HOST') || !defined('SMTP_USERNAME') || !defined('SMTP_PASSWORD')) {
|
|
||||||
die('SMTP-Konfiguration ist unvollständig. Bitte überprüfen Sie die mail-config.php');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) {
|
if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) {
|
||||||
die('Ungültige SMTP_FROM_EMAIL Adresse');
|
die('Ungültige SMTP_FROM_EMAIL Adresse');
|
||||||
}
|
}
|
||||||
@@ -85,20 +77,17 @@ function logEmail($type, $data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function isValidEmail($email) {
|
function isValidEmail($email) {
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (in_array($email, BLACKLISTED_EMAILS)) {
|
if (in_array($email, BLACKLISTED_EMAILS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!empty(ALLOWED_EMAIL_DOMAINS)) {
|
if (!empty(ALLOWED_EMAIL_DOMAINS)) {
|
||||||
$domain = substr(strrchr($email, "@"), 1);
|
$domain = substr(strrchr($email, "@"), 1);
|
||||||
if (!in_array($domain, ALLOWED_EMAIL_DOMAINS)) {
|
if (!in_array($domain, ALLOWED_EMAIL_DOMAINS)) {
|
||||||
@@ -111,6 +100,11 @@ function isValidEmail($email) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getHexaHostConfig($key = null) {
|
function getHexaHostConfig($key = null) {
|
||||||
$config = [
|
$config = [
|
||||||
|
|
||||||
@@ -120,7 +114,6 @@ function getHexaHostConfig($key = null) {
|
|||||||
'to_name' => 'HexaHost Support',
|
'to_name' => 'HexaHost Support',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR,
|
'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR,
|
||||||
'honeypot_field' => 'website',
|
'honeypot_field' => 'website',
|
||||||
'enable_csrf' => ENABLE_CSRF_PROTECTION,
|
'enable_csrf' => ENABLE_CSRF_PROTECTION,
|
||||||
@@ -128,7 +121,6 @@ function getHexaHostConfig($key = null) {
|
|||||||
'max_message_length' => MAX_MESSAGE_LENGTH,
|
'max_message_length' => MAX_MESSAGE_LENGTH,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'debug_mode' => DEBUG_MODE,
|
'debug_mode' => DEBUG_MODE,
|
||||||
'log_errors' => LOG_EMAILS,
|
'log_errors' => LOG_EMAILS,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,6 +4,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$PRODUCTS['vpc'] = [
|
$PRODUCTS['vpc'] = [
|
||||||
'name' => 'Virtual Private Container',
|
'name' => 'Virtual Private Container',
|
||||||
'short_name' => 'VPC',
|
'short_name' => 'VPC',
|
||||||
@@ -21,6 +31,7 @@ $PRODUCTS['vpc'] = [
|
|||||||
'starter' => [
|
'starter' => [
|
||||||
'name' => 'VPC Starter',
|
'name' => 'VPC Starter',
|
||||||
'price' => '4,99',
|
'price' => '4,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
||||||
@@ -40,6 +51,7 @@ $PRODUCTS['vpc'] = [
|
|||||||
'business' => [
|
'business' => [
|
||||||
'name' => 'VPC Business',
|
'name' => 'VPC Business',
|
||||||
'price' => '9,99',
|
'price' => '9,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => true,
|
'featured' => true,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
||||||
@@ -60,6 +72,7 @@ $PRODUCTS['vpc'] = [
|
|||||||
'professional' => [
|
'professional' => [
|
||||||
'name' => 'VPC Professional',
|
'name' => 'VPC Professional',
|
||||||
'price' => '19,99',
|
'price' => '19,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
||||||
@@ -81,6 +94,7 @@ $PRODUCTS['vpc'] = [
|
|||||||
'enterprise' => [
|
'enterprise' => [
|
||||||
'name' => 'VPC Enterprise',
|
'name' => 'VPC Enterprise',
|
||||||
'price' => '39,99',
|
'price' => '39,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
||||||
@@ -123,6 +137,7 @@ $PRODUCTS['vps'] = [
|
|||||||
'starter' => [
|
'starter' => [
|
||||||
'name' => 'VPS Starter',
|
'name' => 'VPS Starter',
|
||||||
'price' => '9,99',
|
'price' => '9,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
||||||
@@ -142,6 +157,7 @@ $PRODUCTS['vps'] = [
|
|||||||
'business' => [
|
'business' => [
|
||||||
'name' => 'VPS Business',
|
'name' => 'VPS Business',
|
||||||
'price' => '19,99',
|
'price' => '19,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => true,
|
'featured' => true,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
||||||
@@ -162,6 +178,7 @@ $PRODUCTS['vps'] = [
|
|||||||
'professional' => [
|
'professional' => [
|
||||||
'name' => 'VPS Professional',
|
'name' => 'VPS Professional',
|
||||||
'price' => '39,99',
|
'price' => '39,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
||||||
@@ -183,6 +200,7 @@ $PRODUCTS['vps'] = [
|
|||||||
'enterprise' => [
|
'enterprise' => [
|
||||||
'name' => 'VPS Enterprise',
|
'name' => 'VPS Enterprise',
|
||||||
'price' => '79,99',
|
'price' => '79,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
||||||
@@ -225,6 +243,7 @@ $PRODUCTS['mail-gateway'] = [
|
|||||||
'starter' => [
|
'starter' => [
|
||||||
'name' => 'Mail Starter',
|
'name' => 'Mail Starter',
|
||||||
'price' => '4,99',
|
'price' => '4,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Postfächer', 'value' => '5'],
|
['label' => 'Postfächer', 'value' => '5'],
|
||||||
@@ -243,6 +262,7 @@ $PRODUCTS['mail-gateway'] = [
|
|||||||
'business' => [
|
'business' => [
|
||||||
'name' => 'Mail Business',
|
'name' => 'Mail Business',
|
||||||
'price' => '14,99',
|
'price' => '14,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => true,
|
'featured' => true,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Postfächer', 'value' => '25'],
|
['label' => 'Postfächer', 'value' => '25'],
|
||||||
@@ -263,6 +283,7 @@ $PRODUCTS['mail-gateway'] = [
|
|||||||
'professional' => [
|
'professional' => [
|
||||||
'name' => 'Mail Professional',
|
'name' => 'Mail Professional',
|
||||||
'price' => '29,99',
|
'price' => '29,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Postfächer', 'value' => '100'],
|
['label' => 'Postfächer', 'value' => '100'],
|
||||||
@@ -284,6 +305,7 @@ $PRODUCTS['mail-gateway'] = [
|
|||||||
'enterprise' => [
|
'enterprise' => [
|
||||||
'name' => 'Mail Enterprise',
|
'name' => 'Mail Enterprise',
|
||||||
'price' => '59,99',
|
'price' => '59,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Postfächer', 'value' => 'Unbegrenzt'],
|
['label' => 'Postfächer', 'value' => 'Unbegrenzt'],
|
||||||
@@ -327,6 +349,7 @@ $PRODUCTS['webhosting'] = [
|
|||||||
'starter' => [
|
'starter' => [
|
||||||
'name' => 'Webhosting Starter',
|
'name' => 'Webhosting Starter',
|
||||||
'price' => '4,99',
|
'price' => '4,99',
|
||||||
|
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-starter',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Webspace', 'value' => '10 GB'],
|
['label' => 'Webspace', 'value' => '10 GB'],
|
||||||
@@ -349,6 +372,7 @@ $PRODUCTS['webhosting'] = [
|
|||||||
'business' => [
|
'business' => [
|
||||||
'name' => 'Webhosting Business',
|
'name' => 'Webhosting Business',
|
||||||
'price' => '7,99',
|
'price' => '7,99',
|
||||||
|
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-business',
|
||||||
'featured' => true,
|
'featured' => true,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Webspace', 'value' => '30 GB'],
|
['label' => 'Webspace', 'value' => '30 GB'],
|
||||||
@@ -371,6 +395,7 @@ $PRODUCTS['webhosting'] = [
|
|||||||
'professional' => [
|
'professional' => [
|
||||||
'name' => 'Webhosting Professional',
|
'name' => 'Webhosting Professional',
|
||||||
'price' => '9,99',
|
'price' => '9,99',
|
||||||
|
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-professional',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Webspace', 'value' => '50 GB'],
|
['label' => 'Webspace', 'value' => '50 GB'],
|
||||||
@@ -393,6 +418,7 @@ $PRODUCTS['webhosting'] = [
|
|||||||
'enterprise' => [
|
'enterprise' => [
|
||||||
'name' => 'Webhosting Enterprise',
|
'name' => 'Webhosting Enterprise',
|
||||||
'price' => '29,99',
|
'price' => '29,99',
|
||||||
|
'shop_url' => '',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'specs' => [
|
'specs' => [
|
||||||
['label' => 'Webspace', 'value' => '200 GB'],
|
['label' => 'Webspace', 'value' => '200 GB'],
|
||||||
@@ -418,6 +444,41 @@ $PRODUCTS['webhosting'] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -428,41 +489,87 @@ function getAllProducts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getProduct($productId) {
|
function getProduct($productId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId] ?? null;
|
return $PRODUCTS[$productId] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getProductPackages($productId) {
|
function getProductPackages($productId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId]['packages'] ?? [];
|
return $PRODUCTS[$productId]['packages'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getPackage($productId, $packageId) {
|
function getPackage($productId, $packageId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
|
return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getPackagePrice($productId, $packageId) {
|
function getPackagePrice($productId, $packageId) {
|
||||||
$package = getPackage($productId, $packageId);
|
$package = getPackage($productId, $packageId);
|
||||||
return $package['price'] ?? null;
|
return $package['price'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getMinPrice($productId) {
|
function getMinPrice($productId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId]['min_price'] ?? null;
|
return $PRODUCTS[$productId]['min_price'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function formatPrice($price, $withCurrency = true) {
|
function formatPrice($price, $withCurrency = true) {
|
||||||
return $withCurrency ? $price . '€' : $price;
|
return $withCurrency ? $price . '€' : $price;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
function renderPackageCard($productId, $packageId, $package) {
|
||||||
$featuredClass = $package['featured'] ? ' featured' : '';
|
$featuredClass = $package['featured'] ? ' featured' : '';
|
||||||
$featuredBadge = $package['featured'] ? '<div class="featured-badge">Beliebt</div>' : '';
|
$featuredBadge = $package['featured'] ? '<div class="featured-badge">Beliebt</div>' : '';
|
||||||
@@ -497,7 +604,7 @@ function renderPackageCard($productId, $packageId, $package) {
|
|||||||
<div class="package-features">
|
<div class="package-features">
|
||||||
%s
|
%s
|
||||||
</div>
|
</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>',
|
</div>',
|
||||||
$featuredClass,
|
$featuredClass,
|
||||||
$featuredBadge,
|
$featuredBadge,
|
||||||
@@ -505,12 +612,13 @@ function renderPackageCard($productId, $packageId, $package) {
|
|||||||
$package['price'],
|
$package['price'],
|
||||||
$specsHtml,
|
$specsHtml,
|
||||||
$featuresHtml,
|
$featuresHtml,
|
||||||
$productId,
|
htmlspecialchars(getOrderUrl($productId, $packageId), ENT_QUOTES, 'UTF-8')
|
||||||
$packageId
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function renderAllPackages($productId) {
|
function renderAllPackages($productId) {
|
||||||
$packages = getProductPackages($productId);
|
$packages = getProductPackages($productId);
|
||||||
$html = '';
|
$html = '';
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* Gemeinsame Hilfsfunktionen für öffentliche DNS/API-Endpunkte
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client-IP für Rate-Limiting (Cloudflare-sicher, kein blindes X-Forwarded-For)
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getApiClientIp(): string {
|
function getApiClientIp(): string {
|
||||||
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
|
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
|
||||||
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
|
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
|
||||||
@@ -34,9 +34,9 @@ function getApiClientIp(): string {
|
|||||||
return $remoteAddr;
|
return $remoteAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Einfaches Rate-Limiting pro Endpunkt und IP
|
|
||||||
*/
|
|
||||||
function checkApiRateLimit(string $endpoint, int $maxPerHour = 120): bool {
|
function checkApiRateLimit(string $endpoint, int $maxPerHour = 120): bool {
|
||||||
$ip = getApiClientIp();
|
$ip = getApiClientIp();
|
||||||
$cacheFile = sys_get_temp_dir() . '/hexahost_api_' . md5($endpoint . '_' . $ip) . '.txt';
|
$cacheFile = sys_get_temp_dir() . '/hexahost_api_' . md5($endpoint . '_' . $ip) . '.txt';
|
||||||
@@ -82,9 +82,9 @@ function checkApiRateLimit(string $endpoint, int $maxPerHour = 120): bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain aus GET-Parameter normalisieren und validieren
|
|
||||||
*/
|
|
||||||
function getValidatedDomainParam(string $param = 'domain'): ?string {
|
function getValidatedDomainParam(string $param = 'domain'): ?string {
|
||||||
if (empty($_GET[$param])) {
|
if (empty($_GET[$param])) {
|
||||||
return null;
|
return null;
|
||||||
@@ -102,9 +102,9 @@ function getValidatedDomainParam(string $param = 'domain'): ?string {
|
|||||||
return $domain;
|
return $domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rate-Limit-JSON-Antwort senden und beenden
|
|
||||||
*/
|
|
||||||
function rejectApiRateLimit(): void {
|
function rejectApiRateLimit(): void {
|
||||||
http_response_code(429);
|
http_response_code(429);
|
||||||
echo json_encode(['error' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.']);
|
echo json_encode(['error' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.']);
|
||||||
|
|||||||
@@ -15,10 +15,17 @@
|
|||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
<h4>Produkte</h4>
|
<h4>Produkte</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/vpc">Virtual Private Container</a></li>
|
<li<?php echo productHiddenAttr('vpc'); ?>><a href="/vpc">Virtual Private Container</a></li>
|
||||||
<li><a href="/vps">Virtual Private Server</a></li>
|
<li<?php echo productHiddenAttr('vps'); ?>><a href="/vps">Virtual Private Server</a></li>
|
||||||
<li><a href="/mail-gateway">Mail Gateway</a></li>
|
<li<?php echo productHiddenAttr('mail-gateway'); ?>><a href="/mail-gateway">Mail Gateway</a></li>
|
||||||
<li><a href="/webhosting">Webhosting</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
@@ -49,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<!-- Cookie Consent Banner -->
|
||||||
<div id="cookieConsent" class="cookie-consent" role="dialog" aria-labelledby="cookieConsentTitle" aria-describedby="cookieConsentDesc">
|
<div id="cookieConsent" class="cookie-consent" role="dialog" aria-labelledby="cookieConsentTitle" aria-describedby="cookieConsentDesc">
|
||||||
<div class="cookie-consent-container">
|
<div class="cookie-consent-container">
|
||||||
<div class="cookie-consent-content">
|
<div class="cookie-consent-content">
|
||||||
@@ -79,7 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Erweiterte Cookie-Einstellungen (standardmäßig versteckt) -->
|
||||||
<div id="cookieSettingsPanel" class="cookie-settings-panel" style="display: none;">
|
<div id="cookieSettingsPanel" class="cookie-settings-panel" style="display: none;">
|
||||||
<div class="cookie-settings-content">
|
<div class="cookie-settings-content">
|
||||||
<h4>Cookie-Einstellungen</h4>
|
<h4>Cookie-Einstellungen</h4>
|
||||||
@@ -121,12 +128,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Google Analytics (GA4) mit Consent Mode -->
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag(){dataLayer.push(arguments);}
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
|
||||||
// Standard: keine Analyse/Marketing-Cookies bis zur Einwilligung
|
|
||||||
gtag('consent', 'default', {
|
gtag('consent', 'default', {
|
||||||
analytics_storage: 'denied',
|
analytics_storage: 'denied',
|
||||||
ad_storage: 'denied',
|
ad_storage: 'denied',
|
||||||
@@ -139,7 +146,7 @@
|
|||||||
anonymize_ip: true
|
anonymize_ip: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Übergibt Consent-Änderungen aus dem eigenen Cookie-Banner an GA
|
|
||||||
window.addEventListener('cookieConsentUpdated', function (event) {
|
window.addEventListener('cookieConsentUpdated', function (event) {
|
||||||
var payload = event && event.detail ? event.detail : {};
|
var payload = event && event.detail ? event.detail : {};
|
||||||
var consent = payload.consent ? payload.consent : payload;
|
var consent = payload.consent ? payload.consent : payload;
|
||||||
@@ -156,8 +163,8 @@
|
|||||||
</script>
|
</script>
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-EF0E9VPMTD"></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/main.9189c38109cf.js" defer></script>
|
||||||
<script src="/assets/js/cookie-consent.js" defer></script>
|
<script src="/assets/js/cookie-consent.91c79812d22c.js" defer></script>
|
||||||
<?php if (isset($additional_scripts)): ?>
|
<?php if (isset($additional_scripts)): ?>
|
||||||
<?php foreach ($additional_scripts as $script): ?>
|
<?php foreach ($additional_scripts as $script): ?>
|
||||||
<script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>
|
<script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../config/products-config.php';
|
||||||
|
|
||||||
|
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
|
||||||
ini_set('session.cookie_httponly', 1);
|
ini_set('session.cookie_httponly', 1);
|
||||||
@@ -28,6 +32,13 @@ if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
|
function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
|
||||||
global $page_title, $page_description, $current_page, $additional_scripts;
|
global $page_title, $page_description, $current_page, $additional_scripts;
|
||||||
|
|
||||||
@@ -47,11 +58,17 @@ function includeHeader($title = '', $description = '', $page = '', $scripts = []
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function includeFooter() {
|
function includeFooter() {
|
||||||
include __DIR__ . '/footer.php';
|
include __DIR__ . '/footer.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function generateBreadcrumbs($breadcrumbs) {
|
function generateBreadcrumbs($breadcrumbs) {
|
||||||
echo '<div class="breadcrumb">';
|
echo '<div class="breadcrumb">';
|
||||||
$last_index = count($breadcrumbs) - 1;
|
$last_index = count($breadcrumbs) - 1;
|
||||||
@@ -70,6 +87,10 @@ function generateBreadcrumbs($breadcrumbs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function generateCSRFToken() {
|
function generateCSRFToken() {
|
||||||
if (!isset($_SESSION['csrf_token'])) {
|
if (!isset($_SESSION['csrf_token'])) {
|
||||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
@@ -78,6 +99,8 @@ function generateCSRFToken() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function validateCSRFToken($token) {
|
function validateCSRFToken($token) {
|
||||||
if (!isset($_SESSION['csrf_token']) || !is_string($token)) {
|
if (!isset($_SESSION['csrf_token']) || !is_string($token)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -90,11 +113,15 @@ function validateCSRFToken($token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function sanitizeHeaderValue(string $value): string {
|
function sanitizeHeaderValue(string $value): string {
|
||||||
return str_replace(["\r", "\n", "\0"], '', trim($value));
|
return str_replace(["\r", "\n", "\0"], '', trim($value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getClientIP(): string {
|
function getClientIP(): string {
|
||||||
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
|
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
|
||||||
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
|
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<!-- Performance: DNS Prefetch & Preconnect -->
|
||||||
<link rel="dns-prefetch" href="//fonts.googleapis.com">
|
<link rel="dns-prefetch" href="//fonts.googleapis.com">
|
||||||
<link rel="dns-prefetch" href="//fonts.gstatic.com">
|
<link rel="dns-prefetch" href="//fonts.gstatic.com">
|
||||||
<link rel="dns-prefetch" href="//cdn.hexahost.de">
|
<link rel="dns-prefetch" href="//cdn.hexahost.de">
|
||||||
@@ -12,37 +12,37 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="preconnect" href="https://cdn.hexahost.de" crossorigin>
|
<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">
|
<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>
|
<title><?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern'; ?></title>
|
||||||
|
|
||||||
|
<!-- SEO Meta Tags -->
|
||||||
<meta name="description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.'; ?>">
|
<meta name="description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.'; ?>">
|
||||||
<meta name="robots" content="index, follow">
|
<meta name="robots" content="index, follow">
|
||||||
<meta name="author" content="HexaHost.de">
|
<meta name="author" content="HexaHost.de">
|
||||||
<meta name="theme-color" content="#0d0821">
|
<meta name="theme-color" content="#0d0821">
|
||||||
|
|
||||||
|
<!-- Open Graph / Social Media -->
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:site_name" content="HexaHost.de">
|
<meta property="og:site_name" content="HexaHost.de">
|
||||||
<meta property="og:title" content="<?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de'; ?>">
|
<meta property="og:title" content="<?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de'; ?>">
|
||||||
<meta property="og:description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'Zuverlässiges Hosting aus Niederbayern'; ?>">
|
<meta property="og:description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'Zuverlässiges Hosting aus Niederbayern'; ?>">
|
||||||
<meta property="og:locale" content="de_DE">
|
<meta property="og:locale" content="de_DE">
|
||||||
|
|
||||||
|
<!-- Main Stylesheets -->
|
||||||
|
<link rel="stylesheet" href="/assets/css/style.d01979e8c871.css">
|
||||||
|
<link rel="stylesheet" href="/assets/css/custom.d35eb3499212.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="/assets/css/style.css">
|
<!-- Fonts -->
|
||||||
<link rel="stylesheet" href="/assets/css/custom.css">
|
|
||||||
|
|
||||||
|
|
||||||
<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">
|
<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">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||||
<link rel="apple-touch-icon" href="/favicon.svg">
|
<link rel="apple-touch-icon" href="/favicon.svg">
|
||||||
|
|
||||||
|
<!-- Canonical URL (falls gesetzt) -->
|
||||||
<?php if (isset($canonical_url)): ?>
|
<?php if (isset($canonical_url)): ?>
|
||||||
<link rel="canonical" href="<?php echo htmlspecialchars($canonical_url); ?>">
|
<link rel="canonical" href="<?php echo htmlspecialchars($canonical_url); ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -59,12 +59,12 @@
|
|||||||
<ul class="nav-menu">
|
<ul class="nav-menu">
|
||||||
<li><a href="/" class="nav-link <?php echo ($current_page === 'home') ? 'active' : ''; ?>">Home</a></li>
|
<li><a href="/" class="nav-link <?php echo ($current_page === 'home') ? 'active' : ''; ?>">Home</a></li>
|
||||||
<li class="nav-dropdown">
|
<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">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="/vpc" class="<?php echo ($current_page === 'vpc') ? 'active' : ''; ?>">Virtual Private Container</a></li>
|
<li<?php echo productHiddenAttr('vpc'); ?>><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<?php echo productHiddenAttr('vps'); ?>><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<?php echo productHiddenAttr('mail-gateway'); ?>><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('webhosting'); ?>><a href="/webhosting" class="<?php echo ($current_page === 'webhosting') ? 'active' : ''; ?>">Webhosting</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li>
|
<li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
.error-code {
|
.error-code {
|
||||||
font-size: 6rem;
|
font-size: 6rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
background: linear-gradient(135deg, #ff51f9, #a348ff);
|
background: linear-gradient(135deg,
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
@@ -57,7 +57,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.error-content p {
|
.error-content p {
|
||||||
color: #888;
|
color:
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
.error-actions {
|
.error-actions {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
.error-code {
|
.error-code {
|
||||||
font-size: 6rem;
|
font-size: 6rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
background: linear-gradient(135deg, #ff51f9, #a348ff);
|
background: linear-gradient(135deg,
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
@@ -57,7 +57,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.error-content p {
|
.error-content p {
|
||||||
color: #888;
|
color:
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
.error-actions {
|
.error-actions {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- About Hero -->
|
||||||
<section class="about-hero">
|
<section class="about-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="about-hero-content">
|
<div class="about-hero-content">
|
||||||
@@ -33,7 +33,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Company Story -->
|
||||||
<section class="company-story">
|
<section class="company-story">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="story-content">
|
<div class="story-content">
|
||||||
@@ -78,7 +78,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Values -->
|
||||||
<section class="values">
|
<section class="values">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -128,9 +128,53 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<section class="team">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">Mein Team</h2>
|
||||||
|
<p class="section-description">
|
||||||
|
Die Menschen, die hinter HexaHost.de stehen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="team-content">
|
||||||
|
<div class="team-text">
|
||||||
|
<p>
|
||||||
|
Mein Team besteht aus erfahrenen IT-Experten, die langjährige Erfahrung
|
||||||
|
im Bereich Hosting und Server-Management. Ich bin leidenschaftlich
|
||||||
|
daran interessiert, Ihnen die bestmöglichen Hosting- und IT-Lösungen zu bieten.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Als regionales Unternehmen aus Niederbayern kennen wir die lokalen
|
||||||
|
Bedürfnisse und bieten persönlichen Support in deutscher Sprache.
|
||||||
|
Unser Ziel ist es, Ihnen nicht nur technische Lösungen, sondern
|
||||||
|
auch eine echte Partnerschaft zu bieten.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="team-stats glass-card">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number">5+</span>
|
||||||
|
<span class="stat-label">Jahre Erfahrung</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number">500+</span>
|
||||||
|
<span class="stat-label">Zufriedene Kunden</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number">99.9%</span>
|
||||||
|
<span class="stat-label">Uptime</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number">24/7</span>
|
||||||
|
<span class="stat-label">Support</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Technology -->
|
||||||
<section class="technology">
|
<section class="technology">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -185,7 +229,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content glass-card">
|
<div class="cta-content glass-card">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- AGB Hero -->
|
||||||
<section class="legal-hero">
|
<section class="legal-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="legal-hero-content">
|
<div class="legal-hero-content">
|
||||||
@@ -29,12 +29,12 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- AGB Content -->
|
||||||
<section class="legal-content">
|
<section class="legal-content">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="legal-container">
|
<div class="legal-container">
|
||||||
|
|
||||||
|
<!-- § 1 Geltungsbereich -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 1 Geltungsbereich</h2>
|
<h2>§ 1 Geltungsbereich</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -67,7 +67,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 2 Vertragsschluss -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 2 Vertragsschluss</h2>
|
<h2>§ 2 Vertragsschluss</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -98,7 +98,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 3 Leistungsbeschreibung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 3 Leistungsbeschreibung</h2>
|
<h2>§ 3 Leistungsbeschreibung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -129,7 +129,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 4 Preise und Zahlung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 4 Preise und Zahlungsbedingungen</h2>
|
<h2>§ 4 Preise und Zahlungsbedingungen</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -168,7 +168,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 5 Vertragslaufzeit und Kündigung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 5 Vertragslaufzeit und Kündigung</h2>
|
<h2>§ 5 Vertragslaufzeit und Kündigung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -207,7 +207,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 6 Widerrufsrecht für Verbraucher -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 6 Widerrufsrecht für Verbraucher</h2>
|
<h2>§ 6 Widerrufsrecht für Verbraucher</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -252,7 +252,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 7 Pflichten des Kunden -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 7 Pflichten des Kunden</h2>
|
<h2>§ 7 Pflichten des Kunden</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -286,7 +286,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 8 Verbotene Nutzung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 8 Verbotene Nutzung</h2>
|
<h2>§ 8 Verbotene Nutzung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -320,7 +320,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 9 Sperrung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 9 Sperrung von Diensten</h2>
|
<h2>§ 9 Sperrung von Diensten</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -350,7 +350,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 10 Haftung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 10 Haftung</h2>
|
<h2>§ 10 Haftung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -384,7 +384,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 11 Datensicherung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 11 Datensicherung</h2>
|
<h2>§ 11 Datensicherung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -406,7 +406,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 12 Datenschutz -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 12 Datenschutz</h2>
|
<h2>§ 12 Datenschutz</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -430,7 +430,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 13 Geheimhaltung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 13 Geheimhaltung</h2>
|
<h2>§ 13 Geheimhaltung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -455,7 +455,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 14 Änderungen der AGB -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 14 Änderungen der AGB</h2>
|
<h2>§ 14 Änderungen der AGB</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -478,7 +478,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- § 15 Schlussbestimmungen -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>§ 15 Schlussbestimmungen</h2>
|
<h2>§ 15 Schlussbestimmungen</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -506,7 +506,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Kontakt -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Kontakt bei Fragen</h2>
|
<h2>Kontakt bei Fragen</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
.btn-tertiary{color:var(--text-primary);background:0 0;border:1px solid rgba(255,255,255,.25);transition:.3s}.btn-tertiary:hover{border-color:var(--primary-color);color:var(--primary-color);background:rgba(255,81,249,.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,6 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
require_once __DIR__ . '/mail-config.php';
|
|
||||||
?>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Zentrale Betreff-Konfiguration für das Kontaktformular
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string> Betreff-Schlüssel => Anzeigename
|
|
||||||
*/
|
|
||||||
function getContactSubjectMap(): array {
|
|
||||||
return [
|
|
||||||
'allgemeine-anfrage' => 'Allgemeine Anfrage',
|
|
||||||
'vpc-anfrage' => 'Virtual Private Container Anfrage',
|
|
||||||
'vps-anfrage' => 'Virtual Private Server Anfrage',
|
|
||||||
'mail-gateway-anfrage' => 'Mail Gateway Anfrage',
|
|
||||||
'webhosting-anfrage' => 'Webhosting Anfrage',
|
|
||||||
'it-beratung' => 'IT-Beratung',
|
|
||||||
'it-support' => 'IT-Support & Fehlerbehebung',
|
|
||||||
'netzwerk-wlan' => 'Netzwerk & WLAN-Einrichtung',
|
|
||||||
'it-sicherheit-backup' => 'IT-Sicherheit & Backup',
|
|
||||||
'webseiten-hosting-service' => 'Webseiten- & Hosting-Service',
|
|
||||||
'wartung-betreuung' => 'Wartung & Betreuung',
|
|
||||||
'support' => 'Technischer Support',
|
|
||||||
'beratung' => 'Persönliche Beratung',
|
|
||||||
'migration' => 'Migration/Umzug',
|
|
||||||
'sonstiges' => 'Sonstige Anfrage',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $subjectKey
|
|
||||||
*/
|
|
||||||
function isAllowedContactSubject(string $subjectKey): bool {
|
|
||||||
return array_key_exists($subjectKey, getContactSubjectMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Betreff aus ?product= oder ?package= für die Kontaktseite ableiten
|
|
||||||
*/
|
|
||||||
function getPreselectedContactSubject(): string {
|
|
||||||
$productMap = [
|
|
||||||
'vpc' => 'vpc-anfrage',
|
|
||||||
'vps' => 'vps-anfrage',
|
|
||||||
'mail-gateway' => 'mail-gateway-anfrage',
|
|
||||||
'webhosting' => 'webhosting-anfrage',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!empty($_GET['product'])) {
|
|
||||||
$product = strtolower(preg_replace('/[^a-z0-9-]/', '', (string) $_GET['product']));
|
|
||||||
if (isset($productMap[$product])) {
|
|
||||||
return $productMap[$product];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($_GET['package'])) {
|
|
||||||
$package = strtolower(preg_replace('/[^a-z0-9-]/', '', (string) $_GET['package']));
|
|
||||||
foreach ($productMap as $productId => $subjectKey) {
|
|
||||||
if (str_starts_with($package, $productId . '-')) {
|
|
||||||
return $subjectKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
@@ -2,134 +2,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
define('SMTP_HOST', 'smtp.ihre-domain.de');
|
require_once __DIR__ . '/../../backend/config/mail-config.php';
|
||||||
define('SMTP_PORT', 587);
|
|
||||||
define('SMTP_USERNAME', 'kontakt@ihre-domain.de');
|
|
||||||
define('SMTP_PASSWORD', 'ihr-smtp-passwort');
|
|
||||||
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', 10);
|
|
||||||
|
|
||||||
|
|
||||||
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',
|
|
||||||
'X-MSMail-Priority' => 'Normal',
|
|
||||||
'Importance' => 'Normal',
|
|
||||||
'X-Report-Abuse' => 'Please report abuse here: abuse@hexahost.de',
|
|
||||||
'List-Unsubscribe' => '<mailto:unsubscribe@hexahost.de>',
|
|
||||||
'Precedence' => 'bulk'
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
define('ALLOWED_EMAIL_DOMAINS', [
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
define('BLACKLISTED_EMAILS', [
|
|
||||||
|
|
||||||
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
if (!defined('SMTP_HOST') || !defined('SMTP_USERNAME') || !defined('SMTP_PASSWORD')) {
|
|
||||||
die('SMTP-Konfiguration ist unvollständig. Bitte überprüfen Sie die mail-config.php');
|
|
||||||
}
|
|
||||||
if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
die('Ungültige SMTP_FROM_EMAIL Adresse');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter_var(SMTP_TO_EMAIL, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
die('Ungültige SMTP_TO_EMAIL Adresse');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function logEmail($type, $data) {
|
|
||||||
if (!LOG_EMAILS) return;
|
|
||||||
|
|
||||||
$logFile = __DIR__ . '/../logs/email.log';
|
|
||||||
$logDir = dirname($logFile);
|
|
||||||
|
|
||||||
if (!is_dir($logDir)) {
|
|
||||||
mkdir($logDir, 0755, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$timestamp = date('Y-m-d H:i:s');
|
|
||||||
$logEntry = "[$timestamp] $type: " . json_encode($data) . "\n";
|
|
||||||
|
|
||||||
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function isValidEmail($email) {
|
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (in_array($email, BLACKLISTED_EMAILS)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!empty(ALLOWED_EMAIL_DOMAINS)) {
|
|
||||||
$domain = substr(strrchr($email, "@"), 1);
|
|
||||||
if (!in_array($domain, ALLOWED_EMAIL_DOMAINS)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getHexaHostConfig($key = null) {
|
|
||||||
$config = [
|
|
||||||
|
|
||||||
'smtp_host' => SMTP_HOST,
|
|
||||||
'smtp_port' => SMTP_PORT,
|
|
||||||
'smtp_username' => SMTP_USERNAME,
|
|
||||||
'smtp_password' => SMTP_PASSWORD,
|
|
||||||
'smtp_encryption' => 'tls',
|
|
||||||
'from_email' => SMTP_FROM_EMAIL,
|
|
||||||
'from_name' => 'HexaHost.de Kontaktformular',
|
|
||||||
'to_email' => SMTP_TO_EMAIL,
|
|
||||||
'to_name' => 'HexaHost Support',
|
|
||||||
|
|
||||||
|
|
||||||
'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_mode' => DEBUG_MODE,
|
|
||||||
'log_errors' => LOG_EMAILS,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($key === null) {
|
|
||||||
return $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $config[$key] ?? null;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|||||||
@@ -1,522 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$PRODUCTS['vpc'] = [
|
|
||||||
'name' => 'Virtual Private Container',
|
|
||||||
'short_name' => 'VPC',
|
|
||||||
'description' => 'Effiziente LXC-Container auf Proxmox-Basis',
|
|
||||||
'min_price' => '4,99',
|
|
||||||
'hero_highlight' => 'auf Proxmox LXC',
|
|
||||||
'hero_description' => 'Erleben Sie die Effizienz von Linux-Containern mit der Zuverlässigkeit von Proxmox. Unsere VPC-Lösungen bieten optimale Performance bei minimalem Ressourcenverbrauch.',
|
|
||||||
'packages_title' => 'VPC Pakete',
|
|
||||||
'packages_description' => 'Wählen Sie das perfekte Container-Paket für Ihre Anforderungen',
|
|
||||||
'cta_title' => 'Bereit für Ihren VPC?',
|
|
||||||
'cta_description' => 'Starten Sie noch heute mit einem Virtual Private Container',
|
|
||||||
'page_title' => 'Virtual Private Container - Effiziente LXC Container | HexaHost.de',
|
|
||||||
'page_description' => 'Virtual Private Container auf Proxmox LXC-Basis. Effiziente und preiswerte Container-Lösungen ab 4,99€/Monat bei HexaHost.de',
|
|
||||||
'packages' => [
|
|
||||||
'starter' => [
|
|
||||||
'name' => 'VPC Starter',
|
|
||||||
'price' => '4,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
|
||||||
['label' => 'RAM', 'value' => '1 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '20 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '1 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '1'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox LXC Container',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Backup inklusive',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'business' => [
|
|
||||||
'name' => 'VPC Business',
|
|
||||||
'price' => '9,99',
|
|
||||||
'featured' => true,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
|
||||||
['label' => 'RAM', 'value' => '4 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '80 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '3 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '1'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox LXC Container',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Tägliches Backup',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
'Snapshot-Funktion',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'professional' => [
|
|
||||||
'name' => 'VPC Professional',
|
|
||||||
'price' => '19,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
|
||||||
['label' => 'RAM', 'value' => '8 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '160 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '5 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '2'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox LXC Container',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Stündliches Backup',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
'Snapshot-Funktion',
|
|
||||||
'Priority Support',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'enterprise' => [
|
|
||||||
'name' => 'VPC Enterprise',
|
|
||||||
'price' => '39,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
|
||||||
['label' => 'RAM', 'value' => '16 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '320 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '10 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '3'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox LXC Container',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Stündliches Backup',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
'Snapshot-Funktion',
|
|
||||||
'Priority Support',
|
|
||||||
'Individuelle Konfiguration',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$PRODUCTS['vps'] = [
|
|
||||||
'name' => 'Virtual Private Server',
|
|
||||||
'short_name' => 'VPS',
|
|
||||||
'description' => 'Vollwertige KVM-Virtualisierung mit Root-Zugriff',
|
|
||||||
'min_price' => '9,99',
|
|
||||||
'hero_highlight' => 'auf Proxmox KVM',
|
|
||||||
'hero_description' => 'Maximale Flexibilität und Kontrolle mit vollwertiger KVM-Virtualisierung. Installieren Sie jedes Betriebssystem und genießen Sie vollständigen Root-Zugriff.',
|
|
||||||
'packages_title' => 'VPS Pakete',
|
|
||||||
'packages_description' => 'Wählen Sie das perfekte VPS-Paket für Ihre Anforderungen',
|
|
||||||
'cta_title' => 'Bereit für Ihren VPS?',
|
|
||||||
'cta_description' => 'Starten Sie noch heute mit einem Virtual Private Server',
|
|
||||||
'page_title' => 'Virtual Private Server - KVM Virtualisierung | HexaHost.de',
|
|
||||||
'page_description' => 'Virtual Private Server auf Proxmox KVM-Basis. Vollwertige Virtualisierung mit Root-Zugriff ab 9,99€/Monat bei HexaHost.de',
|
|
||||||
'packages' => [
|
|
||||||
'starter' => [
|
|
||||||
'name' => 'VPS Starter',
|
|
||||||
'price' => '9,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '1 vCore'],
|
|
||||||
['label' => 'RAM', 'value' => '2 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '40 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '2 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '1'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox KVM Virtualisierung',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Backup inklusive',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'business' => [
|
|
||||||
'name' => 'VPS Business',
|
|
||||||
'price' => '19,99',
|
|
||||||
'featured' => true,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '2 vCores'],
|
|
||||||
['label' => 'RAM', 'value' => '4 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '80 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '4 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '1'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox KVM Virtualisierung',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Tägliches Backup',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
'Snapshot-Funktion',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'professional' => [
|
|
||||||
'name' => 'VPS Professional',
|
|
||||||
'price' => '39,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '4 vCores'],
|
|
||||||
['label' => 'RAM', 'value' => '8 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '160 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '8 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '2'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox KVM Virtualisierung',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Stündliches Backup',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
'Snapshot-Funktion',
|
|
||||||
'Priority Support',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'enterprise' => [
|
|
||||||
'name' => 'VPS Enterprise',
|
|
||||||
'price' => '79,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'CPU Kerne', 'value' => '8 vCores'],
|
|
||||||
['label' => 'RAM', 'value' => '16 GB'],
|
|
||||||
['label' => 'SSD Speicher', 'value' => '320 GB'],
|
|
||||||
['label' => 'Traffic', 'value' => '15 TB'],
|
|
||||||
['label' => 'IPv4 Adressen', 'value' => '3'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Proxmox KVM Virtualisierung',
|
|
||||||
'Root-Zugriff',
|
|
||||||
'SSH-Zugang',
|
|
||||||
'Stündliches Backup',
|
|
||||||
'24/7 Monitoring',
|
|
||||||
'Snapshot-Funktion',
|
|
||||||
'Priority Support',
|
|
||||||
'Individuelle Konfiguration',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$PRODUCTS['mail-gateway'] = [
|
|
||||||
'name' => 'Mail Gateway',
|
|
||||||
'short_name' => 'Mail',
|
|
||||||
'description' => 'Professioneller E-Mail-Schutz für Unternehmen',
|
|
||||||
'min_price' => '4,99',
|
|
||||||
'hero_highlight' => 'für Unternehmen',
|
|
||||||
'hero_description' => 'Professionelle E-Mail-Infrastruktur mit maximalem Schutz vor Spam und Malware. Sichern Sie Ihre geschäftliche Kommunikation mit unseren Mail Gateway Lösungen.',
|
|
||||||
'packages_title' => 'Mail Gateway Pakete',
|
|
||||||
'packages_description' => 'Wählen Sie das passende Mail Gateway Paket für Ihr Unternehmen',
|
|
||||||
'cta_title' => 'Bereit für professionelle E-Mail-Kommunikation?',
|
|
||||||
'cta_description' => 'Starten Sie noch heute mit unserem Mail Gateway',
|
|
||||||
'page_title' => 'Mail Gateway - Professionelle E-Mail-Lösungen | HexaHost.de',
|
|
||||||
'page_description' => 'Professionelle Mail Gateway Lösungen für Unternehmen. Spam-Schutz, E-Mail-Archivierung und sichere E-Mail-Kommunikation bei HexaHost.de',
|
|
||||||
'packages' => [
|
|
||||||
'starter' => [
|
|
||||||
'name' => 'Mail Starter',
|
|
||||||
'price' => '4,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'Postfächer', 'value' => '5'],
|
|
||||||
['label' => 'Speicher/Postfach', 'value' => '5 GB'],
|
|
||||||
['label' => 'Domains', 'value' => '1'],
|
|
||||||
['label' => 'E-Mails/Tag', 'value' => '500'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Spam-Filter',
|
|
||||||
'Virus-Schutz',
|
|
||||||
'Webmail',
|
|
||||||
'IMAP/POP3',
|
|
||||||
'SSL/TLS Verschlüsselung',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'business' => [
|
|
||||||
'name' => 'Mail Business',
|
|
||||||
'price' => '14,99',
|
|
||||||
'featured' => true,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'Postfächer', 'value' => '25'],
|
|
||||||
['label' => 'Speicher/Postfach', 'value' => '10 GB'],
|
|
||||||
['label' => 'Domains', 'value' => '3'],
|
|
||||||
['label' => 'E-Mails/Tag', 'value' => '2.000'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Spam-Filter (erweitert)',
|
|
||||||
'Virus-Schutz',
|
|
||||||
'Webmail',
|
|
||||||
'IMAP/POP3',
|
|
||||||
'SSL/TLS Verschlüsselung',
|
|
||||||
'E-Mail Archivierung (30 Tage)',
|
|
||||||
'Kalender & Kontakte',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'professional' => [
|
|
||||||
'name' => 'Mail Professional',
|
|
||||||
'price' => '29,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'Postfächer', 'value' => '100'],
|
|
||||||
['label' => 'Speicher/Postfach', 'value' => '25 GB'],
|
|
||||||
['label' => 'Domains', 'value' => '10'],
|
|
||||||
['label' => 'E-Mails/Tag', 'value' => '10.000'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Spam-Filter (KI-gestützt)',
|
|
||||||
'Virus-Schutz',
|
|
||||||
'Webmail',
|
|
||||||
'IMAP/POP3',
|
|
||||||
'SSL/TLS Verschlüsselung',
|
|
||||||
'E-Mail Archivierung (1 Jahr)',
|
|
||||||
'Kalender & Kontakte',
|
|
||||||
'ActiveSync für Mobile',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'enterprise' => [
|
|
||||||
'name' => 'Mail Enterprise',
|
|
||||||
'price' => '59,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'Postfächer', 'value' => 'Unbegrenzt'],
|
|
||||||
['label' => 'Speicher/Postfach', 'value' => '50 GB'],
|
|
||||||
['label' => 'Domains', 'value' => 'Unbegrenzt'],
|
|
||||||
['label' => 'E-Mails/Tag', 'value' => 'Unbegrenzt'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Spam-Filter (KI-gestützt)',
|
|
||||||
'Virus-Schutz',
|
|
||||||
'Webmail',
|
|
||||||
'IMAP/POP3',
|
|
||||||
'SSL/TLS Verschlüsselung',
|
|
||||||
'E-Mail Archivierung (10 Jahre)',
|
|
||||||
'Kalender & Kontakte',
|
|
||||||
'ActiveSync für Mobile',
|
|
||||||
'Dedizierte IP',
|
|
||||||
'Priority Support',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$PRODUCTS['webhosting'] = [
|
|
||||||
'name' => 'Webhosting',
|
|
||||||
'short_name' => 'Webhosting',
|
|
||||||
'description' => 'Klassisches Hosting mit PHP, MySQL und SSL',
|
|
||||||
'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. 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 Plesk, PHP und SSL-Zertifikaten. Klassisches Hosting für Websites ab 4,99€/Monat bei HexaHost.de',
|
|
||||||
'packages' => [
|
|
||||||
'starter' => [
|
|
||||||
'name' => 'Webhosting Starter',
|
|
||||||
'price' => '4,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['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' => [
|
|
||||||
'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' => '7,99',
|
|
||||||
'featured' => true,
|
|
||||||
'specs' => [
|
|
||||||
['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' => '100 GB'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Perfekt für mittlere Websites und Blogs',
|
|
||||||
'Plesk',
|
|
||||||
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
|
|
||||||
'SSL-Zertifikat (Let\'s Encrypt)',
|
|
||||||
'E-Mail-Postfächer à 100MB',
|
|
||||||
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'professional' => [
|
|
||||||
'name' => 'Webhosting Professional',
|
|
||||||
'price' => '9,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'Webspace', 'value' => '50 GB'],
|
|
||||||
['label' => 'Domains inkl.', 'value' => '3'],
|
|
||||||
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
|
|
||||||
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
|
|
||||||
['label' => 'E-Mail-Postfächer', 'value' => '100'],
|
|
||||||
['label' => 'Datenbanken', 'value' => '20 MySQL'],
|
|
||||||
['label' => 'Traffic', 'value' => '100 GB'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Perfekt für größere Websites und Blogs',
|
|
||||||
'Plesk',
|
|
||||||
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
|
|
||||||
'SSL-Zertifikat (Let\'s Encrypt)',
|
|
||||||
'E-Mail-Postfächer à 100MB',
|
|
||||||
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'enterprise' => [
|
|
||||||
'name' => 'Webhosting Enterprise',
|
|
||||||
'price' => '29,99',
|
|
||||||
'featured' => false,
|
|
||||||
'specs' => [
|
|
||||||
['label' => 'Webspace', 'value' => '200 GB'],
|
|
||||||
['label' => 'Domains inkl.', 'value' => '5'],
|
|
||||||
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
|
|
||||||
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
|
|
||||||
['label' => 'E-Mail-Postfächer', 'value' => 'Unbegrenzt'],
|
|
||||||
['label' => 'Datenbanken', 'value' => '50 MySQL'],
|
|
||||||
['label' => 'Traffic', 'value' => '1 TB'],
|
|
||||||
],
|
|
||||||
'features' => [
|
|
||||||
'Perfekt für Enterprise-Websites und Blogs',
|
|
||||||
'Plesk',
|
|
||||||
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
|
|
||||||
'SSL-Zertifikat (Let\'s Encrypt)',
|
|
||||||
'E-Mail-Postfächer à 100MB',
|
|
||||||
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
|
|
||||||
'Priority Support',
|
|
||||||
'Individuelle Konfiguration',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getAllProducts() {
|
|
||||||
global $PRODUCTS;
|
|
||||||
return $PRODUCTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getProduct($productId) {
|
|
||||||
global $PRODUCTS;
|
|
||||||
return $PRODUCTS[$productId] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getProductPackages($productId) {
|
|
||||||
global $PRODUCTS;
|
|
||||||
return $PRODUCTS[$productId]['packages'] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getPackage($productId, $packageId) {
|
|
||||||
global $PRODUCTS;
|
|
||||||
return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getPackagePrice($productId, $packageId) {
|
|
||||||
$package = getPackage($productId, $packageId);
|
|
||||||
return $package['price'] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getMinPrice($productId) {
|
|
||||||
global $PRODUCTS;
|
|
||||||
return $PRODUCTS[$productId]['min_price'] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function formatPrice($price, $withCurrency = true) {
|
|
||||||
return $withCurrency ? $price . '€' : $price;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function renderPackageCard($productId, $packageId, $package) {
|
|
||||||
$featuredClass = $package['featured'] ? ' featured' : '';
|
|
||||||
$featuredBadge = $package['featured'] ? '<div class="featured-badge">Beliebt</div>' : '';
|
|
||||||
|
|
||||||
$specsHtml = '';
|
|
||||||
foreach ($package['specs'] as $spec) {
|
|
||||||
$specsHtml .= sprintf(
|
|
||||||
'<div class="spec-item"><span class="spec-label">%s:</span><span class="spec-value">%s</span></div>',
|
|
||||||
htmlspecialchars($spec['label']),
|
|
||||||
htmlspecialchars($spec['value'])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$featuresHtml = '';
|
|
||||||
foreach ($package['features'] as $feature) {
|
|
||||||
$featuresHtml .= sprintf('<div class="feature">✓ %s</div>', htmlspecialchars($feature));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf('
|
|
||||||
<div class="package-card glass-card%s">
|
|
||||||
%s
|
|
||||||
<div class="package-header">
|
|
||||||
<h3 class="package-name">%s</h3>
|
|
||||||
<div class="package-price">
|
|
||||||
<span class="price">%s€</span>
|
|
||||||
<span class="period">/Monat</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="package-specs">
|
|
||||||
%s
|
|
||||||
</div>
|
|
||||||
<div class="package-features">
|
|
||||||
%s
|
|
||||||
</div>
|
|
||||||
<a href="contact.php?package=%s-%s" class="btn btn-primary">Jetzt bestellen</a>
|
|
||||||
</div>',
|
|
||||||
$featuredClass,
|
|
||||||
$featuredBadge,
|
|
||||||
htmlspecialchars($package['name']),
|
|
||||||
$package['price'],
|
|
||||||
$specsHtml,
|
|
||||||
$featuresHtml,
|
|
||||||
$productId,
|
|
||||||
$packageId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function renderAllPackages($productId) {
|
|
||||||
$packages = getProductPackages($productId);
|
|
||||||
$html = '';
|
|
||||||
foreach ($packages as $packageId => $package) {
|
|
||||||
$html .= renderPackageCard($productId, $packageId, $package);
|
|
||||||
}
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* HexaHost.de Contact Form Handler
|
|
||||||
* E-Mail-Verarbeitung mit nativer PHP-mail()-Funktion und Spam-Schutz
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
require_once __DIR__ . '/../backend/config/mail-config.php';
|
require_once __DIR__ . '/../backend/config/mail-config.php';
|
||||||
@@ -10,7 +10,7 @@ require_once __DIR__ . '/../backend/config/contact-config.php';
|
|||||||
|
|
||||||
$config = getHexaHostConfig();
|
$config = getHexaHostConfig();
|
||||||
|
|
||||||
// CORS Headers für AJAX-Requests (nur eigene Domain erlauben)
|
|
||||||
$allowed_origins = [
|
$allowed_origins = [
|
||||||
'https://hexahost.de',
|
'https://hexahost.de',
|
||||||
'https://www.hexahost.de',
|
'https://www.hexahost.de',
|
||||||
@@ -113,7 +113,7 @@ function sendEmail($data) {
|
|||||||
'X-Report-Abuse: Please report abuse here: abuse@hexahost.de',
|
'X-Report-Abuse: Please report abuse here: abuse@hexahost.de',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Native PHP Mailversand ohne externe Libraries
|
|
||||||
return mail($config['to_email'], $subject, generateEmailHTML($data), implode("\r\n", $headers));
|
return mail($config['to_email'], $subject, generateEmailHTML($data), implode("\r\n", $headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ $preselected_subject = getPreselectedContactSubject();
|
|||||||
$page_title = 'Kontakt - HexaHost.de | Hosting aus Niederbayern';
|
$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.';
|
$page_description = 'Kontaktieren Sie HexaHost.de - Ihr Hosting-Partner aus Niederbayern. Persönlicher Support und kompetente Beratung.';
|
||||||
$current_page = 'contact';
|
$current_page = 'contact';
|
||||||
$additional_scripts = ['assets/js/contact.js'];
|
$additional_scripts = ['assets/js/contact.c2399194863d.js'];
|
||||||
|
|
||||||
|
|
||||||
includeHeader($page_title, $page_description, $current_page, $additional_scripts);
|
includeHeader($page_title, $page_description, $current_page, $additional_scripts);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Contact Hero -->
|
||||||
<section class="contact-hero">
|
<section class="contact-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="contact-hero-content">
|
<div class="contact-hero-content">
|
||||||
@@ -36,7 +36,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Contact Options -->
|
||||||
<section class="contact-options">
|
<section class="contact-options">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="contact-grid">
|
<div class="contact-grid">
|
||||||
@@ -88,7 +88,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Contact Form -->
|
||||||
<section class="contact-form-section">
|
<section class="contact-form-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
@@ -100,7 +100,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
|
|||||||
</div>
|
</div>
|
||||||
<form class="contact-form glass-card" id="contactForm" action="contact-handler.php" method="POST">
|
<form class="contact-form glass-card" id="contactForm" action="contact-handler.php" method="POST">
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
|
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
|
||||||
|
<!-- Honeypot-Feld für Bot-Schutz (versteckt via CSS) -->
|
||||||
<div style="position: absolute; left: -9999px;" aria-hidden="true">
|
<div style="position: absolute; left: -9999px;" aria-hidden="true">
|
||||||
<input type="text" name="website" tabindex="-1" autocomplete="off">
|
<input type="text" name="website" tabindex="-1" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
@@ -156,7 +156,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- FAQ Section -->
|
||||||
<section class="faq-section">
|
<section class="faq-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -224,7 +224,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Response Time -->
|
||||||
<section class="response-time">
|
<section class="response-time">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="response-content glass-card">
|
<div class="response-content glass-card">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Datenschutz Hero -->
|
||||||
<section class="legal-hero">
|
<section class="legal-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="legal-hero-content">
|
<div class="legal-hero-content">
|
||||||
@@ -29,12 +29,12 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Datenschutz Content -->
|
||||||
<section class="legal-content">
|
<section class="legal-content">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="legal-container">
|
<div class="legal-container">
|
||||||
|
|
||||||
|
<!-- Verantwortlicher -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>1. Verantwortlicher</h2>
|
<h2>1. Verantwortlicher</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -53,7 +53,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Allgemeine Hinweise -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>2. Allgemeine Hinweise zur Datenverarbeitung</h2>
|
<h2>2. Allgemeine Hinweise zur Datenverarbeitung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -75,7 +75,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Rechtsgrundlagen -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>3. Rechtsgrundlagen der Verarbeitung</h2>
|
<h2>3. Rechtsgrundlagen der Verarbeitung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -89,7 +89,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Server-Logfiles -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>4. Server-Logfiles</h2>
|
<h2>4. Server-Logfiles</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -119,7 +119,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Cookies -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>5. Cookies</h2>
|
<h2>5. Cookies</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -146,7 +146,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Kontaktformular -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>6. Kontaktformular</h2>
|
<h2>6. Kontaktformular</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -168,7 +168,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hosting und Vertragsabwicklung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>7. Hosting-Dienstleistungen und Vertragsabwicklung</h2>
|
<h2>7. Hosting-Dienstleistungen und Vertragsabwicklung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -193,7 +193,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SSL-Verschlüsselung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>8. SSL- bzw. TLS-Verschlüsselung</h2>
|
<h2>8. SSL- bzw. TLS-Verschlüsselung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -212,7 +212,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Speicherdauer -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>9. Speicherdauer</h2>
|
<h2>9. Speicherdauer</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -229,7 +229,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Betroffenenrechte -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>10. Ihre Rechte als betroffene Person</h2>
|
<h2>10. Ihre Rechte als betroffene Person</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -281,7 +281,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Beschwerderecht -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>11. Beschwerderecht bei einer Aufsichtsbehörde</h2>
|
<h2>11. Beschwerderecht bei einer Aufsichtsbehörde</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -302,7 +302,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Datensicherheit -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>12. Datensicherheit</h2>
|
<h2>12. Datensicherheit</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -320,7 +320,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Änderungen -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>13. Aktualität und Änderung dieser Datenschutzerklärung</h2>
|
<h2>13. Aktualität und Änderung dieser Datenschutzerklärung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Impressum Hero -->
|
||||||
<section class="legal-hero">
|
<section class="legal-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="legal-hero-content">
|
<div class="legal-hero-content">
|
||||||
@@ -29,12 +29,12 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Impressum Content -->
|
||||||
<section class="legal-content">
|
<section class="legal-content">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="legal-container">
|
<div class="legal-container">
|
||||||
|
|
||||||
|
<!-- Anbieterkennung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Angaben gemäß § 5 DDG</h2>
|
<h2>Angaben gemäß § 5 DDG</h2>
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Kontakt -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Kontakt</h2>
|
<h2>Kontakt</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -61,7 +61,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Umsatzsteuer-ID (falls vorhanden) -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Umsatzsteuer-ID</h2>
|
<h2>Umsatzsteuer-ID</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -72,7 +72,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Verantwortlich für den Inhalt -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Redaktionell verantwortlich</h2>
|
<h2>Redaktionell verantwortlich</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -85,14 +85,14 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- EU-Streitschlichtung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>EU-Streitschlichtung</h2>
|
<h2>EU-Streitschlichtung</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
<p>
|
<p>
|
||||||
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
|
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">
|
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener noreferrer">
|
||||||
https://ec.europa.eu/consumers/odr/
|
https:
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -101,7 +101,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Verbraucherstreitbeilegung -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Verbraucherstreitbeilegung / Universalschlichtungsstelle</h2>
|
<h2>Verbraucherstreitbeilegung / Universalschlichtungsstelle</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -112,7 +112,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Haftungsausschluss -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Haftung für Inhalte</h2>
|
<h2>Haftung für Inhalte</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -133,7 +133,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Haftung für Links -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Haftung für Links</h2>
|
<h2>Haftung für Links</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
@@ -153,7 +153,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Urheberrecht -->
|
||||||
<div class="legal-section glass-card">
|
<div class="legal-section glass-card">
|
||||||
<h2>Urheberrecht</h2>
|
<h2>Urheberrecht</h2>
|
||||||
<div class="legal-block">
|
<div class="legal-block">
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Gemeinsame Hilfsfunktionen für öffentliche DNS/API-Endpunkte
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client-IP für Rate-Limiting (Cloudflare-sicher, kein blindes X-Forwarded-For)
|
|
||||||
*/
|
|
||||||
function getApiClientIp(): string {
|
|
||||||
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
|
|
||||||
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
|
|
||||||
return $_SERVER['HTTP_CF_CONNECTING_IP'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
|
||||||
$isTrustedProxy = filter_var(
|
|
||||||
$remoteAddr,
|
|
||||||
FILTER_VALIDATE_IP,
|
|
||||||
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
|
|
||||||
) === false;
|
|
||||||
|
|
||||||
if ($isTrustedProxy) {
|
|
||||||
foreach (['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
|
|
||||||
if (empty($_SERVER[$header])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$ip = trim(explode(',', $_SERVER[$header])[0]);
|
|
||||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
|
||||||
return $ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $remoteAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Einfaches Rate-Limiting pro Endpunkt und IP
|
|
||||||
*/
|
|
||||||
function checkApiRateLimit(string $endpoint, int $maxPerHour = 120): bool {
|
|
||||||
$ip = getApiClientIp();
|
|
||||||
$cacheFile = sys_get_temp_dir() . '/hexahost_api_' . md5($endpoint . '_' . $ip) . '.txt';
|
|
||||||
$currentTime = time();
|
|
||||||
$data = ['requests' => []];
|
|
||||||
|
|
||||||
$handle = @fopen($cacheFile, 'c+');
|
|
||||||
if ($handle === false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!flock($handle, LOCK_EX)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$contents = stream_get_contents($handle);
|
|
||||||
if ($contents !== false && $contents !== '') {
|
|
||||||
$decoded = json_decode($contents, true);
|
|
||||||
if (is_array($decoded) && isset($decoded['requests'])) {
|
|
||||||
$data = $decoded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['requests'] = array_values(array_filter(
|
|
||||||
$data['requests'],
|
|
||||||
static fn($timestamp) => ($currentTime - (int) $timestamp) < 3600
|
|
||||||
));
|
|
||||||
|
|
||||||
if (count($data['requests']) >= $maxPerHour) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['requests'][] = $currentTime;
|
|
||||||
ftruncate($handle, 0);
|
|
||||||
rewind($handle);
|
|
||||||
fwrite($handle, json_encode($data));
|
|
||||||
} finally {
|
|
||||||
flock($handle, LOCK_UN);
|
|
||||||
fclose($handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain aus GET-Parameter normalisieren und validieren
|
|
||||||
*/
|
|
||||||
function getValidatedDomainParam(string $param = 'domain'): ?string {
|
|
||||||
if (empty($_GET[$param])) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$domain = trim((string) $_GET[$param]);
|
|
||||||
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
|
|
||||||
$domain = explode('/', $domain)[0];
|
|
||||||
$domain = explode(':', $domain)[0];
|
|
||||||
|
|
||||||
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rate-Limit-JSON-Antwort senden und beenden
|
|
||||||
*/
|
|
||||||
function rejectApiRateLimit(): void {
|
|
||||||
http_response_code(429);
|
|
||||||
echo json_encode(['error' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
<footer class="footer">
|
|
||||||
<div class="container">
|
|
||||||
<div class="footer-content">
|
|
||||||
<div class="footer-section">
|
|
||||||
<h4>HexaHost.de</h4>
|
|
||||||
<p>Zuverlässiges Hosting aus Niederbayern</p>
|
|
||||||
<div class="footer-location">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
|
||||||
<circle cx="12" cy="10" r="3"/>
|
|
||||||
</svg>
|
|
||||||
<span>Niederbayern, Deutschland</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="footer-section">
|
|
||||||
<h4>Unternehmen</h4>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/about">Über mich</a></li>
|
|
||||||
<li><a href="/contact">Kontakt</a></li>
|
|
||||||
<li><a href="/impressum">Impressum</a></li>
|
|
||||||
<li><a href="/datenschutz">Datenschutz</a></li>
|
|
||||||
<li><a href="/agb">AGB</a></li>
|
|
||||||
<li><a href="/widerruf">Widerrufsbelehrung</a></li>
|
|
||||||
<li><a href="#" id="openCookieSettings" onclick="CookieConsent.resetConsent(); return false;">Cookie-Einstellungen</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="footer-section">
|
|
||||||
<h4>Support</h4>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://shop.hexahost.de/clientarea.php">Kunden-Center</a></li>
|
|
||||||
<li><a href="https://shop.hexahost.de/serverstatus.php">Status</a></li>
|
|
||||||
<li><a href="https://shop.hexahost.de/supporttickets.php">Support-Ticket</a></li>
|
|
||||||
<li><a href="#">FAQ</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="footer-bottom">
|
|
||||||
<p>© <?php echo date('Y'); ?> HexaHost.de - Alle Rechte vorbehalten</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="cookieConsent" class="cookie-consent" role="dialog" aria-labelledby="cookieConsentTitle" aria-describedby="cookieConsentDesc">
|
|
||||||
<div class="cookie-consent-container">
|
|
||||||
<div class="cookie-consent-content">
|
|
||||||
<div class="cookie-consent-icon">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="10"/>
|
|
||||||
<circle cx="8" cy="9" r="1" fill="currentColor"/>
|
|
||||||
<circle cx="15" cy="8" r="1" fill="currentColor"/>
|
|
||||||
<circle cx="10" cy="14" r="1" fill="currentColor"/>
|
|
||||||
<circle cx="16" cy="13" r="1" fill="currentColor"/>
|
|
||||||
<circle cx="13" cy="17" r="1" fill="currentColor"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="cookie-consent-text">
|
|
||||||
<h3 id="cookieConsentTitle">Cookie-Einstellungen</h3>
|
|
||||||
<p id="cookieConsentDesc">
|
|
||||||
Wir verwenden Cookies, um Ihnen die bestmögliche Erfahrung auf unserer Website zu bieten.
|
|
||||||
Technisch notwendige Cookies sind für die Funktionalität erforderlich.
|
|
||||||
<a href="/datenschutz">Mehr erfahren</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="cookie-consent-actions">
|
|
||||||
<button type="button" id="cookieAcceptAll" class="btn btn-primary">Alle akzeptieren</button>
|
|
||||||
<button type="button" id="cookieAcceptEssential" class="btn btn-secondary">Nur notwendige</button>
|
|
||||||
<button type="button" id="cookieSettings" class="btn btn-text">Einstellungen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="cookieSettingsPanel" class="cookie-settings-panel" style="display: none;">
|
|
||||||
<div class="cookie-settings-content">
|
|
||||||
<h4>Cookie-Einstellungen</h4>
|
|
||||||
<div class="cookie-option">
|
|
||||||
<div class="cookie-option-info">
|
|
||||||
<strong>Notwendige Cookies</strong>
|
|
||||||
<p>Diese Cookies sind für die Grundfunktionen der Website erforderlich.</p>
|
|
||||||
</div>
|
|
||||||
<label class="cookie-toggle disabled">
|
|
||||||
<input type="checkbox" checked disabled>
|
|
||||||
<span class="cookie-toggle-slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="cookie-option">
|
|
||||||
<div class="cookie-option-info">
|
|
||||||
<strong>Analyse-Cookies</strong>
|
|
||||||
<p>Helfen uns zu verstehen, wie Besucher unsere Website nutzen.</p>
|
|
||||||
</div>
|
|
||||||
<label class="cookie-toggle">
|
|
||||||
<input type="checkbox" id="cookieAnalytics">
|
|
||||||
<span class="cookie-toggle-slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="cookie-option">
|
|
||||||
<div class="cookie-option-info">
|
|
||||||
<strong>Marketing-Cookies</strong>
|
|
||||||
<p>Werden verwendet, um relevante Werbung anzuzeigen.</p>
|
|
||||||
</div>
|
|
||||||
<label class="cookie-toggle">
|
|
||||||
<input type="checkbox" id="cookieMarketing">
|
|
||||||
<span class="cookie-toggle-slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="cookie-settings-actions">
|
|
||||||
<button type="button" id="cookieSaveSettings" class="btn btn-primary">Einstellungen speichern</button>
|
|
||||||
<button type="button" id="cookieCloseSettings" class="btn btn-secondary">Abbrechen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
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',
|
|
||||||
ad_user_data: 'denied',
|
|
||||||
ad_personalization: 'denied'
|
|
||||||
});
|
|
||||||
|
|
||||||
gtag('js', new Date());
|
|
||||||
gtag('config', 'G-EF0E9VPMTD', {
|
|
||||||
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;
|
|
||||||
var analyticsGranted = !!(consent && consent.analytics);
|
|
||||||
var marketingGranted = !!(consent && consent.marketing);
|
|
||||||
|
|
||||||
gtag('consent', 'update', {
|
|
||||||
analytics_storage: analyticsGranted ? 'granted' : 'denied',
|
|
||||||
ad_storage: marketingGranted ? 'granted' : 'denied',
|
|
||||||
ad_user_data: marketingGranted ? 'granted' : 'denied',
|
|
||||||
ad_personalization: marketingGranted ? 'granted' : 'denied'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</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>
|
|
||||||
<?php if (isset($additional_scripts)): ?>
|
|
||||||
<?php foreach ($additional_scripts as $script): ?>
|
|
||||||
<script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
|
||||||
|
|
||||||
ini_set('session.cookie_httponly', 1);
|
|
||||||
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0);
|
|
||||||
ini_set('session.cookie_samesite', 'Strict');
|
|
||||||
ini_set('session.use_strict_mode', 1);
|
|
||||||
ini_set('session.use_only_cookies', 1);
|
|
||||||
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
|
|
||||||
if (!isset($_SESSION['initiated'])) {
|
|
||||||
session_regenerate_id(true);
|
|
||||||
$_SESSION['initiated'] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
|
|
||||||
ini_set('display_errors', 0);
|
|
||||||
ini_set('display_startup_errors', 0);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
ini_set('log_errors', 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
|
|
||||||
global $page_title, $page_description, $current_page, $additional_scripts;
|
|
||||||
|
|
||||||
|
|
||||||
$page_title = !empty($title)
|
|
||||||
? $title
|
|
||||||
: 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
|
|
||||||
|
|
||||||
$page_description = !empty($description)
|
|
||||||
? $description
|
|
||||||
: 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.';
|
|
||||||
|
|
||||||
$current_page = $page;
|
|
||||||
$additional_scripts = $scripts;
|
|
||||||
|
|
||||||
include __DIR__ . '/header.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function includeFooter() {
|
|
||||||
include __DIR__ . '/footer.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function generateBreadcrumbs($breadcrumbs) {
|
|
||||||
echo '<div class="breadcrumb">';
|
|
||||||
$last_index = count($breadcrumbs) - 1;
|
|
||||||
|
|
||||||
foreach ($breadcrumbs as $index => $item) {
|
|
||||||
if ($index === $last_index) {
|
|
||||||
|
|
||||||
echo '<span>' . htmlspecialchars($item['title']) . '</span>';
|
|
||||||
} else {
|
|
||||||
|
|
||||||
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
|
||||||
echo '<span>/</span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="dns-prefetch" href="//fonts.googleapis.com">
|
|
||||||
<link rel="dns-prefetch" href="//fonts.gstatic.com">
|
|
||||||
<link rel="dns-prefetch" href="//cdn.hexahost.de">
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link rel="preconnect" href="https://cdn.hexahost.de" crossorigin>
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="preload" href="/assets/css/style.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>
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.'; ?>">
|
|
||||||
<meta name="robots" content="index, follow">
|
|
||||||
<meta name="author" content="HexaHost.de">
|
|
||||||
<meta name="theme-color" content="#0d0821">
|
|
||||||
|
|
||||||
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:site_name" content="HexaHost.de">
|
|
||||||
<meta property="og:title" content="<?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de'; ?>">
|
|
||||||
<meta property="og:description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'Zuverlässiges Hosting aus Niederbayern'; ?>">
|
|
||||||
<meta property="og:locale" content="de_DE">
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/assets/css/style.css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/custom.css">
|
|
||||||
|
|
||||||
|
|
||||||
<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">
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
||||||
<link rel="apple-touch-icon" href="/favicon.svg">
|
|
||||||
|
|
||||||
|
|
||||||
<?php if (isset($canonical_url)): ?>
|
|
||||||
<link rel="canonical" href="<?php echo htmlspecialchars($canonical_url); ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="header">
|
|
||||||
<nav class="nav">
|
|
||||||
<div class="nav-container">
|
|
||||||
<div class="nav-logo">
|
|
||||||
<a href="/">
|
|
||||||
<img src="https://cdn.hexahost.de/assets/img/logo/8iFs123BynHQWHI5.png" alt="HexaHost.de Logo" class="logo-image">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li>
|
|
||||||
<li><a href="/about" class="nav-link <?php echo ($current_page === 'about') ? 'active' : ''; ?>">Über mich</a></li>
|
|
||||||
<li><a href="/contact" class="nav-link <?php echo ($current_page === 'contact') ? 'active' : ''; ?>">Kontakt</a></li>
|
|
||||||
</ul>
|
|
||||||
<div class="nav-toggle">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Hero Section -->
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="hero-container">
|
<div class="hero-container">
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
@@ -44,7 +44,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Products Section -->
|
||||||
<section id="products" class="products">
|
<section id="products" class="products">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -54,7 +54,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="products-grid">
|
<div class="products-grid">
|
||||||
<div class="product-card glass-card">
|
<div class="product-card glass-card"<?php echo productHiddenAttr('vpc'); ?>>
|
||||||
<div class="product-icon">
|
<div class="product-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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"/>
|
<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>
|
</ul>
|
||||||
<a href="/vpc" class="btn btn-primary">Mehr erfahren</a>
|
<a href="/vpc" class="btn btn-primary">Mehr erfahren</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-card glass-card">
|
<div class="product-card glass-card"<?php echo productHiddenAttr('vps'); ?>>
|
||||||
<div class="product-icon">
|
<div class="product-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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"/>
|
<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>
|
</ul>
|
||||||
<a href="/vps" class="btn btn-primary">Mehr erfahren</a>
|
<a href="/vps" class="btn btn-primary">Mehr erfahren</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-card glass-card">
|
<div class="product-card glass-card"<?php echo productHiddenAttr('mail-gateway'); ?>>
|
||||||
<div class="product-icon">
|
<div class="product-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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"/>
|
<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"/>
|
||||||
@@ -150,7 +150,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- IT Services Section -->
|
||||||
<section class="features">
|
<section class="features">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -192,7 +192,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Features Section -->
|
||||||
<section class="features">
|
<section class="features">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -248,7 +248,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content glass-card">
|
<div class="cta-content glass-card">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Services Hero -->
|
||||||
<section class="about-hero">
|
<section class="about-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="about-hero-content">
|
<div class="about-hero-content">
|
||||||
@@ -32,7 +32,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Target Groups -->
|
||||||
<section class="values">
|
<section class="values">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -60,7 +60,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Services Grid -->
|
||||||
<section class="products">
|
<section class="products">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -128,7 +128,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content glass-card">
|
<div class="cta-content glass-card">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Product Hero -->
|
||||||
<section class="product-hero">
|
<section class="product-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="product-hero-content">
|
<div class="product-hero-content">
|
||||||
@@ -59,7 +59,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Mail Gateway Packages -->
|
||||||
<section class="packages">
|
<section class="packages">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -74,7 +74,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Security Features -->
|
||||||
<section class="technical-details">
|
<section class="technical-details">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -129,7 +129,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Use Cases -->
|
||||||
<section class="use-cases">
|
<section class="use-cases">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -159,15 +159,15 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content glass-card">
|
<div class="cta-content glass-card">
|
||||||
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
|
<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>
|
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
<a href="contact.php?product=mail-gateway" class="btn btn-primary">Jetzt bestellen</a>
|
<a href="<?php echo htmlspecialchars(getProductOrderUrl('mail-gateway'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
|
||||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Disallow: /assets/js/
|
|||||||
Disallow: /assets/css/
|
Disallow: /assets/css/
|
||||||
|
|
||||||
# Allow CSS and JS files for better SEO
|
# Allow CSS and JS files for better SEO
|
||||||
Allow: /assets/css/style.css
|
Allow: /assets/css/style.d01979e8c871.css
|
||||||
Allow: /assets/js/main.js
|
Allow: /assets/js/main.9189c38109cf.js
|
||||||
Allow: /assets/js/contact.js
|
Allow: /assets/js/contact.c2399194863d.js
|
||||||
|
|
||||||
# Sitemap location
|
# Sitemap location
|
||||||
Sitemap: https://hexahost.de/sitemap.xml
|
Sitemap: https://hexahost.de/sitemap.xml
|
||||||
|
|||||||
@@ -2,43 +2,43 @@
|
|||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://hexahost.de/</loc>
|
<loc>https://hexahost.de/</loc>
|
||||||
<lastmod>2024-01-01</lastmod>
|
<lastmod>2026-05-28</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>1.0</priority>
|
<priority>1.0</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://hexahost.de/vpc.html</loc>
|
<loc>https://hexahost.de/vpc.html</loc>
|
||||||
<lastmod>2024-01-01</lastmod>
|
<lastmod>2026-05-28</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://hexahost.de/vps.html</loc>
|
<loc>https://hexahost.de/vps.html</loc>
|
||||||
<lastmod>2024-01-01</lastmod>
|
<lastmod>2026-05-28</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://hexahost.de/mail-gateway.html</loc>
|
<loc>https://hexahost.de/mail-gateway.html</loc>
|
||||||
<lastmod>2024-01-01</lastmod>
|
<lastmod>2026-05-28</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://hexahost.de/webhosting.html</loc>
|
<loc>https://hexahost.de/webhosting.html</loc>
|
||||||
<lastmod>2024-01-01</lastmod>
|
<lastmod>2026-05-28</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://hexahost.de/about.html</loc>
|
<loc>https://hexahost.de/about.html</loc>
|
||||||
<lastmod>2024-01-01</lastmod>
|
<lastmod>2026-05-28</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://hexahost.de/contact.html</loc>
|
<loc>https://hexahost.de/contact.html</loc>
|
||||||
<lastmod>2024-01-01</lastmod>
|
<lastmod>2026-05-28</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Product Hero -->
|
||||||
<section class="product-hero">
|
<section class="product-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="product-hero-content">
|
<div class="product-hero-content">
|
||||||
@@ -58,7 +58,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- VPC Packages -->
|
||||||
<section class="packages">
|
<section class="packages">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -73,7 +73,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Technical Details -->
|
||||||
<section class="technical-details">
|
<section class="technical-details">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -129,7 +129,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Use Cases -->
|
||||||
<section class="use-cases">
|
<section class="use-cases">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -159,15 +159,15 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content glass-card">
|
<div class="cta-content glass-card">
|
||||||
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
|
<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>
|
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
<a href="contact.php?product=vpc" class="btn btn-primary">Jetzt bestellen</a>
|
<a href="<?php echo htmlspecialchars(getProductOrderUrl('vpc'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
|
||||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Product Hero -->
|
||||||
<section class="product-hero">
|
<section class="product-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="product-hero-content">
|
<div class="product-hero-content">
|
||||||
@@ -63,7 +63,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- VPS Packages -->
|
||||||
<section class="packages">
|
<section class="packages">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -78,7 +78,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Technical Details -->
|
||||||
<section class="technical-details">
|
<section class="technical-details">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -134,7 +134,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Use Cases -->
|
||||||
<section class="use-cases">
|
<section class="use-cases">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -164,15 +164,15 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content glass-card">
|
<div class="cta-content glass-card">
|
||||||
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
|
<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>
|
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
<a href="contact.php?product=vps" class="btn btn-primary">Jetzt bestellen</a>
|
<a href="<?php echo htmlspecialchars(getProductOrderUrl('vps'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
|
||||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
<!-- Product Hero -->
|
||||||
<section class="product-hero">
|
<section class="product-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="product-hero-content">
|
<div class="product-hero-content">
|
||||||
@@ -60,7 +60,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Webhosting Packages -->
|
||||||
<section class="packages">
|
<section class="packages">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -75,7 +75,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Technical Details -->
|
||||||
<section class="technical-details">
|
<section class="technical-details">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -95,7 +95,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
<polyline points="10,9 9,9 8,9"/>
|
<polyline points="10,9 9,9 8,9"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>cPanel/Webmin</h3>
|
<h3>Plesk</h3>
|
||||||
<p>Benutzerfreundliche Verwaltungsoberfläche für einfache Website-Verwaltung und E-Mail-Konfiguration.</p>
|
<p>Benutzerfreundliche Verwaltungsoberfläche für einfache Website-Verwaltung und E-Mail-Konfiguration.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-card glass-card">
|
<div class="detail-card glass-card">
|
||||||
@@ -133,7 +133,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Use Cases -->
|
||||||
<section class="use-cases">
|
<section class="use-cases">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -163,15 +163,15 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content glass-card">
|
<div class="cta-content glass-card">
|
||||||
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
|
<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>
|
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
<a href="contact.php?product=webhosting" class="btn btn-primary">Jetzt bestellen</a>
|
<a href="<?php echo htmlspecialchars(getProductOrderUrl('webhosting'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
|
||||||
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a>
|
<a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
scripts/__pycache__/obfuscate_release.cpython-314.pyc
Normal file
BIN
scripts/__pycache__/obfuscate_release.cpython-314.pyc
Normal file
Binary file not shown.
463
scripts/obfuscate_release.py
Normal file
463
scripts/obfuscate_release.py
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
TEXT_EXTENSIONS = {".php", ".html", ".htm", ".xml", ".txt", ".js", ".css"}
|
||||||
|
HASH_SUFFIX_RE = re.compile(r"\.[a-f0-9]{12}$", re.I)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_comments_keep_strings(text: str) -> str:
|
||||||
|
out = []
|
||||||
|
i = 0
|
||||||
|
n = len(text)
|
||||||
|
in_single = False
|
||||||
|
in_double = False
|
||||||
|
in_template = False
|
||||||
|
escape = False
|
||||||
|
in_line_comment = False
|
||||||
|
in_block_comment = False
|
||||||
|
|
||||||
|
while i < n:
|
||||||
|
ch = text[i]
|
||||||
|
nxt = text[i + 1] if i + 1 < n else ""
|
||||||
|
|
||||||
|
if in_line_comment:
|
||||||
|
if ch == "\n":
|
||||||
|
in_line_comment = False
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_block_comment:
|
||||||
|
if ch == "*" and nxt == "/":
|
||||||
|
in_block_comment = False
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
if ch == "\n":
|
||||||
|
out.append("\n")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_single or in_double or in_template:
|
||||||
|
out.append(ch)
|
||||||
|
if escape:
|
||||||
|
escape = False
|
||||||
|
elif ch == "\\":
|
||||||
|
escape = True
|
||||||
|
elif in_single and ch == "'":
|
||||||
|
in_single = False
|
||||||
|
elif in_double and ch == '"':
|
||||||
|
in_double = False
|
||||||
|
elif in_template and ch == "`":
|
||||||
|
in_template = False
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ch == "/" and nxt == "/":
|
||||||
|
in_line_comment = True
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if ch == "/" and nxt == "*":
|
||||||
|
in_block_comment = True
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ch == "'":
|
||||||
|
in_single = True
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if ch == '"':
|
||||||
|
in_double = True
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if ch == "`":
|
||||||
|
in_template = True
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_php_comments(text: str) -> str:
|
||||||
|
out = []
|
||||||
|
i = 0
|
||||||
|
n = len(text)
|
||||||
|
in_single = False
|
||||||
|
in_double = False
|
||||||
|
in_line_comment = False
|
||||||
|
in_block_comment = False
|
||||||
|
escape = False
|
||||||
|
|
||||||
|
while i < n:
|
||||||
|
ch = text[i]
|
||||||
|
nxt = text[i + 1] if i + 1 < n else ""
|
||||||
|
|
||||||
|
if in_line_comment:
|
||||||
|
if ch == "\n":
|
||||||
|
in_line_comment = False
|
||||||
|
out.append("\n")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_block_comment:
|
||||||
|
if ch == "*" and nxt == "/":
|
||||||
|
in_block_comment = False
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
if ch == "\n":
|
||||||
|
out.append("\n")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_single or in_double:
|
||||||
|
out.append(ch)
|
||||||
|
if escape:
|
||||||
|
escape = False
|
||||||
|
elif ch == "\\":
|
||||||
|
escape = True
|
||||||
|
elif in_single and ch == "'":
|
||||||
|
in_single = False
|
||||||
|
elif in_double and ch == '"':
|
||||||
|
in_double = False
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ch == "/" and nxt == "/":
|
||||||
|
in_line_comment = True
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if ch == "#":
|
||||||
|
in_line_comment = True
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if ch == "/" and nxt == "*":
|
||||||
|
in_block_comment = True
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ch == "'":
|
||||||
|
in_single = True
|
||||||
|
elif ch == '"':
|
||||||
|
in_double = True
|
||||||
|
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def minify_css_fallback(text: str) -> str:
|
||||||
|
text = strip_comments_keep_strings(text)
|
||||||
|
text = re.sub(r"\s+", " ", text)
|
||||||
|
text = re.sub(r"\s*([{}:;,>+~])\s*", r"\1", text)
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def minify_js_fallback(text: str) -> str:
|
||||||
|
text = strip_comments_keep_strings(text)
|
||||||
|
text = re.sub(r"\s+", " ", text)
|
||||||
|
text = re.sub(r"\s*([{}:;,()=+\-*/<>!&|?])\s*", r"\1", text)
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_asset_base(stem: str) -> str:
|
||||||
|
name = stem
|
||||||
|
while HASH_SUFFIX_RE.search(name):
|
||||||
|
name = HASH_SUFFIX_RE.sub("", name)
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def is_skipped_asset(path: Path) -> bool:
|
||||||
|
lowered = path.as_posix().lower()
|
||||||
|
if ".min." in path.name or ".obf." in path.name or ".deob." in path.name:
|
||||||
|
return True
|
||||||
|
if "deobfuscated" in lowered:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_source_content(content: str) -> bool:
|
||||||
|
if "[javascript-obfuscator-cli]" in content:
|
||||||
|
return False
|
||||||
|
return len(content.strip()) >= 20
|
||||||
|
|
||||||
|
|
||||||
|
def collect_asset_groups(asset_root: Path) -> dict[tuple[Path, str, str], list[Path]]:
|
||||||
|
groups: dict[tuple[Path, str, str], list[Path]] = defaultdict(list)
|
||||||
|
for ext in (".js", ".css"):
|
||||||
|
for file_path in sorted(asset_root.rglob(f"*{ext}")):
|
||||||
|
if is_skipped_asset(file_path):
|
||||||
|
continue
|
||||||
|
base = canonical_asset_base(file_path.stem)
|
||||||
|
key = (file_path.parent, base, ext)
|
||||||
|
groups[key].append(file_path)
|
||||||
|
return groups
|
||||||
|
|
||||||
|
|
||||||
|
def pick_source_file(paths: list[Path], base: str, ext: str) -> Path | None:
|
||||||
|
if not paths:
|
||||||
|
return None
|
||||||
|
|
||||||
|
parent = paths[0].parent
|
||||||
|
plain = parent / f"{base}{ext}"
|
||||||
|
ordered: list[Path] = []
|
||||||
|
if plain in paths:
|
||||||
|
ordered.append(plain)
|
||||||
|
for candidate in sorted(paths, key=lambda p: len(p.name)):
|
||||||
|
if candidate not in ordered:
|
||||||
|
ordered.append(candidate)
|
||||||
|
|
||||||
|
for candidate in ordered:
|
||||||
|
try:
|
||||||
|
content = candidate.read_text(encoding="utf-8")
|
||||||
|
except (OSError, UnicodeDecodeError):
|
||||||
|
continue
|
||||||
|
if is_valid_source_content(content):
|
||||||
|
return candidate
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_invalid_siblings(paths: list[Path], source: Path) -> None:
|
||||||
|
for path in paths:
|
||||||
|
if path == source or not path.exists():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
content = path.read_text(encoding="utf-8")
|
||||||
|
except (OSError, UnicodeDecodeError):
|
||||||
|
content = ""
|
||||||
|
if not is_valid_source_content(content):
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(command: list[str], cwd: Path) -> None:
|
||||||
|
proc = subprocess.run(
|
||||||
|
command,
|
||||||
|
cwd=str(cwd),
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "command failed")
|
||||||
|
|
||||||
|
|
||||||
|
def process_js(path: Path, cwd: Path) -> None:
|
||||||
|
original = path.read_text(encoding="utf-8")
|
||||||
|
if not is_valid_source_content(original):
|
||||||
|
raise ValueError(
|
||||||
|
f"invalid or corrupted JS source: {path} "
|
||||||
|
f"(restore e.g. 'git checkout dev -- {path.as_posix()}')"
|
||||||
|
)
|
||||||
|
|
||||||
|
if shutil.which("npx"):
|
||||||
|
tmpdir = Path(tempfile.mkdtemp())
|
||||||
|
try:
|
||||||
|
src = tmpdir / "input.js"
|
||||||
|
out = tmpdir / "output.js"
|
||||||
|
src.write_text(original, encoding="utf-8")
|
||||||
|
run_cmd(
|
||||||
|
[
|
||||||
|
"npx",
|
||||||
|
"--yes",
|
||||||
|
"terser",
|
||||||
|
str(src),
|
||||||
|
"-o",
|
||||||
|
str(src),
|
||||||
|
"--compress",
|
||||||
|
"--mangle",
|
||||||
|
"--comments",
|
||||||
|
"false",
|
||||||
|
],
|
||||||
|
cwd,
|
||||||
|
)
|
||||||
|
run_cmd(
|
||||||
|
[
|
||||||
|
"npx",
|
||||||
|
"--yes",
|
||||||
|
"javascript-obfuscator",
|
||||||
|
str(src),
|
||||||
|
"--output",
|
||||||
|
str(out),
|
||||||
|
"--compact",
|
||||||
|
"true",
|
||||||
|
"--control-flow-flattening",
|
||||||
|
"true",
|
||||||
|
"--dead-code-injection",
|
||||||
|
"true",
|
||||||
|
"--string-array",
|
||||||
|
"true",
|
||||||
|
"--string-array-encoding",
|
||||||
|
"base64",
|
||||||
|
"--target",
|
||||||
|
"browser-no-eval",
|
||||||
|
"--source-map",
|
||||||
|
"false",
|
||||||
|
],
|
||||||
|
cwd,
|
||||||
|
)
|
||||||
|
if not out.exists():
|
||||||
|
raise RuntimeError("obfuscator produced no output file")
|
||||||
|
result = out.read_text(encoding="utf-8")
|
||||||
|
if not is_valid_source_content(result):
|
||||||
|
raise RuntimeError("obfuscator output looks invalid")
|
||||||
|
path.write_text(result.strip() + "\n", encoding="utf-8")
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||||
|
|
||||||
|
path.write_text(minify_js_fallback(original) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def process_css(path: Path, cwd: Path) -> None:
|
||||||
|
original = path.read_text(encoding="utf-8")
|
||||||
|
if shutil.which("npx"):
|
||||||
|
tmpdir = Path(tempfile.mkdtemp())
|
||||||
|
try:
|
||||||
|
src = tmpdir / "input.css"
|
||||||
|
out = tmpdir / "output.css"
|
||||||
|
src.write_text(original, encoding="utf-8")
|
||||||
|
run_cmd(
|
||||||
|
[
|
||||||
|
"npx",
|
||||||
|
"--yes",
|
||||||
|
"clean-css-cli",
|
||||||
|
str(src),
|
||||||
|
"-o",
|
||||||
|
str(out),
|
||||||
|
"--skip-rebase",
|
||||||
|
"-O2",
|
||||||
|
],
|
||||||
|
cwd,
|
||||||
|
)
|
||||||
|
path.write_text(out.read_text(encoding="utf-8").strip() + "\n", encoding="utf-8")
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||||
|
path.write_text(minify_css_fallback(original) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def process_php(path: Path) -> None:
|
||||||
|
original = path.read_text(encoding="utf-8")
|
||||||
|
stripped = strip_php_comments(original)
|
||||||
|
path.write_text(stripped, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def hash_file(path: Path) -> str:
|
||||||
|
return hashlib.sha256(path.read_bytes()).hexdigest()[:12]
|
||||||
|
|
||||||
|
|
||||||
|
def replace_references(root: Path, mapping: dict[str, str]) -> None:
|
||||||
|
for candidate in root.rglob("*"):
|
||||||
|
if not candidate.is_file() or candidate.suffix.lower() not in TEXT_EXTENSIONS:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
content = candidate.read_text(encoding="utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
continue
|
||||||
|
updated = content
|
||||||
|
for src, dst in sorted(mapping.items(), key=lambda item: len(item[0]), reverse=True):
|
||||||
|
updated = updated.replace(src, dst)
|
||||||
|
updated = updated.replace("/" + src, "/" + dst)
|
||||||
|
if updated != content:
|
||||||
|
candidate.write_text(updated, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def build_hash_mapping(public_root: Path) -> dict[str, str]:
|
||||||
|
mapping: dict[str, str] = {}
|
||||||
|
asset_root = public_root / "assets"
|
||||||
|
if not asset_root.exists():
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
groups = collect_asset_groups(asset_root)
|
||||||
|
for (parent, base, ext), paths in groups.items():
|
||||||
|
source = pick_source_file(paths, base, ext)
|
||||||
|
if source is None:
|
||||||
|
continue
|
||||||
|
digest = hash_file(source)
|
||||||
|
target = parent / f"{base}.{digest}{ext}"
|
||||||
|
rel_new = target.relative_to(public_root).as_posix()
|
||||||
|
|
||||||
|
for old in paths:
|
||||||
|
rel_old = old.relative_to(public_root).as_posix()
|
||||||
|
if rel_old != rel_new:
|
||||||
|
mapping[rel_old] = rel_new
|
||||||
|
|
||||||
|
if source != target:
|
||||||
|
if target.exists():
|
||||||
|
target.unlink()
|
||||||
|
source.replace(target)
|
||||||
|
|
||||||
|
for old in paths:
|
||||||
|
if old != target and old.exists():
|
||||||
|
old.unlink()
|
||||||
|
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Release obfuscation build.")
|
||||||
|
parser.add_argument("--root", default=".", help="Repository root")
|
||||||
|
parser.add_argument("--hash-assets", action="store_true", help="Hash JS/CSS file names")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
repo_root = Path(args.root).resolve()
|
||||||
|
public_root = repo_root / "public"
|
||||||
|
|
||||||
|
if not public_root.exists():
|
||||||
|
print("public directory not found", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
asset_root = public_root / "assets"
|
||||||
|
if asset_root.exists():
|
||||||
|
groups = collect_asset_groups(asset_root)
|
||||||
|
for (parent, base, ext), paths in sorted(groups.items()):
|
||||||
|
source = pick_source_file(paths, base, ext)
|
||||||
|
if source is None:
|
||||||
|
rel = (parent / f"{base}{ext}").relative_to(public_root)
|
||||||
|
print(
|
||||||
|
f"ERROR: No valid source for {rel}. "
|
||||||
|
f"Restore from dev, e.g.: git checkout dev -- {rel}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
cleanup_invalid_siblings(paths, source)
|
||||||
|
if ext == ".js":
|
||||||
|
process_js(source, repo_root)
|
||||||
|
else:
|
||||||
|
process_css(source, repo_root)
|
||||||
|
|
||||||
|
for php in sorted(public_root.rglob("*.php")):
|
||||||
|
process_php(php)
|
||||||
|
for php in sorted((repo_root / "backend").rglob("*.php")):
|
||||||
|
process_php(php)
|
||||||
|
|
||||||
|
if args.hash_assets:
|
||||||
|
mapping = build_hash_mapping(public_root)
|
||||||
|
replace_references(repo_root, mapping)
|
||||||
|
|
||||||
|
print("Release obfuscation complete.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
#Requires -Version 5.1
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Erstellt einen Production-Build und veroeffentlicht ihn auf den Branch main.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
1. Wechselt auf main und setzt ihn auf den Stand von dev
|
|
||||||
2. Entfernt Kommentare, minifiziert CSS, obfuskiert JavaScript
|
|
||||||
3. Committet und pusht main (optional)
|
|
||||||
4. Wechselt zurueck auf dev (Quellcode bleibt unveraendert)
|
|
||||||
|
|
||||||
.PARAMETER Push
|
|
||||||
Pusht main nach origin (Standard: nur lokaler Commit)
|
|
||||||
|
|
||||||
.PARAMETER DryRun
|
|
||||||
Fuehrt Git-Schritte nur simuliert aus (Build wird trotzdem erstellt)
|
|
||||||
|
|
||||||
.PARAMETER AllowDirty
|
|
||||||
Erlaubt uncommittete Aenderungen
|
|
||||||
|
|
||||||
.PARAMETER Message
|
|
||||||
Commit-Nachricht fuer den Production-Build
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
.\scripts\publish-to-main.ps1
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
.\scripts\publish-to-main.ps1 -Push
|
|
||||||
#>
|
|
||||||
[CmdletBinding()]
|
|
||||||
param(
|
|
||||||
[switch]$Push,
|
|
||||||
[switch]$DryRun,
|
|
||||||
[switch]$AllowDirty,
|
|
||||||
[string]$Message = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
||||||
$BuildDir = Join-Path $Root "scripts\build"
|
|
||||||
$OriginalBranch = ""
|
|
||||||
|
|
||||||
function Write-Step([string]$Text) {
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "==> $Text" -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
function Ensure-GitClean {
|
|
||||||
$status = git -C $Root status --porcelain
|
|
||||||
if ($status) {
|
|
||||||
throw "Uncommittete Aenderungen im Repository. Bitte zuerst committen oder stashen."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Resolve-NodeTool([string]$ToolName) {
|
|
||||||
$command = Get-Command $ToolName -ErrorAction SilentlyContinue
|
|
||||||
if ($command) {
|
|
||||||
return $command.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
$candidates = @(
|
|
||||||
(Join-Path $env:ProgramFiles "nodejs\$ToolName.cmd"),
|
|
||||||
(Join-Path ${env:ProgramFiles(x86)} "nodejs\$ToolName.cmd"),
|
|
||||||
(Join-Path $env:LOCALAPPDATA "Programs\nodejs\$ToolName.cmd"),
|
|
||||||
"c:\Program Files\cursor\resources\app\resources\helpers\node.exe"
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach ($candidate in $candidates) {
|
|
||||||
if ($ToolName -eq "node" -and (Test-Path $candidate)) {
|
|
||||||
return $candidate
|
|
||||||
}
|
|
||||||
if ($ToolName -ne "node" -and (Test-Path $candidate)) {
|
|
||||||
return $candidate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
|
|
||||||
function Ensure-Node {
|
|
||||||
$script:NodeExe = Resolve-NodeTool "node"
|
|
||||||
$script:NpmExe = Resolve-NodeTool "npm"
|
|
||||||
|
|
||||||
if (-not $script:NodeExe) {
|
|
||||||
throw "Node.js ist nicht installiert. Bitte Node.js 18+ installieren: https://nodejs.org/"
|
|
||||||
}
|
|
||||||
if (-not $script:NpmExe) {
|
|
||||||
throw "npm wurde nicht gefunden. Bitte Node.js inkl. npm installieren und PATH setzen."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Set-Location $Root
|
|
||||||
Ensure-Node
|
|
||||||
if (-not $AllowDirty) {
|
|
||||||
Ensure-GitClean
|
|
||||||
} else {
|
|
||||||
Write-Warning "AllowDirty aktiv - uncommittete Aenderungen werden mit veroeffentlicht."
|
|
||||||
}
|
|
||||||
|
|
||||||
$OriginalBranch = (git branch --show-current).Trim()
|
|
||||||
if ($OriginalBranch -ne "dev") {
|
|
||||||
Write-Warning "Empfohlen: Auf Branch 'dev' starten (aktuell: $OriginalBranch)"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([string]::IsNullOrWhiteSpace($Message)) {
|
|
||||||
$Message = "chore(release): production build $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Step "Installiere Build-Abhaengigkeiten"
|
|
||||||
Set-Location $BuildDir
|
|
||||||
if (-not $DryRun) {
|
|
||||||
& $NpmExe ci --no-fund --no-audit
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw "npm ci fehlgeschlagen" }
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Step "Wechsle auf main und synchronisiere mit dev"
|
|
||||||
Set-Location $Root
|
|
||||||
if ($DryRun) {
|
|
||||||
Write-Host '[DryRun] git checkout main'
|
|
||||||
Write-Host '[DryRun] git reset --hard dev'
|
|
||||||
} else {
|
|
||||||
git checkout main
|
|
||||||
git reset --hard dev
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Step "Production-Build (Kommentare entfernen, JS obfuscaten)"
|
|
||||||
Set-Location $BuildDir
|
|
||||||
if ($DryRun) {
|
|
||||||
Write-Host '[DryRun] npm run build:in-place'
|
|
||||||
} else {
|
|
||||||
& $NpmExe run build:in-place
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw "Production-Build fehlgeschlagen" }
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Step "Production-Build committen"
|
|
||||||
Set-Location $Root
|
|
||||||
if ($DryRun) {
|
|
||||||
Write-Host '[DryRun] git add -A'
|
|
||||||
Write-Host ('[DryRun] git commit -m "' + $Message + '"')
|
|
||||||
} else {
|
|
||||||
git add -A
|
|
||||||
$null = git diff --cached --quiet
|
|
||||||
if ($LASTEXITCODE -eq 0) {
|
|
||||||
Write-Warning "Keine Build-Aenderungen - nichts zu committen."
|
|
||||||
} else {
|
|
||||||
git commit -m $Message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($Push) {
|
|
||||||
Write-Step "Push nach origin/main"
|
|
||||||
if ($DryRun) {
|
|
||||||
Write-Host '[DryRun] git push origin main'
|
|
||||||
} else {
|
|
||||||
git push origin main
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "Hinweis: Ohne -Push wurde nur lokal auf main gebaut." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Step "Zurueck auf $OriginalBranch"
|
|
||||||
if (-not $DryRun) {
|
|
||||||
if ([string]::IsNullOrWhiteSpace($OriginalBranch)) {
|
|
||||||
git checkout dev
|
|
||||||
} else {
|
|
||||||
git checkout $OriginalBranch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "Production-Release abgeschlossen." -ForegroundColor Green
|
|
||||||
if (-not $Push -and -not $DryRun) {
|
|
||||||
Write-Host "Zum Veroeffentlichen: git push origin main" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host ('FEHLER: ' + $_.Exception.Message) -ForegroundColor Red
|
|
||||||
Set-Location $Root
|
|
||||||
if ($OriginalBranch -and -not $DryRun) {
|
|
||||||
git checkout $OriginalBranch 2>$null
|
|
||||||
}
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# Erstellt einen Production-Build und veröffentlicht ihn auf den Branch main.
|
|
||||||
#
|
|
||||||
# 1. Wechselt auf main und setzt ihn auf den Stand von dev
|
|
||||||
# 2. Entfernt Kommentare, minifiziert CSS, obfuskiert JavaScript
|
|
||||||
# 3. Committet und pusht main (optional)
|
|
||||||
# 4. Wechselt zurück auf den ursprünglichen Branch (dev bleibt unverändert)
|
|
||||||
#
|
|
||||||
# Nutzung:
|
|
||||||
# ./scripts/publish-to-main.sh
|
|
||||||
# ./scripts/publish-to-main.sh --push
|
|
||||||
# ./scripts/publish-to-main.sh --dry-run
|
|
||||||
# ./scripts/publish-to-main.sh --allow-dirty --message "chore(release): v1.2"
|
|
||||||
#
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
PUSH=false
|
|
||||||
DRY_RUN=false
|
|
||||||
ALLOW_DIRTY=false
|
|
||||||
MESSAGE=""
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'EOF'
|
|
||||||
Usage: publish-to-main.sh [OPTIONS]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--push Push nach origin/main
|
|
||||||
--dry-run Git-Schritte nur anzeigen (Build wird ausgeführt)
|
|
||||||
--allow-dirty Uncommittete Änderungen erlauben
|
|
||||||
--message TEXT Commit-Nachricht
|
|
||||||
-h, --help Hilfe anzeigen
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--push)
|
|
||||||
PUSH=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--dry-run)
|
|
||||||
DRY_RUN=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--allow-dirty)
|
|
||||||
ALLOW_DIRTY=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--message)
|
|
||||||
MESSAGE="${2:-}"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unbekannte Option: $1" >&2
|
|
||||||
usage >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
BUILD_DIR="$ROOT/scripts/build"
|
|
||||||
ORIGINAL_BRANCH=""
|
|
||||||
|
|
||||||
step() {
|
|
||||||
echo ""
|
|
||||||
echo "==> $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
require_command() {
|
|
||||||
if ! command -v "$1" >/dev/null 2>&1; then
|
|
||||||
echo "FEHLER: '$1' nicht gefunden. Bitte installieren und PATH setzen." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_git_clean() {
|
|
||||||
if [[ -n "$(git -C "$ROOT" status --porcelain)" ]]; then
|
|
||||||
echo "FEHLER: Uncommittete Änderungen. Bitte zuerst committen oder stashen." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup_on_error() {
|
|
||||||
echo ""
|
|
||||||
echo "FEHLER: Abgebrochen." >&2
|
|
||||||
cd "$ROOT" || true
|
|
||||||
if [[ -n "$ORIGINAL_BRANCH" && "$DRY_RUN" == false ]]; then
|
|
||||||
git checkout "$ORIGINAL_BRANCH" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup_on_error ERR
|
|
||||||
|
|
||||||
require_command node
|
|
||||||
require_command npm
|
|
||||||
require_command git
|
|
||||||
|
|
||||||
cd "$ROOT"
|
|
||||||
|
|
||||||
if [[ "$ALLOW_DIRTY" == false ]]; then
|
|
||||||
ensure_git_clean
|
|
||||||
else
|
|
||||||
echo "WARNUNG: --allow-dirty aktiv – uncommittete Änderungen werden mit veröffentlicht." >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
ORIGINAL_BRANCH="$(git branch --show-current | tr -d '[:space:]')"
|
|
||||||
if [[ "$ORIGINAL_BRANCH" != "dev" ]]; then
|
|
||||||
echo "WARNUNG: Empfohlen auf Branch 'dev' zu starten (aktuell: ${ORIGINAL_BRANCH:-detached})" >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$MESSAGE" ]]; then
|
|
||||||
MESSAGE="chore(release): production build $(date '+%Y-%m-%d %H:%M')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
step "Installiere Build-Abhängigkeiten"
|
|
||||||
cd "$BUILD_DIR"
|
|
||||||
if [[ "$DRY_RUN" == false ]]; then
|
|
||||||
npm ci --no-fund --no-audit
|
|
||||||
fi
|
|
||||||
|
|
||||||
step "Wechsle auf main und synchronisiere mit dev"
|
|
||||||
cd "$ROOT"
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
|
||||||
echo "[DryRun] git checkout main"
|
|
||||||
echo "[DryRun] git reset --hard dev"
|
|
||||||
else
|
|
||||||
git checkout main
|
|
||||||
git reset --hard dev
|
|
||||||
fi
|
|
||||||
|
|
||||||
step "Production-Build (Kommentare entfernen, JS obfuscaten)"
|
|
||||||
cd "$BUILD_DIR"
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
|
||||||
echo "[DryRun] npm run build:in-place"
|
|
||||||
else
|
|
||||||
npm run build:in-place
|
|
||||||
fi
|
|
||||||
|
|
||||||
step "Production-Build committen"
|
|
||||||
cd "$ROOT"
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
|
||||||
echo "[DryRun] git add -A"
|
|
||||||
echo "[DryRun] git commit -m \"$MESSAGE\""
|
|
||||||
else
|
|
||||||
git add -A
|
|
||||||
if git diff --cached --quiet; then
|
|
||||||
echo "WARNUNG: Keine Build-Änderungen – nichts zu committen." >&2
|
|
||||||
else
|
|
||||||
git commit -m "$MESSAGE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$PUSH" == true ]]; then
|
|
||||||
step "Push nach origin/main"
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
|
||||||
echo "[DryRun] git push origin main"
|
|
||||||
else
|
|
||||||
git push origin main
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Hinweis: Ohne --push wurde nur lokal auf main gebaut."
|
|
||||||
fi
|
|
||||||
|
|
||||||
step "Zurück auf ${ORIGINAL_BRANCH:-dev}"
|
|
||||||
if [[ "$DRY_RUN" == false ]]; then
|
|
||||||
if [[ -n "$ORIGINAL_BRANCH" ]]; then
|
|
||||||
git checkout "$ORIGINAL_BRANCH"
|
|
||||||
else
|
|
||||||
git checkout dev
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
trap - ERR
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Production-Release abgeschlossen."
|
|
||||||
if [[ "$PUSH" == false && "$DRY_RUN" == false ]]; then
|
|
||||||
echo "Zum Veröffentlichen: git push origin main"
|
|
||||||
fi
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#Requires -Version 5.1
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Erstellt ein Production-Bundle unter dist/ (ohne Branch-Wechsel).
|
|
||||||
#>
|
|
||||||
[CmdletBinding()]
|
|
||||||
param(
|
|
||||||
[switch]$InPlace
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
||||||
$BuildDir = Join-Path $Root "scripts\build"
|
|
||||||
|
|
||||||
function Resolve-NodeTool([string]$ToolName) {
|
|
||||||
$command = Get-Command $ToolName -ErrorAction SilentlyContinue
|
|
||||||
if ($command) { return $command.Source }
|
|
||||||
|
|
||||||
$candidates = @(
|
|
||||||
(Join-Path $env:ProgramFiles "nodejs\$ToolName.cmd"),
|
|
||||||
(Join-Path ${env:ProgramFiles(x86)} "nodejs\$ToolName.cmd")
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach ($candidate in $candidates) {
|
|
||||||
if (Test-Path $candidate) { return $candidate }
|
|
||||||
}
|
|
||||||
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
|
|
||||||
$npm = Resolve-NodeTool "npm"
|
|
||||||
if (-not $npm) {
|
|
||||||
throw "npm nicht gefunden. Bitte Node.js installieren."
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location $BuildDir
|
|
||||||
& $npm ci --no-fund --no-audit
|
|
||||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
||||||
|
|
||||||
if ($InPlace) {
|
|
||||||
& $npm run build:in-place
|
|
||||||
} else {
|
|
||||||
& $npm run build
|
|
||||||
}
|
|
||||||
|
|
||||||
exit $LASTEXITCODE
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# Erstellt ein Production-Bundle unter dist/ (ohne Branch-Wechsel).
|
|
||||||
#
|
|
||||||
# Nutzung:
|
|
||||||
# ./scripts/run-build.sh
|
|
||||||
# ./scripts/run-build.sh --in-place
|
|
||||||
#
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
IN_PLACE=false
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'EOF'
|
|
||||||
Usage: run-build.sh [OPTIONS]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--in-place Build direkt im Repository (statt dist/)
|
|
||||||
-h, --help Hilfe anzeigen
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--in-place)
|
|
||||||
IN_PLACE=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unbekannte Option: $1" >&2
|
|
||||||
usage >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
BUILD_DIR="$ROOT/scripts/build"
|
|
||||||
|
|
||||||
if ! command -v npm >/dev/null 2>&1; then
|
|
||||||
echo "FEHLER: npm nicht gefunden. Bitte Node.js installieren." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$BUILD_DIR"
|
|
||||||
npm ci --no-fund --no-audit
|
|
||||||
|
|
||||||
if [[ "$IN_PLACE" == true ]]; then
|
|
||||||
npm run build:in-place
|
|
||||||
else
|
|
||||||
npm run build
|
|
||||||
fi
|
|
||||||
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user