mirror of
https://git.hexahost.dev/smueller/HexaHost-Frontend.git
synced 2026-06-02 07:48:43 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d54cea5ec | ||
|
|
fdd0367281 | ||
|
|
ded8778b6c | ||
|
|
7a03f5aa1b | ||
|
|
f7ea36f4f2 | ||
|
|
99f0056106 | ||
|
|
e91a9ed9c3 | ||
|
|
76aceddcca | ||
|
|
d7851763f7 | ||
|
|
1c0a3ff468 | ||
|
|
4b9940c18b | ||
|
|
24a852aab5 | ||
|
|
219f1d2fcf | ||
|
|
06a932a048 | ||
|
|
f4947d5e25 | ||
|
|
45a7067878 | ||
|
|
4787d7b770 | ||
|
|
a0aa8b12ca | ||
|
|
b113bdeaa2 | ||
|
|
5d2be60dfa | ||
|
|
62d0076799 | ||
|
|
e920fdfc8e | ||
|
|
5d953fda7b | ||
|
|
6ca4786955 | ||
|
|
b9bd339607 | ||
|
|
b893272d64 | ||
|
|
3dd707ab93 | ||
|
|
cc1a48943a | ||
|
|
dfc781f3ed | ||
|
|
d0e5baa443 | ||
|
|
8afba16905 | ||
|
|
96a5977283 | ||
|
|
ec8686761c | ||
|
|
d3da589a1d | ||
|
|
b6e268855e | ||
|
|
d02377c735 | ||
|
|
d62d6b576d | ||
|
|
2c0138f55d | ||
|
|
2074707c9d | ||
|
|
55f9fdd957 | ||
|
|
ab81d1c49f | ||
|
|
b40ad53d9c | ||
|
|
e5402189ea | ||
|
|
e544720900 | ||
| a5bba86db0 | |||
| d34dbbb079 |
@@ -1,9 +1,9 @@
|
|||||||
name: Release Build (dev → main)
|
name: Release Build (ci → main)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- ci
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -16,29 +16,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout (volle History)
|
- name: Checkout ci (Integration)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
repository-url: https://git.hexahost.dev/smueller/HexaHost-Frontend
|
repository-url: https://git.hexahost.dev/smueller/HexaHost-Frontend
|
||||||
ref: dev
|
ref: ci
|
||||||
|
|
||||||
- name: Merge dev in CI-Workspace (Basis 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 dev
|
|
||||||
|
|
||||||
if git show-ref --verify --quiet refs/remotes/origin/main; then
|
|
||||||
git checkout -B main origin/main
|
|
||||||
git merge origin/dev -X theirs --no-edit -m "ci: merge dev for release build"
|
|
||||||
else
|
|
||||||
echo "main branch missing, initializing from dev"
|
|
||||||
git checkout -B main origin/dev
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -60,10 +43,20 @@ jobs:
|
|||||||
git config user.name "gitea-actions"
|
git config user.name "gitea-actions"
|
||||||
git config user.email "actions@local"
|
git config user.email "actions@local"
|
||||||
git remote set-url origin "https://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}.git"
|
git remote set-url origin "https://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}.git"
|
||||||
|
git fetch origin main ci
|
||||||
|
|
||||||
git add -A
|
git add -A
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo "No release changes to publish."
|
echo "No release changes to publish."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
git commit -m "chore(release): obfuscate and hash production assets [skip ci]"
|
|
||||||
git push origin main
|
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
|
||||||
39
.github/workflows/obfuscate-main.yml
vendored
39
.github/workflows/obfuscate-main.yml
vendored
@@ -1,10 +1,10 @@
|
|||||||
# Hinweis: Gitea nutzt .gitea/workflows/obfuscate-main.yml (identischer Ablauf).
|
# Hinweis: Gitea nutzt .gitea/workflows/obfuscate-main.yml (identischer Ablauf).
|
||||||
name: Release Build (dev → main)
|
name: Release Build (ci → main)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- ci
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -17,29 +17,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout (volle History)
|
- name: Checkout ci (Integration)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
repository-url: https://git.hexahost.dev/smueller/HexaHost-Frontend
|
repository-url: https://git.hexahost.dev/smueller/HexaHost-Frontend
|
||||||
ref: dev
|
ref: ci
|
||||||
|
|
||||||
- name: Merge dev in CI-Workspace (Basis 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 dev
|
|
||||||
|
|
||||||
if git show-ref --verify --quiet refs/remotes/origin/main; then
|
|
||||||
git checkout -B main origin/main
|
|
||||||
git merge origin/dev -X theirs --no-edit -m "ci: merge dev for release build"
|
|
||||||
else
|
|
||||||
echo "main branch missing, initializing from dev"
|
|
||||||
git checkout -B main origin/dev
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -61,10 +44,20 @@ jobs:
|
|||||||
git config user.name "gitea-actions"
|
git config user.name "gitea-actions"
|
||||||
git config user.email "actions@local"
|
git config user.email "actions@local"
|
||||||
git remote set-url origin "https://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}.git"
|
git remote set-url origin "https://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}.git"
|
||||||
|
git fetch origin main ci
|
||||||
|
|
||||||
git add -A
|
git add -A
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo "No release changes to publish."
|
echo "No release changes to publish."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
git commit -m "chore(release): obfuscate and hash production assets [skip ci]"
|
|
||||||
git push origin main
|
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
|
||||||
31
README.md
31
README.md
@@ -169,16 +169,33 @@ Für den Produktivbetrieb `public/` als Webroot konfigurieren.
|
|||||||
| Branch | Zweck |
|
| Branch | Zweck |
|
||||||
|--------|--------|
|
|--------|--------|
|
||||||
| **`dev`** | Entwicklung (lesbarer Code, Kommentare) |
|
| **`dev`** | Entwicklung (lesbarer Code, Kommentare) |
|
||||||
| **`main`** | Release/Produktion (obfuskiert, gehashte Assets) |
|
| **`ci`** | Integration (du mergst `dev` hierher) |
|
||||||
|
| **`main`** | Release/Produktion (obfuskiert, gehashte Assets — nur per Pipeline) |
|
||||||
|
|
||||||
**Workflow:** Nur auf `dev` entwickeln und pushen — **nicht** `dev` manuell nach `main` mergen.
|
**Ablauf: `dev` → `ci` → `main`**
|
||||||
|
|
||||||
Bei jedem Push auf `dev` startet `.gitea/workflows/obfuscate-main.yml`:
|
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`**
|
||||||
|
|
||||||
1. Checkout in temporärem Runner-Workspace
|
```powershell
|
||||||
2. `dev` in CI mit `main` mergen (`-X theirs`, dev-Inhalte bei Konflikten)
|
# Nach fertigen Änderungen auf dev:
|
||||||
3. Obfuscation-Build (`scripts/obfuscate_release.py --hash-assets`)
|
git checkout ci
|
||||||
4. Ergebnis nach `main` pushen (Bot-Commit mit `[skip ci]`)
|
git pull origin ci
|
||||||
|
git merge dev
|
||||||
|
git push origin ci
|
||||||
|
```
|
||||||
|
|
||||||
|
Bei jedem Push auf **`ci`**:
|
||||||
|
|
||||||
|
1. Checkout von `ci` im temporären Runner-Workspace
|
||||||
|
2. Obfuscation-Build (`scripts/obfuscate_release.py --hash-assets`)
|
||||||
|
3. Ergebnis nach `main` pushen (Bot-Commit mit `[skip ci]`)
|
||||||
|
|
||||||
|
**Nicht** `dev` oder `ci` direkt nach `main` mergen. Der Branch **`ci` bleibt lesbar** — Obfuscation wird nur nach `main` publiziert.
|
||||||
|
|
||||||
|
`ci`-Branch einmalig anlegen (falls noch nicht vorhanden): `git checkout -b ci dev && git push -u origin ci`
|
||||||
|
|
||||||
Der Build führt aus:
|
Der Build führt aus:
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* HexaHost.de Konfiguration
|
|
||||||
*
|
|
||||||
* HINWEIS: Diese Datei ist veraltet!
|
|
||||||
*
|
|
||||||
* Die Konfiguration wurde nach mail-config.php verschoben.
|
|
||||||
* Bitte verwenden Sie stattdessen:
|
|
||||||
*
|
|
||||||
* require_once 'config/mail-config.php';
|
|
||||||
*
|
|
||||||
* Diese Datei wird nur aus Kompatibilitätsgründen beibehalten.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Lade die neue Konfiguration
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
require_once __DIR__ . '/mail-config.php';
|
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',
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* HexaHost.de Mail Configuration
|
|
||||||
*
|
|
||||||
* Dieses Projekt versendet E-Mails nativ über PHP mail().
|
|
||||||
* Es sind keine externen Bibliotheken oder Composer-Installationen erforderlich.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// E-Mail Adressen
|
|
||||||
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de'); // Absender-E-Mail
|
|
||||||
define('SMTP_TO_EMAIL', 'info@hexahost.de'); // Empfänger-E-Mail für Kontaktformular
|
|
||||||
|
|
||||||
// Sicherheitseinstellungen
|
|
||||||
define('ENABLE_CSRF_PROTECTION', true); // CSRF-Schutz aktivieren
|
|
||||||
define('ENABLE_RATE_LIMITING', true); // Rate-Limiting aktivieren
|
|
||||||
define('MAX_REQUESTS_PER_HOUR', 10); // Max. Anfragen pro Stunde
|
|
||||||
|
|
||||||
// Spam-Schutz Einstellungen
|
|
||||||
define('ENABLE_SPAM_PROTECTION', true); // Spam-Schutz aktivieren
|
|
||||||
define('MAX_MESSAGE_LENGTH', 5000); // Max. Nachrichtenlänge
|
|
||||||
define('MIN_MESSAGE_LENGTH', 10); // Min. Nachrichtenlänge
|
|
||||||
|
|
||||||
// Debug-Einstellungen (nur für Entwicklung)
|
|
||||||
define('DEBUG_MODE', false); // Debug-Modus (true/false)
|
|
||||||
define('LOG_EMAILS', true); // E-Mails loggen (true/false)
|
|
||||||
|
|
||||||
// Zusätzliche Sicherheitsheader
|
|
||||||
|
|
||||||
|
|
||||||
|
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
|
||||||
|
define('SMTP_TO_EMAIL', 'info@hexahost.de');
|
||||||
|
|
||||||
|
|
||||||
|
define('ENABLE_CSRF_PROTECTION', true);
|
||||||
|
define('ENABLE_RATE_LIMITING', true);
|
||||||
|
define('MAX_REQUESTS_PER_HOUR', 5);
|
||||||
|
|
||||||
|
|
||||||
|
define('ENABLE_SPAM_PROTECTION', true);
|
||||||
|
define('MAX_MESSAGE_LENGTH', 5000);
|
||||||
|
define('MIN_MESSAGE_LENGTH', 10);
|
||||||
|
|
||||||
|
|
||||||
|
define('DEBUG_MODE', false);
|
||||||
|
define('LOG_EMAILS', true);
|
||||||
|
|
||||||
|
|
||||||
define('ADDITIONAL_HEADERS', [
|
define('ADDITIONAL_HEADERS', [
|
||||||
'X-Mailer' => 'HexaHost.de Contact Form',
|
'X-Mailer' => 'HexaHost.de Contact Form',
|
||||||
'X-Priority' => '3',
|
'X-Priority' => '3',
|
||||||
@@ -35,22 +35,22 @@ define('ADDITIONAL_HEADERS', [
|
|||||||
'Precedence' => 'bulk'
|
'Precedence' => 'bulk'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Erlaubte Domains für E-Mail-Adressen (optional)
|
|
||||||
define('ALLOWED_EMAIL_DOMAINS', [
|
define('ALLOWED_EMAIL_DOMAINS', [
|
||||||
// Leer lassen für alle Domains zu erlauben
|
|
||||||
// 'gmail.com',
|
|
||||||
// 'outlook.com',
|
|
||||||
// 'web.de',
|
|
||||||
// 'gmx.de'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Blacklist für E-Mail-Adressen (optional)
|
|
||||||
define('BLACKLISTED_EMAILS', [
|
define('BLACKLISTED_EMAILS', [
|
||||||
// 'spam@example.com',
|
|
||||||
// 'test@test.com'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Überprüfung der E-Mail-Adressen
|
|
||||||
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');
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ if (!filter_var(SMTP_TO_EMAIL, FILTER_VALIDATE_EMAIL)) {
|
|||||||
die('Ungültige SMTP_TO_EMAIL Adresse');
|
die('Ungültige SMTP_TO_EMAIL Adresse');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logging-Funktion
|
|
||||||
function logEmail($type, $data) {
|
function logEmail($type, $data) {
|
||||||
if (!LOG_EMAILS) return;
|
if (!LOG_EMAILS) return;
|
||||||
|
|
||||||
@@ -76,18 +76,18 @@ function logEmail($type, $data) {
|
|||||||
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hilfsfunktion für E-Mail-Validierung
|
|
||||||
function isValidEmail($email) {
|
function isValidEmail($email) {
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfe Blacklist
|
|
||||||
if (in_array($email, BLACKLISTED_EMAILS)) {
|
if (in_array($email, BLACKLISTED_EMAILS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfe Domain-Whitelist (falls gesetzt)
|
|
||||||
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)) {
|
||||||
@@ -98,29 +98,29 @@ function isValidEmail($email) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsfunktion zum Abrufen der Konfiguration als Array
|
|
||||||
* Kompatibilität mit contact-handler.php
|
|
||||||
*
|
|
||||||
* @param string|null $key Optional: einzelner Schlüssel
|
|
||||||
* @return mixed Konfigurationsarray oder einzelner Wert
|
|
||||||
*/
|
|
||||||
function getHexaHostConfig($key = null) {
|
function getHexaHostConfig($key = null) {
|
||||||
$config = [
|
$config = [
|
||||||
// Absender/Empfänger
|
|
||||||
'from_email' => SMTP_FROM_EMAIL,
|
'from_email' => SMTP_FROM_EMAIL,
|
||||||
'from_name' => 'HexaHost.de Kontaktformular',
|
'from_name' => 'HexaHost.de Kontaktformular',
|
||||||
'to_email' => SMTP_TO_EMAIL,
|
'to_email' => SMTP_TO_EMAIL,
|
||||||
'to_name' => 'HexaHost Support',
|
'to_name' => 'HexaHost Support',
|
||||||
|
|
||||||
// Sicherheit
|
|
||||||
'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,
|
||||||
'min_message_length' => MIN_MESSAGE_LENGTH,
|
'min_message_length' => MIN_MESSAGE_LENGTH,
|
||||||
'max_message_length' => MAX_MESSAGE_LENGTH,
|
'max_message_length' => MAX_MESSAGE_LENGTH,
|
||||||
|
|
||||||
// Debug
|
|
||||||
'debug_mode' => DEBUG_MODE,
|
'debug_mode' => DEBUG_MODE,
|
||||||
'log_errors' => LOG_EMAILS,
|
'log_errors' => LOG_EMAILS,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* HexaHost.de Produkt-Konfiguration
|
|
||||||
*
|
|
||||||
* Hier können Sie alle Preise, Shop-Links und Produktinformationen zentral verwalten.
|
|
||||||
* Pro Paket: shop_url (WHMCS/Warenkorb-Link, z. B. https://shop.hexahost.de/cart.php?a=add&pid=123)
|
|
||||||
* Nach Änderungen: npm run build && npm run deploy
|
|
||||||
*
|
|
||||||
* Verwendung in PHP-Seiten:
|
|
||||||
* require_once 'config/products-config.php';
|
|
||||||
* $packages = getProductPackages('vpc');
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// VIRTUAL PRIVATE CONTAINER (VPC)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$PRODUCTS['vpc'] = [
|
$PRODUCTS['vpc'] = [
|
||||||
'name' => 'Virtual Private Container',
|
'name' => 'Virtual Private Container',
|
||||||
'short_name' => 'VPC',
|
'short_name' => 'VPC',
|
||||||
@@ -117,9 +117,9 @@ $PRODUCTS['vpc'] = [
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// VIRTUAL PRIVATE SERVER (VPS)
|
|
||||||
// ============================================================================
|
|
||||||
$PRODUCTS['vps'] = [
|
$PRODUCTS['vps'] = [
|
||||||
'name' => 'Virtual Private Server',
|
'name' => 'Virtual Private Server',
|
||||||
'short_name' => 'VPS',
|
'short_name' => 'VPS',
|
||||||
@@ -223,9 +223,9 @@ $PRODUCTS['vps'] = [
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// MAIL GATEWAY
|
|
||||||
// ============================================================================
|
|
||||||
$PRODUCTS['mail-gateway'] = [
|
$PRODUCTS['mail-gateway'] = [
|
||||||
'name' => 'Mail Gateway',
|
'name' => 'Mail Gateway',
|
||||||
'short_name' => 'Mail',
|
'short_name' => 'Mail',
|
||||||
@@ -329,9 +329,9 @@ $PRODUCTS['mail-gateway'] = [
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// WEBHOSTING
|
|
||||||
// ============================================================================
|
|
||||||
$PRODUCTS['webhosting'] = [
|
$PRODUCTS['webhosting'] = [
|
||||||
'name' => 'Webhosting',
|
'name' => 'Webhosting',
|
||||||
'short_name' => 'Webhosting',
|
'short_name' => 'Webhosting',
|
||||||
@@ -443,68 +443,101 @@ $PRODUCTS['webhosting'] = [
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// HILFSFUNKTIONEN
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
$PRODUCT_VISIBILITY = [
|
||||||
* Alle Produkte abrufen
|
'vpc' => false,
|
||||||
*/
|
'vps' => false,
|
||||||
|
'mail-gateway' => false,
|
||||||
|
'webhosting' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function isProductVisible(string $productId): bool {
|
||||||
|
global $PRODUCT_VISIBILITY;
|
||||||
|
return $PRODUCT_VISIBILITY[$productId] ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function productHiddenAttr(string $productId): string {
|
||||||
|
return isProductVisible($productId) ? '' : ' hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getVisibleProductPageIds(): array {
|
||||||
|
global $PRODUCT_VISIBILITY;
|
||||||
|
return array_keys(array_filter($PRODUCT_VISIBILITY, static fn(bool $visible): bool => $visible));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getAllProducts() {
|
function getAllProducts() {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS;
|
return $PRODUCTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein Produkt abrufen
|
|
||||||
*/
|
|
||||||
function getProduct($productId) {
|
function getProduct($productId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId] ?? null;
|
return $PRODUCTS[$productId] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Alle Pakete eines Produkts abrufen
|
|
||||||
*/
|
|
||||||
function getProductPackages($productId) {
|
function getProductPackages($productId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId]['packages'] ?? [];
|
return $PRODUCTS[$productId]['packages'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein bestimmtes Paket abrufen
|
|
||||||
*/
|
|
||||||
function getPackage($productId, $packageId) {
|
function getPackage($productId, $packageId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
|
return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Preis eines Pakets abrufen
|
|
||||||
*/
|
|
||||||
function getPackagePrice($productId, $packageId) {
|
function getPackagePrice($productId, $packageId) {
|
||||||
$package = getPackage($productId, $packageId);
|
$package = getPackage($productId, $packageId);
|
||||||
return $package['price'] ?? null;
|
return $package['price'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimalen Preis eines Produkts abrufen
|
|
||||||
*/
|
|
||||||
function getMinPrice($productId) {
|
function getMinPrice($productId) {
|
||||||
global $PRODUCTS;
|
global $PRODUCTS;
|
||||||
return $PRODUCTS[$productId]['min_price'] ?? null;
|
return $PRODUCTS[$productId]['min_price'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Preis formatiert ausgeben
|
|
||||||
*/
|
|
||||||
function formatPrice($price, $withCurrency = true) {
|
function formatPrice($price, $withCurrency = true) {
|
||||||
return $withCurrency ? $price . '€' : $price;
|
return $withCurrency ? $price . '€' : $price;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bestell-Link für ein Paket (Online-Shop oder Kontaktformular)
|
|
||||||
*/
|
|
||||||
function getOrderUrl($productId, $packageId) {
|
function getOrderUrl($productId, $packageId) {
|
||||||
$package = getPackage($productId, $packageId);
|
$package = getPackage($productId, $packageId);
|
||||||
if ($package && !empty($package['shop_url'])) {
|
if ($package && !empty($package['shop_url'])) {
|
||||||
@@ -514,9 +547,9 @@ function getOrderUrl($productId, $packageId) {
|
|||||||
return sprintf('contact.php?package=%s-%s', $productId, $packageId);
|
return sprintf('contact.php?package=%s-%s', $productId, $packageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bestell-Link für CTA (beliebtes Paket oder erstes Paket)
|
|
||||||
*/
|
|
||||||
function getProductOrderUrl($productId) {
|
function getProductOrderUrl($productId) {
|
||||||
$packages = getProductPackages($productId);
|
$packages = getProductPackages($productId);
|
||||||
|
|
||||||
@@ -534,9 +567,9 @@ function getProductOrderUrl($productId) {
|
|||||||
return sprintf('contact.php?product=%s', $productId);
|
return sprintf('contact.php?product=%s', $productId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generiert HTML für eine Paket-Karte
|
|
||||||
*/
|
|
||||||
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>' : '';
|
||||||
@@ -583,9 +616,9 @@ function renderPackageCard($productId, $packageId, $package) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generiert HTML für alle Pakete eines Produkts
|
|
||||||
*/
|
|
||||||
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">
|
||||||
@@ -126,7 +133,7 @@
|
|||||||
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>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* Helper functions for HexaHost.de
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Sichere Session-Konfiguration
|
|
||||||
|
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../config/products-config.php';
|
||||||
|
|
||||||
|
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
// Session-Cookie-Sicherheit
|
|
||||||
ini_set('session.cookie_httponly', 1);
|
ini_set('session.cookie_httponly', 1);
|
||||||
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0);
|
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0);
|
||||||
ini_set('session.cookie_samesite', 'Strict');
|
ini_set('session.cookie_samesite', 'Strict');
|
||||||
@@ -14,14 +16,14 @@ if (session_status() === PHP_SESSION_NONE) {
|
|||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
// Session-ID regenerieren bei Login/wichtigen Aktionen (Schutz vor Session Fixation)
|
|
||||||
if (!isset($_SESSION['initiated'])) {
|
if (!isset($_SESSION['initiated'])) {
|
||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
$_SESSION['initiated'] = true;
|
$_SESSION['initiated'] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PHP Error Display in Produktion deaktivieren
|
|
||||||
if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
|
if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
|
||||||
ini_set('display_errors', 0);
|
ini_set('display_errors', 0);
|
||||||
ini_set('display_startup_errors', 0);
|
ini_set('display_startup_errors', 0);
|
||||||
@@ -29,18 +31,18 @@ if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
|
|||||||
ini_set('log_errors', 1);
|
ini_set('log_errors', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set page configuration and include header
|
|
||||||
*
|
|
||||||
* @param string $title The page title
|
|
||||||
* @param string $description The page description
|
|
||||||
* @param string $page The current page identifier
|
|
||||||
* @param array $scripts Additional scripts to include
|
|
||||||
*/
|
|
||||||
function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
|
function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
|
||||||
global $page_title, $page_description, $current_page, $additional_scripts;
|
global $page_title, $page_description, $current_page, $additional_scripts;
|
||||||
|
|
||||||
// Set page configuration from parameters
|
|
||||||
$page_title = !empty($title)
|
$page_title = !empty($title)
|
||||||
? $title
|
? $title
|
||||||
: 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
|
: 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
|
||||||
@@ -55,28 +57,28 @@ function includeHeader($title = '', $description = '', $page = '', $scripts = []
|
|||||||
include __DIR__ . '/header.php';
|
include __DIR__ . '/header.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Include footer
|
|
||||||
*/
|
|
||||||
function includeFooter() {
|
function includeFooter() {
|
||||||
include __DIR__ . '/footer.php';
|
include __DIR__ . '/footer.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate breadcrumb navigation
|
|
||||||
*
|
|
||||||
* @param array $breadcrumbs Array of breadcrumb items [['title' => 'Home', 'url' => 'index.html'], ...]
|
|
||||||
*/
|
|
||||||
function generateBreadcrumbs($breadcrumbs) {
|
function generateBreadcrumbs($breadcrumbs) {
|
||||||
echo '<div class="breadcrumb">';
|
echo '<div class="breadcrumb">';
|
||||||
$last_index = count($breadcrumbs) - 1;
|
$last_index = count($breadcrumbs) - 1;
|
||||||
|
|
||||||
foreach ($breadcrumbs as $index => $item) {
|
foreach ($breadcrumbs as $index => $item) {
|
||||||
if ($index === $last_index) {
|
if ($index === $last_index) {
|
||||||
// Last item (current page)
|
|
||||||
echo '<span>' . htmlspecialchars($item['title']) . '</span>';
|
echo '<span>' . htmlspecialchars($item['title']) . '</span>';
|
||||||
} else {
|
} else {
|
||||||
// Link to other pages
|
|
||||||
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||||
echo '<span>/</span>';
|
echo '<span>/</span>';
|
||||||
}
|
}
|
||||||
@@ -84,11 +86,11 @@ function generateBreadcrumbs($breadcrumbs) {
|
|||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate CSRF token for form security
|
|
||||||
*
|
|
||||||
* @return string CSRF token
|
|
||||||
*/
|
|
||||||
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));
|
||||||
@@ -96,9 +98,9 @@ function generateCSRFToken() {
|
|||||||
return $_SESSION['csrf_token'];
|
return $_SESSION['csrf_token'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* CSRF-Token prüfen und nach Erfolg invalidieren (Replay-Schutz)
|
|
||||||
*/
|
|
||||||
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;
|
||||||
@@ -110,16 +112,16 @@ function validateCSRFToken($token) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Werte für E-Mail-Header bereinigen (Header-Injection verhindern)
|
|
||||||
*/
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Client-IP für Logging (Cloudflare / vertrauenswürdiger Reverse-Proxy)
|
|
||||||
*/
|
|
||||||
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)) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<link rel="preconnect" href="https://cdn.hexahost.de" crossorigin>
|
<link rel="preconnect" href="https://cdn.hexahost.de" crossorigin>
|
||||||
|
|
||||||
<!-- Performance: Preload kritischer Ressourcen -->
|
<!-- 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>
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
<meta property="og:locale" content="de_DE">
|
<meta property="og:locale" content="de_DE">
|
||||||
|
|
||||||
<!-- Main Stylesheets -->
|
<!-- Main Stylesheets -->
|
||||||
<link rel="stylesheet" href="/assets/css/style.css">
|
<link rel="stylesheet" href="/assets/css/style.d01979e8c871.css">
|
||||||
<link rel="stylesheet" href="/assets/css/custom.css">
|
<link rel="stylesheet" href="/assets/css/custom.d35eb3499212.css">
|
||||||
|
|
||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Russo+One&family=Source+Sans+Pro:wght@300;400;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Russo+One&family=Source+Sans+Pro:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||||
@@ -59,12 +59,12 @@
|
|||||||
<ul class="nav-menu">
|
<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>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = '404 - Seite nicht gefunden | HexaHost.de';
|
$page_title = '404 - Seite nicht gefunden | HexaHost.de';
|
||||||
$page_description = 'Die angeforderte Seite wurde nicht gefunden.';
|
$page_description = 'Die angeforderte Seite wurde nicht gefunden.';
|
||||||
$current_page = '404';
|
$current_page = '404';
|
||||||
|
|
||||||
// Set 404 header
|
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = '500 - Serverfehler | HexaHost.de';
|
$page_title = '500 - Serverfehler | HexaHost.de';
|
||||||
$page_description = 'Ein interner Serverfehler ist aufgetreten.';
|
$page_description = 'Ein interner Serverfehler ist aufgetreten.';
|
||||||
$current_page = '500';
|
$current_page = '500';
|
||||||
|
|
||||||
// Set 500 header
|
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = 'Über mich - HexaHost.de | Hosting aus Niederbayern';
|
$page_title = 'Über mich - HexaHost.de | Hosting aus Niederbayern';
|
||||||
$page_description = 'Erfahren Sie mehr über HexaHost.de - Ihr zuverlässiger Hosting-Partner aus Niederbayern. Moderne Technologie mit persönlichem Service.';
|
$page_description = 'Erfahren Sie mehr über HexaHost.de - Ihr zuverlässiger Hosting-Partner aus Niederbayern. Moderne Technologie mit persönlichem Service.';
|
||||||
$current_page = 'about';
|
$current_page = 'about';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -245,6 +245,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = 'Allgemeine Geschäftsbedingungen - HexaHost.de | AGB';
|
$page_title = 'Allgemeine Geschäftsbedingungen - HexaHost.de | AGB';
|
||||||
$page_description = 'Allgemeine Geschäftsbedingungen (AGB) von HexaHost.de für Hosting-Dienstleistungen.';
|
$page_description = 'Allgemeine Geschäftsbedingungen (AGB) von HexaHost.de für Hosting-Dienstleistungen.';
|
||||||
$current_page = 'agb';
|
$current_page = 'agb';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -527,6 +527,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
.btn-tertiary {
|
|
||||||
color: var(--text-primary);
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-tertiary:hover {
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
color: var(--primary-color);
|
|
||||||
background: rgba(255, 81, 249, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.it-services-actions {
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Legal pages: plain white content with black text */
|
|
||||||
.legal-hero,
|
|
||||||
.legal-content {
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-hero {
|
|
||||||
margin-top: 70px;
|
|
||||||
padding: 2rem 0 1.5rem;
|
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-content {
|
|
||||||
padding-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-hero-title {
|
|
||||||
background: none;
|
|
||||||
-webkit-text-fill-color: #000000;
|
|
||||||
color: #000000;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-hero-description,
|
|
||||||
.legal-section h2,
|
|
||||||
.legal-section h3,
|
|
||||||
.legal-block p,
|
|
||||||
.legal-block li,
|
|
||||||
.breadcrumb,
|
|
||||||
.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,
|
|
||||||
.breadcrumb a {
|
|
||||||
color: #0b57d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-block a:hover,
|
|
||||||
.breadcrumb a:hover {
|
|
||||||
color: #0b57d0;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure absolutely no hover effects on legal text/content */
|
|
||||||
.legal-hero *,
|
|
||||||
.legal-content *,
|
|
||||||
.legal-hero *:hover,
|
|
||||||
.legal-content *:hover,
|
|
||||||
.legal-hero *:focus,
|
|
||||||
.legal-content *:focus,
|
|
||||||
.legal-hero *:active,
|
|
||||||
.legal-content *:active {
|
|
||||||
transform: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
text-shadow: none !important;
|
|
||||||
transition: none !important;
|
|
||||||
animation: none !important;
|
|
||||||
}
|
|
||||||
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
@@ -1,239 +0,0 @@
|
|||||||
(function () {
|
|
||||||
"use strict";
|
|
||||||
function initFaqAccordion() {
|
|
||||||
const faqItems = document.querySelectorAll(".faq-item");
|
|
||||||
faqItems.forEach(faqItem => {
|
|
||||||
const faqQuestion = faqItem.querySelector(".faq-question");
|
|
||||||
const faqAnswer = faqItem.querySelector(".faq-answer");
|
|
||||||
const faqToggle = faqItem.querySelector(".faq-toggle");
|
|
||||||
faqQuestion.addEventListener("click", function () {
|
|
||||||
const isOpen = faqItem.classList.contains("open");
|
|
||||||
faqItems.forEach(otherItem => {
|
|
||||||
if (otherItem !== faqItem) {
|
|
||||||
otherItem.classList.remove("open");
|
|
||||||
const otherAnswer = otherItem.querySelector(".faq-answer");
|
|
||||||
const otherToggle = otherItem.querySelector(".faq-toggle");
|
|
||||||
otherAnswer.style.maxHeight = null;
|
|
||||||
otherToggle.textContent = "+";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (isOpen) {
|
|
||||||
faqItem.classList.remove("open");
|
|
||||||
faqAnswer.style.maxHeight = null;
|
|
||||||
faqToggle.textContent = "+";
|
|
||||||
} else {
|
|
||||||
faqItem.classList.add("open");
|
|
||||||
faqAnswer.style.maxHeight = faqAnswer.scrollHeight + "px";
|
|
||||||
faqToggle.textContent = "−";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function initContactForm() {
|
|
||||||
const contactForm = document.getElementById("contactForm");
|
|
||||||
if (!contactForm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
contactForm.addEventListener("submit", function (submitEvent) {
|
|
||||||
submitEvent.preventDefault();
|
|
||||||
const formData = new FormData(contactForm);
|
|
||||||
const payload = {};
|
|
||||||
for (let [key, value] of formData.entries()) {
|
|
||||||
payload[key] = value;
|
|
||||||
}
|
|
||||||
if (!validateFormData(payload)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const submitButton = contactForm.querySelector("button[type=\"submit\"]");
|
|
||||||
const originalButtonText = submitButton.textContent;
|
|
||||||
submitButton.textContent = "Wird gesendet...";
|
|
||||||
submitButton.disabled = true;
|
|
||||||
const requestOptions = {
|
|
||||||
method: "POST",
|
|
||||||
body: formData
|
|
||||||
};
|
|
||||||
fetch("contact-handler.php", requestOptions).then(response => response.json()).then(result => {
|
|
||||||
submitButton.textContent = originalButtonText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
if (result.success) {
|
|
||||||
contactForm.reset();
|
|
||||||
showNotification(result.message, "success");
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: "smooth"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showNotification(result.message, "error");
|
|
||||||
if (result.missing_fields) {
|
|
||||||
result.missing_fields.forEach(missingFieldId => {
|
|
||||||
const missingField = document.getElementById(missingFieldId);
|
|
||||||
if (missingField) {
|
|
||||||
missingField.style.borderColor = "#ff4d6d";
|
|
||||||
setTimeout(() => {
|
|
||||||
missingField.style.borderColor = "";
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.error("Error:", error);
|
|
||||||
showNotification("Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.", "error");
|
|
||||||
submitButton.textContent = originalButtonText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function validateFormData(formValues) {
|
|
||||||
const requiredKeys = ["firstName", "lastName", "email", "subject", "message"];
|
|
||||||
const errors = [];
|
|
||||||
requiredKeys.forEach(fieldName => {
|
|
||||||
if (!formValues[fieldName] || formValues[fieldName].trim() === "") {
|
|
||||||
errors.push("Das Feld \"" + getFieldLabel(fieldName) + "\" ist erforderlich.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (formValues.email && !isValidEmail(formValues.email)) {
|
|
||||||
errors.push("Bitte geben Sie eine gültige E-Mail-Adresse ein.");
|
|
||||||
}
|
|
||||||
if (!formValues.privacy) {
|
|
||||||
errors.push("Sie müssen der Datenschutzerklärung zustimmen.");
|
|
||||||
}
|
|
||||||
if (errors.length > 0) {
|
|
||||||
showNotification(errors.join("\n"), "error");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
function getFieldLabel(keyName) {
|
|
||||||
const fieldLabels = {
|
|
||||||
firstName: "Vorname",
|
|
||||||
lastName: "Nachname",
|
|
||||||
email: "E-Mail-Adresse",
|
|
||||||
subject: "Betreff",
|
|
||||||
message: "Nachricht"
|
|
||||||
};
|
|
||||||
return fieldLabels[keyName] || keyName;
|
|
||||||
}
|
|
||||||
function isValidEmail(email) {
|
|
||||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
return emailPattern.test(email);
|
|
||||||
}
|
|
||||||
function showNotification(message, type = "info") {
|
|
||||||
if (window.HexaHost && window.HexaHost.showNotification) {
|
|
||||||
window.HexaHost.showNotification(message, type);
|
|
||||||
} else {
|
|
||||||
alert(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.openLiveChat = function () {
|
|
||||||
showNotification("Live Chat wird geöffnet... (Demo-Funktion)", "info");
|
|
||||||
};
|
|
||||||
function prefillFromQueryParams() {
|
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
|
||||||
const packageParam = queryParams.get("package");
|
|
||||||
const productParam = queryParams.get("product");
|
|
||||||
if (packageParam || productParam) {
|
|
||||||
const subjectField = document.getElementById("subject");
|
|
||||||
const messageField = document.getElementById("message");
|
|
||||||
if (packageParam) {
|
|
||||||
const packageLabels = {
|
|
||||||
"vpc-starter": "Virtual Private Container - Starter Paket",
|
|
||||||
"vpc-business": "Virtual Private Container - Business Paket",
|
|
||||||
"vpc-professional": "Virtual Private Container - Professional Paket",
|
|
||||||
"vpc-enterprise": "Virtual Private Container - Enterprise Paket",
|
|
||||||
"vps-basic": "Virtual Private Server - Basic Paket",
|
|
||||||
"vps-standard": "Virtual Private Server - Standard Paket",
|
|
||||||
"vps-premium": "Virtual Private Server - Premium Paket",
|
|
||||||
"vps-enterprise": "Virtual Private Server - Enterprise Paket",
|
|
||||||
"mail-starter": "Mail Gateway - Starter Paket",
|
|
||||||
"mail-business": "Mail Gateway - Business Paket",
|
|
||||||
"mail-professional": "Mail Gateway - Professional Paket",
|
|
||||||
"mail-enterprise": "Mail Gateway - Enterprise Paket",
|
|
||||||
"web-starter": "Webhosting - Starter Paket",
|
|
||||||
"web-business": "Webhosting - Business Paket",
|
|
||||||
"web-professional": "Webhosting - Professional Paket",
|
|
||||||
"web-enterprise": "Webhosting - Enterprise Paket"
|
|
||||||
};
|
|
||||||
if (packageLabels[packageParam]) {
|
|
||||||
messageField.value = "Hallo,\n\nich interessiere mich für das " + packageLabels[packageParam] + ".\n\nBitte senden Sie mir weitere Informationen und ein individuelles Angebot.\n\nVielen Dank!";
|
|
||||||
if (packageParam.startsWith("vpc-")) {
|
|
||||||
subjectField.value = "vpc-anfrage";
|
|
||||||
} else if (packageParam.startsWith("vps-")) {
|
|
||||||
subjectField.value = "vps-anfrage";
|
|
||||||
} else if (packageParam.startsWith("mail-")) {
|
|
||||||
subjectField.value = "mail-gateway-anfrage";
|
|
||||||
} else if (packageParam.startsWith("web-")) {
|
|
||||||
subjectField.value = "webhosting-anfrage";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (productParam) {
|
|
||||||
const productSubjects = {
|
|
||||||
vpc: "vpc-anfrage",
|
|
||||||
vps: "vps-anfrage",
|
|
||||||
"mail-gateway": "mail-gateway-anfrage",
|
|
||||||
webhosting: "webhosting-anfrage"
|
|
||||||
};
|
|
||||||
if (productSubjects[productParam]) {
|
|
||||||
subjectField.value = productSubjects[productParam];
|
|
||||||
messageField.value = "Hallo,\n\nich interessiere mich für Ihre " + productParam.replace("-", " ") + " Lösungen.\n\nBitte kontaktieren Sie mich für eine persönliche Beratung.\n\nVielen Dank!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function initFieldUiEnhancements() {
|
|
||||||
const inputElements = document.querySelectorAll("input, select, textarea");
|
|
||||||
inputElements.forEach(inputEl => {
|
|
||||||
inputEl.addEventListener("focus", function () {
|
|
||||||
this.parentElement.classList.add("focused");
|
|
||||||
});
|
|
||||||
inputEl.addEventListener("blur", function () {
|
|
||||||
if (!this.value) {
|
|
||||||
this.parentElement.classList.remove("focused");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (inputEl.value) {
|
|
||||||
inputEl.parentElement.classList.add("focused");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const phoneInput = document.getElementById("phone");
|
|
||||||
if (phoneInput) {
|
|
||||||
phoneInput.addEventListener("input", function () {
|
|
||||||
let digitsOnly = this.value.replace(/\D/g, "");
|
|
||||||
if (digitsOnly.startsWith("49")) {
|
|
||||||
digitsOnly = "+" + digitsOnly;
|
|
||||||
} else if (digitsOnly.startsWith("0")) {
|
|
||||||
digitsOnly = "+49" + digitsOnly.substring(1);
|
|
||||||
}
|
|
||||||
this.value = digitsOnly;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function initAccessibility() {
|
|
||||||
const requiredInputs = document.querySelectorAll("input[required], select[required], textarea[required]");
|
|
||||||
requiredInputs.forEach(requiredInput => {
|
|
||||||
requiredInput.setAttribute("aria-required", "true");
|
|
||||||
});
|
|
||||||
const faqQuestions = document.querySelectorAll(".faq-question");
|
|
||||||
faqQuestions.forEach(questionEl => {
|
|
||||||
questionEl.setAttribute("tabindex", "0");
|
|
||||||
questionEl.setAttribute("role", "button");
|
|
||||||
questionEl.setAttribute("aria-expanded", "false");
|
|
||||||
questionEl.addEventListener("keydown", function (keyboardEvent) {
|
|
||||||
if (keyboardEvent.key === "Enter" || keyboardEvent.key === " ") {
|
|
||||||
keyboardEvent.preventDefault();
|
|
||||||
this.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
initFaqAccordion();
|
|
||||||
initContactForm();
|
|
||||||
prefillFromQueryParams();
|
|
||||||
initFieldUiEnhancements();
|
|
||||||
initAccessibility();
|
|
||||||
setTimeout(() => {
|
|
||||||
showNotification("💬 Haben Sie Fragen? Wir helfen gerne!", "info");
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
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
@@ -1,210 +0,0 @@
|
|||||||
(function () {
|
|
||||||
"use strict";
|
|
||||||
const CONSENT_COOKIE_NAME = "hexahost_cookie_consent";
|
|
||||||
const CONSENT_DAYS = 365;
|
|
||||||
const cookieBanner = document.getElementById("cookieConsent");
|
|
||||||
const settingsPanel = document.getElementById("cookieSettingsPanel");
|
|
||||||
const acceptAllButton = document.getElementById("cookieAcceptAll");
|
|
||||||
const acceptEssentialButton = document.getElementById("cookieAcceptEssential");
|
|
||||||
const settingsButton = document.getElementById("cookieSettings");
|
|
||||||
const saveSettingsButton = document.getElementById("cookieSaveSettings");
|
|
||||||
const closeSettingsButton = document.getElementById("cookieCloseSettings");
|
|
||||||
const analyticsCheckbox = document.getElementById("cookieAnalytics");
|
|
||||||
const marketingCheckbox = document.getElementById("cookieMarketing");
|
|
||||||
const cookieStore = {
|
|
||||||
set: function (name, value, days) {
|
|
||||||
const expiresDate = new Date();
|
|
||||||
expiresDate.setTime(expiresDate.getTime() + days * 24 * 60 * 60 * 1000);
|
|
||||||
const expiresString = "expires=" + expiresDate.toUTCString();
|
|
||||||
document.cookie = name + "=" + JSON.stringify(value) + ";" + expiresString + ";path=/;SameSite=Lax;Secure";
|
|
||||||
},
|
|
||||||
get: function (name) {
|
|
||||||
const prefix = name + "=";
|
|
||||||
const cookies = document.cookie.split(";");
|
|
||||||
for (let index = 0; index < cookies.length; index++) {
|
|
||||||
let cookie = cookies[index].trim();
|
|
||||||
if (cookie.indexOf(prefix) === 0) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(cookie.substring(prefix.length));
|
|
||||||
} catch (parseError) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
delete: function (name) {
|
|
||||||
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const cookieConsentManager = {
|
|
||||||
defaultConsent: {
|
|
||||||
essential: true,
|
|
||||||
analytics: false,
|
|
||||||
marketing: false,
|
|
||||||
timestamp: null
|
|
||||||
},
|
|
||||||
init: function () {
|
|
||||||
if (!cookieBanner) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const storedConsent = this.getConsent();
|
|
||||||
if (storedConsent && storedConsent.timestamp) {
|
|
||||||
this.hideBanner();
|
|
||||||
this.applyConsent(storedConsent);
|
|
||||||
} else {
|
|
||||||
this.showBanner();
|
|
||||||
}
|
|
||||||
this.bindEvents();
|
|
||||||
},
|
|
||||||
bindEvents: function () {
|
|
||||||
if (acceptAllButton) {
|
|
||||||
acceptAllButton.addEventListener("click", () => this.acceptAll());
|
|
||||||
}
|
|
||||||
if (acceptEssentialButton) {
|
|
||||||
acceptEssentialButton.addEventListener("click", () => this.acceptEssential());
|
|
||||||
}
|
|
||||||
if (settingsButton) {
|
|
||||||
settingsButton.addEventListener("click", () => this.showSettings());
|
|
||||||
}
|
|
||||||
if (saveSettingsButton) {
|
|
||||||
saveSettingsButton.addEventListener("click", () => this.saveSettings());
|
|
||||||
}
|
|
||||||
if (closeSettingsButton) {
|
|
||||||
closeSettingsButton.addEventListener("click", () => this.hideSettings());
|
|
||||||
}
|
|
||||||
document.addEventListener("keydown", event => {
|
|
||||||
if (event.key === "Escape" && settingsPanel && settingsPanel.style.display !== "none") {
|
|
||||||
this.hideSettings();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
acceptAll: function () {
|
|
||||||
const allConsent = {
|
|
||||||
essential: true,
|
|
||||||
analytics: true,
|
|
||||||
marketing: true,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
};
|
|
||||||
this.saveConsent(allConsent);
|
|
||||||
this.hideBanner();
|
|
||||||
this.applyConsent(allConsent);
|
|
||||||
this.showNotification("Alle Cookies wurden akzeptiert.", "success");
|
|
||||||
},
|
|
||||||
acceptEssential: function () {
|
|
||||||
const essentialConsent = {
|
|
||||||
essential: true,
|
|
||||||
analytics: false,
|
|
||||||
marketing: false,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
};
|
|
||||||
this.saveConsent(essentialConsent);
|
|
||||||
this.hideBanner();
|
|
||||||
this.applyConsent(essentialConsent);
|
|
||||||
this.showNotification("Nur notwendige Cookies wurden akzeptiert.", "info");
|
|
||||||
},
|
|
||||||
saveSettings: function () {
|
|
||||||
const customConsent = {
|
|
||||||
essential: true,
|
|
||||||
analytics: analyticsCheckbox ? analyticsCheckbox.checked : false,
|
|
||||||
marketing: marketingCheckbox ? marketingCheckbox.checked : false,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
};
|
|
||||||
this.saveConsent(customConsent);
|
|
||||||
this.hideSettings();
|
|
||||||
this.hideBanner();
|
|
||||||
this.applyConsent(customConsent);
|
|
||||||
this.showNotification("Cookie-Einstellungen wurden gespeichert.", "success");
|
|
||||||
},
|
|
||||||
saveConsent: function (consent) {
|
|
||||||
cookieStore.set(CONSENT_COOKIE_NAME, consent, CONSENT_DAYS);
|
|
||||||
},
|
|
||||||
getConsent: function () {
|
|
||||||
return cookieStore.get(CONSENT_COOKIE_NAME);
|
|
||||||
},
|
|
||||||
applyConsent: function (consent) {
|
|
||||||
const eventPayload = {
|
|
||||||
detail: consent
|
|
||||||
};
|
|
||||||
window.dispatchEvent(new CustomEvent("cookieConsentUpdated", eventPayload));
|
|
||||||
if (consent.analytics) {
|
|
||||||
this.enableAnalytics();
|
|
||||||
} else {
|
|
||||||
this.disableAnalytics();
|
|
||||||
}
|
|
||||||
if (consent.marketing) {
|
|
||||||
this.enableMarketing();
|
|
||||||
} else {
|
|
||||||
this.disableMarketing();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enableAnalytics: function () {
|
|
||||||
console.log("Analytics enabled");
|
|
||||||
},
|
|
||||||
disableAnalytics: function () {
|
|
||||||
console.log("Analytics disabled");
|
|
||||||
},
|
|
||||||
enableMarketing: function () {
|
|
||||||
console.log("Marketing enabled");
|
|
||||||
},
|
|
||||||
disableMarketing: function () {
|
|
||||||
console.log("Marketing disabled");
|
|
||||||
},
|
|
||||||
showBanner: function () {
|
|
||||||
if (cookieBanner) {
|
|
||||||
cookieBanner.classList.remove("hide");
|
|
||||||
cookieBanner.classList.add("show");
|
|
||||||
cookieBanner.setAttribute("aria-hidden", "false");
|
|
||||||
setTimeout(() => {
|
|
||||||
if (acceptAllButton) {
|
|
||||||
acceptAllButton.focus();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hideBanner: function () {
|
|
||||||
if (cookieBanner) {
|
|
||||||
cookieBanner.classList.remove("show");
|
|
||||||
cookieBanner.classList.add("hide");
|
|
||||||
cookieBanner.setAttribute("aria-hidden", "true");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showSettings: function () {
|
|
||||||
if (settingsPanel) {
|
|
||||||
const savedConsent = this.getConsent() || this.defaultConsent;
|
|
||||||
if (analyticsCheckbox) {
|
|
||||||
analyticsCheckbox.checked = savedConsent.analytics;
|
|
||||||
}
|
|
||||||
if (marketingCheckbox) {
|
|
||||||
marketingCheckbox.checked = savedConsent.marketing;
|
|
||||||
}
|
|
||||||
settingsPanel.style.display = "block";
|
|
||||||
settingsPanel.setAttribute("aria-hidden", "false");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hideSettings: function () {
|
|
||||||
if (settingsPanel) {
|
|
||||||
settingsPanel.style.display = "none";
|
|
||||||
settingsPanel.setAttribute("aria-hidden", "true");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showNotification: function (message, type) {
|
|
||||||
if (window.HexaHost && typeof window.HexaHost.showNotification === "function") {
|
|
||||||
window.HexaHost.showNotification(message, type);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetConsent: function () {
|
|
||||||
cookieStore.delete(CONSENT_COOKIE_NAME);
|
|
||||||
this.showBanner();
|
|
||||||
if (settingsPanel) {
|
|
||||||
settingsPanel.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (document.readyState === "loading") {
|
|
||||||
document.addEventListener("DOMContentLoaded", () => cookieConsentManager.init());
|
|
||||||
} else {
|
|
||||||
cookieConsentManager.init();
|
|
||||||
}
|
|
||||||
window.CookieConsent = cookieConsentManager;
|
|
||||||
})();
|
|
||||||
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
@@ -1,235 +0,0 @@
|
|||||||
(function () {
|
|
||||||
"use strict";
|
|
||||||
const navToggle = document.querySelector(".nav-toggle");
|
|
||||||
const navMenu = document.querySelector(".nav-menu");
|
|
||||||
const navLinks = document.querySelectorAll(".nav-link");
|
|
||||||
const glassCards = document.querySelectorAll(".glass-card");
|
|
||||||
const productCards = document.querySelectorAll(".product-card");
|
|
||||||
if (navToggle && navMenu) {
|
|
||||||
navToggle.addEventListener("click", function () {
|
|
||||||
navMenu.classList.toggle("active");
|
|
||||||
navToggle.classList.toggle("active");
|
|
||||||
});
|
|
||||||
navLinks.forEach(navLink => {
|
|
||||||
navLink.addEventListener("click", function () {
|
|
||||||
navMenu.classList.remove("active");
|
|
||||||
navToggle.classList.remove("active");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
document.querySelectorAll("a[href^=\"#\"]").forEach(anchorLink => {
|
|
||||||
anchorLink.addEventListener("click", function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const targetSection = document.querySelector(this.getAttribute("href"));
|
|
||||||
if (targetSection) {
|
|
||||||
targetSection.scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
block: "start"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
glassCards.forEach(card => {
|
|
||||||
card.addEventListener("mouseenter", function () {
|
|
||||||
this.style.transform = "translateY(-8px) scale(1.02)";
|
|
||||||
});
|
|
||||||
card.addEventListener("mouseleave", function () {
|
|
||||||
this.style.transform = "translateY(0) scale(1)";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
productCards.forEach(productCard => {
|
|
||||||
productCard.addEventListener("mouseenter", function () {
|
|
||||||
if (!this.classList.contains("featured")) {
|
|
||||||
this.style.transform = "translateY(-10px) scale(1.03)";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
productCard.addEventListener("mouseleave", function () {
|
|
||||||
if (!this.classList.contains("featured")) {
|
|
||||||
this.style.transform = "translateY(0) scale(1)";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const observerOptions = {
|
|
||||||
threshold: 0.1,
|
|
||||||
rootMargin: "0px 0px -50px 0px"
|
|
||||||
};
|
|
||||||
const animationObserver = new IntersectionObserver(function (entries) {
|
|
||||||
entries.forEach(entry => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
entry.target.classList.add("animate-in");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, observerOptions);
|
|
||||||
const animatedElements = document.querySelectorAll(".glass-card, .feature-item, .product-card");
|
|
||||||
animatedElements.forEach(element => {
|
|
||||||
animationObserver.observe(element);
|
|
||||||
});
|
|
||||||
const headerElement = document.querySelector(".header");
|
|
||||||
const heroSection = document.querySelector(".hero");
|
|
||||||
let isScrollTicking = false;
|
|
||||||
function updateOnScroll() {
|
|
||||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
||||||
if (headerElement) {
|
|
||||||
if (scrollTop > 50) {
|
|
||||||
headerElement.classList.add("scrolled");
|
|
||||||
} else {
|
|
||||||
headerElement.classList.remove("scrolled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (heroSection) {
|
|
||||||
const parallaxOffset = scrollTop * -0.5;
|
|
||||||
heroSection.style.transform = "translateY(" + parallaxOffset + "px)";
|
|
||||||
}
|
|
||||||
isScrollTicking = false;
|
|
||||||
}
|
|
||||||
window.addEventListener("scroll", function () {
|
|
||||||
if (!isScrollTicking) {
|
|
||||||
requestAnimationFrame(updateOnScroll);
|
|
||||||
isScrollTicking = true;
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
passive: true
|
|
||||||
});
|
|
||||||
const forms = document.querySelectorAll("form");
|
|
||||||
forms.forEach(form => {
|
|
||||||
form.addEventListener("submit", function (submitEvent) {
|
|
||||||
const requiredFields = form.querySelectorAll("[required]");
|
|
||||||
let isValid = true;
|
|
||||||
requiredFields.forEach(field => {
|
|
||||||
if (!field.value.trim()) {
|
|
||||||
isValid = false;
|
|
||||||
field.classList.add("error");
|
|
||||||
field.addEventListener("focus", function () {
|
|
||||||
this.classList.remove("error");
|
|
||||||
}, {
|
|
||||||
once: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!isValid) {
|
|
||||||
submitEvent.preventDefault();
|
|
||||||
showNotification("Bitte füllen Sie alle Pflichtfelder aus.", "error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
function showNotification(message, type = "info") {
|
|
||||||
const notificationEl = document.createElement("div");
|
|
||||||
notificationEl.className = "notification notification-" + type;
|
|
||||||
notificationEl.textContent = message;
|
|
||||||
notificationEl.style.position = "fixed";
|
|
||||||
notificationEl.style.top = "20px";
|
|
||||||
notificationEl.style.right = "20px";
|
|
||||||
notificationEl.style.padding = "15px 20px";
|
|
||||||
notificationEl.style.borderRadius = "8px";
|
|
||||||
notificationEl.style.color = "white";
|
|
||||||
notificationEl.style.fontWeight = "500";
|
|
||||||
notificationEl.style.zIndex = "9999";
|
|
||||||
notificationEl.style.transform = "translateX(400px)";
|
|
||||||
notificationEl.style.transition = "transform 0.3s ease-in-out";
|
|
||||||
if (type === "error") {
|
|
||||||
notificationEl.style.background = "linear-gradient(135deg, #ef4444, #dc2626)";
|
|
||||||
} else if (type === "success") {
|
|
||||||
notificationEl.style.background = "linear-gradient(135deg, #10b981, #059669)";
|
|
||||||
} else {
|
|
||||||
notificationEl.style.background = "linear-gradient(135deg, #3b82f6, #2563eb)";
|
|
||||||
}
|
|
||||||
document.body.appendChild(notificationEl);
|
|
||||||
setTimeout(() => {
|
|
||||||
notificationEl.style.transform = "translateX(0)";
|
|
||||||
}, 100);
|
|
||||||
setTimeout(() => {
|
|
||||||
notificationEl.style.transform = "translateX(400px)";
|
|
||||||
setTimeout(() => {
|
|
||||||
if (notificationEl.parentNode) {
|
|
||||||
notificationEl.parentNode.removeChild(notificationEl);
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
const lazyImages = document.querySelectorAll("img[data-src]");
|
|
||||||
const lazyImageObserver = new IntersectionObserver(imageEntries => {
|
|
||||||
imageEntries.forEach(imageEntry => {
|
|
||||||
if (imageEntry.isIntersecting) {
|
|
||||||
const image = imageEntry.target;
|
|
||||||
image.src = image.dataset.src;
|
|
||||||
image.classList.remove("lazy");
|
|
||||||
lazyImageObserver.unobserve(image);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
lazyImages.forEach(lazyImage => lazyImageObserver.observe(lazyImage));
|
|
||||||
function debounce(callback, delay) {
|
|
||||||
let timeoutId;
|
|
||||||
return function debouncedFunction(...args) {
|
|
||||||
const runLater = () => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
callback(...args);
|
|
||||||
};
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
timeoutId = setTimeout(runLater, delay);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const debouncedScrollProgress = debounce(function () {
|
|
||||||
updateScrollProgress();
|
|
||||||
}, 16);
|
|
||||||
window.addEventListener("scroll", debouncedScrollProgress);
|
|
||||||
function updateScrollProgress() {
|
|
||||||
const pageYOffset = window.pageYOffset;
|
|
||||||
const scrollableHeight = document.body.scrollHeight - window.innerHeight;
|
|
||||||
const progressPercent = pageYOffset / scrollableHeight * 100;
|
|
||||||
document.documentElement.style.setProperty("--scroll-progress", progressPercent + "%");
|
|
||||||
}
|
|
||||||
function initDarkMode() {
|
|
||||||
const darkModeToggle = document.querySelector(".dark-mode-toggle");
|
|
||||||
if (darkModeToggle) {
|
|
||||||
darkModeToggle.addEventListener("click", function () {
|
|
||||||
document.body.classList.toggle("dark-mode");
|
|
||||||
localStorage.setItem("darkMode", document.body.classList.contains("dark-mode"));
|
|
||||||
});
|
|
||||||
if (localStorage.getItem("darkMode") === "true") {
|
|
||||||
document.body.classList.add("dark-mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function initFaqAccordion() {
|
|
||||||
const faqItems = document.querySelectorAll(".faq-item");
|
|
||||||
faqItems.forEach(faqItem => {
|
|
||||||
const faqQuestion = faqItem.querySelector(".faq-question");
|
|
||||||
const faqAnswer = faqItem.querySelector(".faq-answer");
|
|
||||||
if (faqQuestion && faqAnswer) {
|
|
||||||
faqQuestion.addEventListener("click", function () {
|
|
||||||
faqItems.forEach(otherFaqItem => {
|
|
||||||
if (otherFaqItem !== faqItem && otherFaqItem.classList.contains("open")) {
|
|
||||||
otherFaqItem.classList.remove("open");
|
|
||||||
const otherFaqAnswer = otherFaqItem.querySelector(".faq-answer");
|
|
||||||
if (otherFaqAnswer) {
|
|
||||||
otherFaqAnswer.style.maxHeight = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
faqItem.classList.toggle("open");
|
|
||||||
if (faqItem.classList.contains("open")) {
|
|
||||||
faqAnswer.style.maxHeight = faqAnswer.scrollHeight + "px";
|
|
||||||
} else {
|
|
||||||
faqAnswer.style.maxHeight = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
initDarkMode();
|
|
||||||
initFaqAccordion();
|
|
||||||
document.body.classList.add("loaded");
|
|
||||||
if (!localStorage.getItem("hasVisited")) {
|
|
||||||
setTimeout(() => {
|
|
||||||
showNotification("Willkommen bei HexaHost.de! 🚀", "success");
|
|
||||||
localStorage.setItem("hasVisited", "true");
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const hexaHostApi = {
|
|
||||||
showNotification: showNotification
|
|
||||||
};
|
|
||||||
window.HexaHost = hexaHostApi;
|
|
||||||
})();
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* Kompatibilitäts-Wrapper – leitet auf die zentrale Backend-Konfiguration um.
|
|
||||||
*/
|
|
||||||
require_once __DIR__ . '/../../backend/config/mail-config.php';
|
require_once __DIR__ . '/../../backend/config/mail-config.php';
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ require_once __DIR__ . '/../backend/config/contact-config.php';
|
|||||||
|
|
||||||
$preselected_subject = getPreselectedContactSubject();
|
$preselected_subject = getPreselectedContactSubject();
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$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'];
|
||||||
|
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page, $additional_scripts);
|
includeHeader($page_title, $page_description, $current_page, $additional_scripts);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -252,6 +252,6 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = 'Datenschutzerklärung - HexaHost.de | Datenschutz';
|
$page_title = 'Datenschutzerklärung - HexaHost.de | Datenschutz';
|
||||||
$page_description = 'Datenschutzerklärung von HexaHost.de - Informationen zum Schutz Ihrer personenbezogenen Daten.';
|
$page_description = 'Datenschutzerklärung von HexaHost.de - Informationen zum Schutz Ihrer personenbezogenen Daten.';
|
||||||
$current_page = 'datenschutz';
|
$current_page = 'datenschutz';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -344,6 +344,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = 'Impressum - HexaHost.de | Rechtliche Angaben';
|
$page_title = 'Impressum - HexaHost.de | Rechtliche Angaben';
|
||||||
$page_description = 'Impressum und rechtliche Angaben von HexaHost.de - Hosting aus Niederbayern.';
|
$page_description = 'Impressum und rechtliche Angaben von HexaHost.de - Hosting aus Niederbayern.';
|
||||||
$current_page = 'impressum';
|
$current_page = 'impressum';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
<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>
|
||||||
@@ -180,6 +180,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
|
$page_title = 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
|
||||||
$page_description = 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.';
|
$page_description = 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.';
|
||||||
$current_page = 'home';
|
$current_page = 'home';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -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"/>
|
||||||
@@ -265,6 +265,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = 'IT-Dienstleistungen - HexaHost.de | Privat & Gewerblich';
|
$page_title = 'IT-Dienstleistungen - HexaHost.de | Privat & Gewerblich';
|
||||||
$page_description = 'IT-Dienstleistungen von HexaHost.de mit Fokus auf Privatkunden und ergänzend für gewerbliche Anforderungen.';
|
$page_description = 'IT-Dienstleistungen von HexaHost.de mit Fokus auf Privatkunden und ergänzend für gewerbliche Anforderungen.';
|
||||||
$current_page = 'it-dienstleistungen';
|
$current_page = 'it-dienstleistungen';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -143,6 +143,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||||
|
|
||||||
// Produkt-Daten aus Config laden
|
|
||||||
$product = getProduct('mail-gateway');
|
$product = getProduct('mail-gateway');
|
||||||
$packages = getProductPackages('mail-gateway');
|
$packages = getProductPackages('mail-gateway');
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = $product['page_title'];
|
$page_title = $product['page_title'];
|
||||||
$page_description = $product['page_description'];
|
$page_description = $product['page_description'];
|
||||||
$current_page = 'mail-gateway';
|
$current_page = 'mail-gateway';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -175,6 +175,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -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,16 +2,16 @@
|
|||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||||
|
|
||||||
// Produkt-Daten aus Config laden
|
|
||||||
$product = getProduct('vpc');
|
$product = getProduct('vpc');
|
||||||
$packages = getProductPackages('vpc');
|
$packages = getProductPackages('vpc');
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = $product['page_title'];
|
$page_title = $product['page_title'];
|
||||||
$page_description = $product['page_description'];
|
$page_description = $product['page_description'];
|
||||||
$current_page = 'vpc';
|
$current_page = 'vpc';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -175,6 +175,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||||
|
|
||||||
// Produkt-Daten aus Config laden
|
|
||||||
$product = getProduct('vps');
|
$product = getProduct('vps');
|
||||||
$packages = getProductPackages('vps');
|
$packages = getProductPackages('vps');
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = $product['page_title'];
|
$page_title = $product['page_title'];
|
||||||
$page_description = $product['page_description'];
|
$page_description = $product['page_description'];
|
||||||
$current_page = 'vps';
|
$current_page = 'vps';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -180,6 +180,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
require_once __DIR__ . '/../backend/config/products-config.php';
|
require_once __DIR__ . '/../backend/config/products-config.php';
|
||||||
|
|
||||||
// Produkt-Daten aus Config laden
|
|
||||||
$product = getProduct('webhosting');
|
$product = getProduct('webhosting');
|
||||||
$packages = getProductPackages('webhosting');
|
$packages = getProductPackages('webhosting');
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = $product['page_title'];
|
$page_title = $product['page_title'];
|
||||||
$page_description = $product['page_description'];
|
$page_description = $product['page_description'];
|
||||||
$current_page = 'webhosting';
|
$current_page = 'webhosting';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -179,6 +179,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../backend/includes/functions.php';
|
require_once __DIR__ . '/../backend/includes/functions.php';
|
||||||
|
|
||||||
// Page configuration
|
|
||||||
$page_title = 'Widerrufsbelehrung - HexaHost.de';
|
$page_title = 'Widerrufsbelehrung - HexaHost.de';
|
||||||
$page_description = 'Widerrufsbelehrung und Muster-Widerrufsformular von HexaHost Inh. Samuel Müller.';
|
$page_description = 'Widerrufsbelehrung und Muster-Widerrufsformular von HexaHost Inh. Samuel Müller.';
|
||||||
$current_page = 'widerruf';
|
$current_page = 'widerruf';
|
||||||
|
|
||||||
// Include header
|
|
||||||
includeHeader($page_title, $page_description, $current_page);
|
includeHeader($page_title, $page_description, $current_page);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -131,6 +131,6 @@ includeHeader($page_title, $page_description, $current_page);
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Include footer
|
|
||||||
includeFooter();
|
includeFooter();
|
||||||
?>
|
?>
|
||||||
|
|||||||
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