Compare commits

..

31 Commits

Author SHA1 Message Date
gitea-actions
3d54cea5ec chore(release): obfuscate and hash production assets [skip ci] 2026-06-01 06:50:07 +00:00
gitea-actions
fdd0367281 chore(release): obfuscate and hash production assets [skip ci] 2026-06-01 06:38:01 +00:00
gitea-actions
ded8778b6c chore(release): obfuscate and hash production assets [skip ci] 2026-05-29 09:04:26 +00:00
gitea-actions
7a03f5aa1b chore(release): obfuscate and hash production assets [skip ci] 2026-05-29 08:43:42 +00:00
gitea-actions
f7ea36f4f2 chore(release): obfuscate and hash production assets [skip ci] 2026-05-28 15:42:41 +00:00
gitea-actions
99f0056106 ci: merge dev for release build 2026-05-28 15:42:22 +00:00
smueller
e9d5b55459 Refactor release build process: Updated README.md to clarify branch usage and workflow for development and production. Enhanced Gitea and GitHub workflows to automate merging from dev to main, ensuring consistent obfuscation and asset management during releases. Improved commit messages and streamlined the build process for better clarity and efficiency.
All checks were successful
Release Build (dev → main) / release-build (push) Successful in 29s
2026-05-28 17:42:06 +02:00
smueller
8f985da61f Enhance obfuscation workflow and asset processing: Updated the Gitea workflow to skip CI for specific commit messages, improving efficiency. Refactored the obfuscation script to include better asset handling, validation, and cleanup processes, ensuring only valid files are processed. Introduced temporary directories for intermediate files during obfuscation, enhancing reliability and reducing errors. 2026-05-28 17:32:21 +02:00
gitea-actions
e91a9ed9c3 chore(release): obfuscate and hash production assets [skip ci] 2026-05-28 15:28:24 +00:00
smueller
76aceddcca fix(release): restore clean assets before obfuscation rebuild
All checks were successful
Obfuscate Main Build / obfuscate (push) Successful in 25s
2026-05-28 17:27:51 +02:00
smueller
d7851763f7 refactor(obfuscate_release): improve source file selection and cleanup logic, enhancing error handling for asset processing
Some checks failed
Obfuscate Main Build / obfuscate (push) Failing after 13s
2026-05-28 17:24:05 +02:00
smueller
1c0a3ff468 refactor(obfuscate_release): enhance asset processing and validation logic for JS and CSS files
Some checks failed
Obfuscate Main Build / obfuscate (push) Failing after 12s
2026-05-28 17:20:50 +02:00
smueller
6c9114e0a7 Update obfuscate workflow: Introduced environment variables for Gitea host and repository path, streamlined commit process to skip CI for bot commits, and adjusted remote URL configuration for secure pushes. Enhanced build process by removing unnecessary steps and ensuring efficient asset obfuscation and hashing. 2026-05-28 17:13:16 +02:00
gitea-actions
4b9940c18b chore(release): obfuscate and hash production assets [skip ci] 2026-05-28 15:12:33 +00:00
smueller
24a852aab5 Merge branch 'main' of https://git.hexahost.dev/smueller/HexaHost-Frontend
All checks were successful
Obfuscate Main Build / obfuscate (push) Successful in 25s
2026-05-28 17:12:00 +02:00
smueller
219f1d2fcf Update Gitea workflow for obfuscation: Added environment variables for Gitea host and repository path, modified git remote URL for pushing obfuscated builds, and ensured proper handling of commits with no changes. This enhances the deployment process for production assets. 2026-05-28 17:11:57 +02:00
gitea-actions
06a932a048 chore(release): obfuscate and hash production assets [skip ci] 2026-05-28 15:11:36 +00:00
smueller
f4947d5e25 Merge branch 'dev'
Some checks failed
Obfuscate Main Build / obfuscate (push) Failing after 2m6s
2026-05-28 11:14:50 +02:00
smueller
f097da7eb1 Update product configuration for improved management: Added new fields for product metadata in the backend, enhancing the organization and retrieval of product information. Adjusted existing product entries to utilize the new metadata structure, ensuring consistency and better data handling. 2026-05-28 11:13:39 +02:00
smueller
b4b1dde484 Refactor production build process: Removed outdated PowerShell and Bash scripts for publishing to main, consolidating build instructions in README.md. Updated production build steps to include automatic obfuscation and minification of assets, enhancing deployment efficiency. 2026-05-28 10:12:27 +02:00
smueller
481d223747 Update product configuration and enhance order functionality: Added shop URLs for various products in the backend configuration, improving the management of product links. Implemented new functions to generate order URLs for featured and first packages, enhancing the user experience on product pages. Updated contact and product pages to utilize the new order URL functionality for streamlined ordering. 2026-05-28 08:30:02 +02:00
smueller
45a7067878 Update sitemap.xml with new lastmod dates: Changed last modification dates for all URLs to 2026-05-28, ensuring accurate indexing and improved SEO performance. 2026-05-28 08:28:08 +02:00
smueller
4787d7b770 Refactor JavaScript files for enhanced clarity and maintainability: Updated contact.js, cookie-consent.js, and main.js by replacing obfuscated code with clearer variable names and structures. This improves readability and facilitates future development efforts. 2026-05-28 08:23:31 +02:00
smueller
a0aa8b12ca Refactor JavaScript files for improved readability and maintainability: Replaced obfuscated code in contact.js, cookie-consent.js, and main.js with clearer structures, enhancing code clarity and facilitating future development. This update streamlines the JavaScript files, making them easier to understand and modify. 2026-05-28 08:17:25 +02:00
smueller
b113bdeaa2 Implement strict hover effect removal for legal text: Added CSS rules to ensure no hover, focus, or active effects on legal hero and content sections, enhancing readability and user experience. 2026-05-27 14:45:10 +02:00
smueller
5d2be60dfa Refine legal page styles in custom CSS: Updated hover effects for glass cards and links to enhance user interaction and visual consistency. Improved text decoration on hover for legal blocks and breadcrumbs. 2026-05-27 14:43:46 +02:00
smueller
62d0076799 Enhance legal page styles in custom CSS: Added new styles for legal hero and content sections, adjusted margins and padding, and modified section hover effects for improved visual consistency and readability. 2026-05-27 14:42:32 +02:00
smueller
e920fdfc8e Add legal styling to custom CSS: Implemented styles for legal pages, including background and text color adjustments, section borders, and hover effects to enhance readability and visual consistency. 2026-05-27 14:05:55 +02:00
smueller
5d953fda7b Enhance product configuration and ordering functionality: Updated products-config.php to include shop URLs for various packages, improving the central management of product information. Added new functions for generating order URLs based on product and package selections. Updated public pages for VPC, VPS, Mail Gateway, and Webhosting to utilize the new order URL functionality, enhancing user experience and streamlining the ordering process. 2026-05-27 13:51:54 +02:00
smueller
6ca4786955 Remove legacy build scripts: Deleted outdated PowerShell and Bash scripts for publishing and running builds, streamlining the project structure and eliminating redundancy. These scripts were previously used for production builds and have been replaced by more efficient methods. 2026-05-27 13:24:20 +02:00
smueller
b9bd339607 Refactor configuration files for HexaHost.de: Updated mail and product configuration files to improve clarity and maintainability. Added deprecation notices in the old config file, migrated email handling to a new structure, and enhanced documentation for better understanding. Improved header comments across various public pages for better organization and readability. 2026-05-27 13:22:46 +02:00
61 changed files with 1255 additions and 2072 deletions

View File

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

29
.githooks/commit-msg Normal file
View File

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

63
.github/workflows/obfuscate-main.yml vendored Normal file
View File

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

1
.gitignore vendored
View File

@@ -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
View 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

View File

@@ -166,25 +166,51 @@ Für den Produktivbetrieb `public/` als Webroot konfigurieren.
### Production-Build & Veröffentlichung ### Production-Build & Veröffentlichung
Der Quellcode bleibt auf `dev`, der veröffentlichte Stand liegt auf `main` (ohne Kommentare, obfuskiertes JS). | Branch | Zweck |
|--------|--------|
| **`dev`** | Entwicklung (lesbarer Code, Kommentare) |
| **`ci`** | Integration (du mergst `dev` hierher) |
| **`main`** | Release/Produktion (obfuskiert, gehashte Assets — nur per Pipeline) |
**Voraussetzungen:** Node.js 18+ (inkl. npm), PHP 8+ CLI, Git **Ablauf: `dev` → `ci` → `main`**
1. Auf **`dev`** entwickeln und pushen
2. **`dev` nach `ci` mergen** (manuell, z. B. in Gitea oder lokal)
3. **`ci` pushen** → startet `.gitea/workflows/obfuscate-main.yml`
4. Pipeline obfuskiert im Runner-Workspace und publiziert nach **`main`**
```powershell ```powershell
# Windows # Nach fertigen Änderungen auf dev:
.\scripts\run-build.ps1 git checkout ci
.\scripts\publish-to-main.ps1 -Push git pull origin ci
git merge dev
git push origin ci
``` ```
Bei jedem Push auf **`ci`**:
1. Checkout von `ci` im temporären Runner-Workspace
2. Obfuscation-Build (`scripts/obfuscate_release.py --hash-assets`)
3. Ergebnis nach `main` pushen (Bot-Commit mit `[skip ci]`)
**Nicht** `dev` oder `ci` direkt nach `main` mergen. Der Branch **`ci` bleibt lesbar** — Obfuscation wird nur nach `main` publiziert.
`ci`-Branch einmalig anlegen (falls noch nicht vorhanden): `git checkout -b ci dev && git push -u origin ci`
Der Build führt aus:
- Entfernen von Kommentaren (inkl. Block-Kommentaren) in PHP/JS/CSS
- Minify + Obfuscate für JavaScript
- Minify für CSS
- Kein Source-Map-Output
- Hashing von JS/CSS-Dateinamen + automatische Referenz-Anpassung
Lokal testen (nur in Kopie, nicht committen):
```bash ```bash
# Linux / macOS python scripts/obfuscate_release.py --root . --hash-assets
chmod +x scripts/*.sh
./scripts/run-build.sh
./scripts/publish-to-main.sh --push
``` ```
Details: `scripts/build/README.md`
## 🔗 Backend-Integration ## 🔗 Backend-Integration
Das Backend-Repository enthält folgende wiederverwendbare Komponenten: Das Backend-Repository enthält folgende wiederverwendbare Komponenten:

View File

@@ -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));
} }

View File

@@ -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;

View File

@@ -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]);
} }

View File

@@ -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)) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -2,5 +2,16 @@
require_once __DIR__ . '/mail-config.php'; require_once __DIR__ . '/mail-config.php';
?> ?>

View File

@@ -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',

View File

@@ -2,13 +2,18 @@
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de'); define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
define('SMTP_TO_EMAIL', 'info@hexahost.de'); define('SMTP_TO_EMAIL', 'info@hexahost.de');
define('ENABLE_CSRF_PROTECTION', true); define('ENABLE_CSRF_PROTECTION', true);
define('ENABLE_RATE_LIMITING', true); define('ENABLE_RATE_LIMITING', true);
define('MAX_REQUESTS_PER_HOUR', 10); define('MAX_REQUESTS_PER_HOUR', 5);
define('ENABLE_SPAM_PROTECTION', true); define('ENABLE_SPAM_PROTECTION', true);
@@ -31,34 +36,21 @@ define('ADDITIONAL_HEADERS', [
]); ]);
define('ALLOWED_EMAIL_DOMAINS', [ define('ALLOWED_EMAIL_DOMAINS', [
]); ]);
define('BLACKLISTED_EMAILS', [ define('BLACKLISTED_EMAILS', [
]); ]);
if (!defined('SMTP_HOST') || !defined('SMTP_USERNAME') || !defined('SMTP_PASSWORD')) {
die('SMTP-Konfiguration ist unvollständig. Bitte überprüfen Sie die mail-config.php');
}
if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) { if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) {
die('Ungültige SMTP_FROM_EMAIL Adresse'); die('Ungültige SMTP_FROM_EMAIL Adresse');
} }
@@ -85,20 +77,17 @@ function logEmail($type, $data) {
} }
function isValidEmail($email) { function isValidEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false; return false;
} }
if (in_array($email, BLACKLISTED_EMAILS)) { if (in_array($email, BLACKLISTED_EMAILS)) {
return false; return false;
} }
if (!empty(ALLOWED_EMAIL_DOMAINS)) { if (!empty(ALLOWED_EMAIL_DOMAINS)) {
$domain = substr(strrchr($email, "@"), 1); $domain = substr(strrchr($email, "@"), 1);
if (!in_array($domain, ALLOWED_EMAIL_DOMAINS)) { if (!in_array($domain, ALLOWED_EMAIL_DOMAINS)) {
@@ -111,6 +100,11 @@ function isValidEmail($email) {
function getHexaHostConfig($key = null) { function getHexaHostConfig($key = null) {
$config = [ $config = [
@@ -120,7 +114,6 @@ function getHexaHostConfig($key = null) {
'to_name' => 'HexaHost Support', 'to_name' => 'HexaHost Support',
'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR, 'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR,
'honeypot_field' => 'website', 'honeypot_field' => 'website',
'enable_csrf' => ENABLE_CSRF_PROTECTION, 'enable_csrf' => ENABLE_CSRF_PROTECTION,
@@ -128,7 +121,6 @@ function getHexaHostConfig($key = null) {
'max_message_length' => MAX_MESSAGE_LENGTH, 'max_message_length' => MAX_MESSAGE_LENGTH,
'debug_mode' => DEBUG_MODE, 'debug_mode' => DEBUG_MODE,
'log_errors' => LOG_EMAILS, 'log_errors' => LOG_EMAILS,
]; ];

View File

@@ -4,6 +4,16 @@
$PRODUCTS['vpc'] = [ $PRODUCTS['vpc'] = [
'name' => 'Virtual Private Container', 'name' => 'Virtual Private Container',
'short_name' => 'VPC', 'short_name' => 'VPC',
@@ -21,6 +31,7 @@ $PRODUCTS['vpc'] = [
'starter' => [ 'starter' => [
'name' => 'VPC Starter', 'name' => 'VPC Starter',
'price' => '4,99', 'price' => '4,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '1 vCore'], ['label' => 'CPU Kerne', 'value' => '1 vCore'],
@@ -40,6 +51,7 @@ $PRODUCTS['vpc'] = [
'business' => [ 'business' => [
'name' => 'VPC Business', 'name' => 'VPC Business',
'price' => '9,99', 'price' => '9,99',
'shop_url' => '',
'featured' => true, 'featured' => true,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '2 vCores'], ['label' => 'CPU Kerne', 'value' => '2 vCores'],
@@ -60,6 +72,7 @@ $PRODUCTS['vpc'] = [
'professional' => [ 'professional' => [
'name' => 'VPC Professional', 'name' => 'VPC Professional',
'price' => '19,99', 'price' => '19,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '4 vCores'], ['label' => 'CPU Kerne', 'value' => '4 vCores'],
@@ -81,6 +94,7 @@ $PRODUCTS['vpc'] = [
'enterprise' => [ 'enterprise' => [
'name' => 'VPC Enterprise', 'name' => 'VPC Enterprise',
'price' => '39,99', 'price' => '39,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '8 vCores'], ['label' => 'CPU Kerne', 'value' => '8 vCores'],
@@ -123,6 +137,7 @@ $PRODUCTS['vps'] = [
'starter' => [ 'starter' => [
'name' => 'VPS Starter', 'name' => 'VPS Starter',
'price' => '9,99', 'price' => '9,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '1 vCore'], ['label' => 'CPU Kerne', 'value' => '1 vCore'],
@@ -142,6 +157,7 @@ $PRODUCTS['vps'] = [
'business' => [ 'business' => [
'name' => 'VPS Business', 'name' => 'VPS Business',
'price' => '19,99', 'price' => '19,99',
'shop_url' => '',
'featured' => true, 'featured' => true,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '2 vCores'], ['label' => 'CPU Kerne', 'value' => '2 vCores'],
@@ -162,6 +178,7 @@ $PRODUCTS['vps'] = [
'professional' => [ 'professional' => [
'name' => 'VPS Professional', 'name' => 'VPS Professional',
'price' => '39,99', 'price' => '39,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '4 vCores'], ['label' => 'CPU Kerne', 'value' => '4 vCores'],
@@ -183,6 +200,7 @@ $PRODUCTS['vps'] = [
'enterprise' => [ 'enterprise' => [
'name' => 'VPS Enterprise', 'name' => 'VPS Enterprise',
'price' => '79,99', 'price' => '79,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'CPU Kerne', 'value' => '8 vCores'], ['label' => 'CPU Kerne', 'value' => '8 vCores'],
@@ -225,6 +243,7 @@ $PRODUCTS['mail-gateway'] = [
'starter' => [ 'starter' => [
'name' => 'Mail Starter', 'name' => 'Mail Starter',
'price' => '4,99', 'price' => '4,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Postfächer', 'value' => '5'], ['label' => 'Postfächer', 'value' => '5'],
@@ -243,6 +262,7 @@ $PRODUCTS['mail-gateway'] = [
'business' => [ 'business' => [
'name' => 'Mail Business', 'name' => 'Mail Business',
'price' => '14,99', 'price' => '14,99',
'shop_url' => '',
'featured' => true, 'featured' => true,
'specs' => [ 'specs' => [
['label' => 'Postfächer', 'value' => '25'], ['label' => 'Postfächer', 'value' => '25'],
@@ -263,6 +283,7 @@ $PRODUCTS['mail-gateway'] = [
'professional' => [ 'professional' => [
'name' => 'Mail Professional', 'name' => 'Mail Professional',
'price' => '29,99', 'price' => '29,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Postfächer', 'value' => '100'], ['label' => 'Postfächer', 'value' => '100'],
@@ -284,6 +305,7 @@ $PRODUCTS['mail-gateway'] = [
'enterprise' => [ 'enterprise' => [
'name' => 'Mail Enterprise', 'name' => 'Mail Enterprise',
'price' => '59,99', 'price' => '59,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Postfächer', 'value' => 'Unbegrenzt'], ['label' => 'Postfächer', 'value' => 'Unbegrenzt'],
@@ -327,6 +349,7 @@ $PRODUCTS['webhosting'] = [
'starter' => [ 'starter' => [
'name' => 'Webhosting Starter', 'name' => 'Webhosting Starter',
'price' => '4,99', 'price' => '4,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-starter',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Webspace', 'value' => '10 GB'], ['label' => 'Webspace', 'value' => '10 GB'],
@@ -349,6 +372,7 @@ $PRODUCTS['webhosting'] = [
'business' => [ 'business' => [
'name' => 'Webhosting Business', 'name' => 'Webhosting Business',
'price' => '7,99', 'price' => '7,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-business',
'featured' => true, 'featured' => true,
'specs' => [ 'specs' => [
['label' => 'Webspace', 'value' => '30 GB'], ['label' => 'Webspace', 'value' => '30 GB'],
@@ -371,6 +395,7 @@ $PRODUCTS['webhosting'] = [
'professional' => [ 'professional' => [
'name' => 'Webhosting Professional', 'name' => 'Webhosting Professional',
'price' => '9,99', 'price' => '9,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-professional',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Webspace', 'value' => '50 GB'], ['label' => 'Webspace', 'value' => '50 GB'],
@@ -393,6 +418,7 @@ $PRODUCTS['webhosting'] = [
'enterprise' => [ 'enterprise' => [
'name' => 'Webhosting Enterprise', 'name' => 'Webhosting Enterprise',
'price' => '29,99', 'price' => '29,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Webspace', 'value' => '200 GB'], ['label' => 'Webspace', 'value' => '200 GB'],
@@ -418,6 +444,41 @@ $PRODUCTS['webhosting'] = [
]; ];
$PRODUCT_VISIBILITY = [
'vpc' => false,
'vps' => false,
'mail-gateway' => false,
'webhosting' => true,
];
function isProductVisible(string $productId): bool {
global $PRODUCT_VISIBILITY;
return $PRODUCT_VISIBILITY[$productId] ?? true;
}
function productHiddenAttr(string $productId): string {
return isProductVisible($productId) ? '' : ' hidden';
}
function getVisibleProductPageIds(): array {
global $PRODUCT_VISIBILITY;
return array_keys(array_filter($PRODUCT_VISIBILITY, static fn(bool $visible): bool => $visible));
}
@@ -428,41 +489,87 @@ function getAllProducts() {
} }
function getProduct($productId) { function getProduct($productId) {
global $PRODUCTS; global $PRODUCTS;
return $PRODUCTS[$productId] ?? null; return $PRODUCTS[$productId] ?? null;
} }
function getProductPackages($productId) { function getProductPackages($productId) {
global $PRODUCTS; global $PRODUCTS;
return $PRODUCTS[$productId]['packages'] ?? []; return $PRODUCTS[$productId]['packages'] ?? [];
} }
function getPackage($productId, $packageId) { function getPackage($productId, $packageId) {
global $PRODUCTS; global $PRODUCTS;
return $PRODUCTS[$productId]['packages'][$packageId] ?? null; return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
} }
function getPackagePrice($productId, $packageId) { function getPackagePrice($productId, $packageId) {
$package = getPackage($productId, $packageId); $package = getPackage($productId, $packageId);
return $package['price'] ?? null; return $package['price'] ?? null;
} }
function getMinPrice($productId) { function getMinPrice($productId) {
global $PRODUCTS; global $PRODUCTS;
return $PRODUCTS[$productId]['min_price'] ?? null; return $PRODUCTS[$productId]['min_price'] ?? null;
} }
function formatPrice($price, $withCurrency = true) { function formatPrice($price, $withCurrency = true) {
return $withCurrency ? $price . '€' : $price; return $withCurrency ? $price . '€' : $price;
} }
function getOrderUrl($productId, $packageId) {
$package = getPackage($productId, $packageId);
if ($package && !empty($package['shop_url'])) {
return $package['shop_url'];
}
return sprintf('contact.php?package=%s-%s', $productId, $packageId);
}
function getProductOrderUrl($productId) {
$packages = getProductPackages($productId);
foreach ($packages as $packageId => $package) {
if (!empty($package['featured'])) {
return getOrderUrl($productId, $packageId);
}
}
$firstPackageId = array_key_first($packages);
if ($firstPackageId !== null) {
return getOrderUrl($productId, $firstPackageId);
}
return sprintf('contact.php?product=%s', $productId);
}
function renderPackageCard($productId, $packageId, $package) { function renderPackageCard($productId, $packageId, $package) {
$featuredClass = $package['featured'] ? ' featured' : ''; $featuredClass = $package['featured'] ? ' featured' : '';
$featuredBadge = $package['featured'] ? '<div class="featured-badge">Beliebt</div>' : ''; $featuredBadge = $package['featured'] ? '<div class="featured-badge">Beliebt</div>' : '';
@@ -497,7 +604,7 @@ function renderPackageCard($productId, $packageId, $package) {
<div class="package-features"> <div class="package-features">
%s %s
</div> </div>
<a href="contact.php?package=%s-%s" class="btn btn-primary">Jetzt bestellen</a> <a href="%s" class="btn btn-primary">Jetzt bestellen</a>
</div>', </div>',
$featuredClass, $featuredClass,
$featuredBadge, $featuredBadge,
@@ -505,12 +612,13 @@ function renderPackageCard($productId, $packageId, $package) {
$package['price'], $package['price'],
$specsHtml, $specsHtml,
$featuresHtml, $featuresHtml,
$productId, htmlspecialchars(getOrderUrl($productId, $packageId), ENT_QUOTES, 'UTF-8')
$packageId
); );
} }
function renderAllPackages($productId) { function renderAllPackages($productId) {
$packages = getProductPackages($productId); $packages = getProductPackages($productId);
$html = ''; $html = '';

View File

@@ -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.']);

View File

@@ -15,10 +15,17 @@
<div class="footer-section"> <div class="footer-section">
<h4>Produkte</h4> <h4>Produkte</h4>
<ul> <ul>
<li><a href="/vpc">Virtual Private Container</a></li> <li<?php echo productHiddenAttr('vpc'); ?>><a href="/vpc">Virtual Private Container</a></li>
<li><a href="/vps">Virtual Private Server</a></li> <li<?php echo productHiddenAttr('vps'); ?>><a href="/vps">Virtual Private Server</a></li>
<li><a href="/mail-gateway">Mail Gateway</a></li> <li<?php echo productHiddenAttr('mail-gateway'); ?>><a href="/mail-gateway">Mail Gateway</a></li>
<li><a href="/webhosting">Webhosting</a></li> <li<?php echo productHiddenAttr('webhosting'); ?>><a href="/webhosting">Webhosting</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Andere Dienste</h4>
<ul>
<li><a href="https://hexadns.de" target="_blank" rel="noopener noreferrer">hexadns.de</a></li>
<li><a href="https://www.hexa-mail.de/" target="_blank" rel="noopener noreferrer">hexa-mail.de</a></li>
</ul> </ul>
</div> </div>
<div class="footer-section"> <div class="footer-section">
@@ -49,7 +56,7 @@
</div> </div>
</footer> </footer>
<!-- Cookie Consent Banner -->
<div id="cookieConsent" class="cookie-consent" role="dialog" aria-labelledby="cookieConsentTitle" aria-describedby="cookieConsentDesc"> <div id="cookieConsent" class="cookie-consent" role="dialog" aria-labelledby="cookieConsentTitle" aria-describedby="cookieConsentDesc">
<div class="cookie-consent-container"> <div class="cookie-consent-container">
<div class="cookie-consent-content"> <div class="cookie-consent-content">
@@ -79,7 +86,7 @@
</div> </div>
</div> </div>
<!-- Erweiterte Cookie-Einstellungen (standardmäßig versteckt) -->
<div id="cookieSettingsPanel" class="cookie-settings-panel" style="display: none;"> <div id="cookieSettingsPanel" class="cookie-settings-panel" style="display: none;">
<div class="cookie-settings-content"> <div class="cookie-settings-content">
<h4>Cookie-Einstellungen</h4> <h4>Cookie-Einstellungen</h4>
@@ -121,12 +128,12 @@
</div> </div>
</div> </div>
<!-- Google Analytics (GA4) mit Consent Mode -->
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag(){dataLayer.push(arguments);}
// Standard: keine Analyse/Marketing-Cookies bis zur Einwilligung
gtag('consent', 'default', { gtag('consent', 'default', {
analytics_storage: 'denied', analytics_storage: 'denied',
ad_storage: 'denied', ad_storage: 'denied',
@@ -139,7 +146,7 @@
anonymize_ip: true anonymize_ip: true
}); });
// Übergibt Consent-Änderungen aus dem eigenen Cookie-Banner an GA
window.addEventListener('cookieConsentUpdated', function (event) { window.addEventListener('cookieConsentUpdated', function (event) {
var payload = event && event.detail ? event.detail : {}; var payload = event && event.detail ? event.detail : {};
var consent = payload.consent ? payload.consent : payload; var consent = payload.consent ? payload.consent : payload;
@@ -156,8 +163,8 @@
</script> </script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-EF0E9VPMTD"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-EF0E9VPMTD"></script>
<script src="/assets/js/main.js" defer></script> <script src="/assets/js/main.9189c38109cf.js" defer></script>
<script src="/assets/js/cookie-consent.js" defer></script> <script src="/assets/js/cookie-consent.91c79812d22c.js" defer></script>
<?php if (isset($additional_scripts)): ?> <?php if (isset($additional_scripts)): ?>
<?php foreach ($additional_scripts as $script): ?> <?php foreach ($additional_scripts as $script): ?>
<script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script> <script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>

View File

@@ -2,6 +2,10 @@
require_once __DIR__ . '/../config/products-config.php';
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
ini_set('session.cookie_httponly', 1); ini_set('session.cookie_httponly', 1);
@@ -28,6 +32,13 @@ if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
} }
function includeHeader($title = '', $description = '', $page = '', $scripts = []) { function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
global $page_title, $page_description, $current_page, $additional_scripts; global $page_title, $page_description, $current_page, $additional_scripts;
@@ -47,11 +58,17 @@ function includeHeader($title = '', $description = '', $page = '', $scripts = []
} }
function includeFooter() { function includeFooter() {
include __DIR__ . '/footer.php'; include __DIR__ . '/footer.php';
} }
function generateBreadcrumbs($breadcrumbs) { function generateBreadcrumbs($breadcrumbs) {
echo '<div class="breadcrumb">'; echo '<div class="breadcrumb">';
$last_index = count($breadcrumbs) - 1; $last_index = count($breadcrumbs) - 1;
@@ -70,6 +87,10 @@ function generateBreadcrumbs($breadcrumbs) {
} }
function generateCSRFToken() { function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) { if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
@@ -78,6 +99,8 @@ function generateCSRFToken() {
} }
function validateCSRFToken($token) { function validateCSRFToken($token) {
if (!isset($_SESSION['csrf_token']) || !is_string($token)) { if (!isset($_SESSION['csrf_token']) || !is_string($token)) {
return false; return false;
@@ -90,11 +113,15 @@ function validateCSRFToken($token) {
} }
function sanitizeHeaderValue(string $value): string { function sanitizeHeaderValue(string $value): string {
return str_replace(["\r", "\n", "\0"], '', trim($value)); return str_replace(["\r", "\n", "\0"], '', trim($value));
} }
function getClientIP(): string { function getClientIP(): string {
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP']) if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) { && filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Performance: DNS Prefetch & Preconnect -->
<link rel="dns-prefetch" href="//fonts.googleapis.com"> <link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//fonts.gstatic.com"> <link rel="dns-prefetch" href="//fonts.gstatic.com">
<link rel="dns-prefetch" href="//cdn.hexahost.de"> <link rel="dns-prefetch" href="//cdn.hexahost.de">
@@ -12,37 +12,37 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.hexahost.de" crossorigin> <link rel="preconnect" href="https://cdn.hexahost.de" crossorigin>
<!-- Performance: Preload kritischer Ressourcen -->
<link rel="preload" href="/assets/css/style.css" as="style"> <link rel="preload" href="/assets/css/style.d01979e8c871.css" as="style">
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" as="style"> <link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" as="style">
<title><?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern'; ?></title> <title><?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern'; ?></title>
<!-- SEO Meta Tags -->
<meta name="description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.'; ?>"> <meta name="description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.'; ?>">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
<meta name="author" content="HexaHost.de"> <meta name="author" content="HexaHost.de">
<meta name="theme-color" content="#0d0821"> <meta name="theme-color" content="#0d0821">
<!-- Open Graph / Social Media -->
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:site_name" content="HexaHost.de"> <meta property="og:site_name" content="HexaHost.de">
<meta property="og:title" content="<?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de'; ?>"> <meta property="og:title" content="<?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de'; ?>">
<meta property="og:description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'Zuverlässiges Hosting aus Niederbayern'; ?>"> <meta property="og:description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'Zuverlässiges Hosting aus Niederbayern'; ?>">
<meta property="og:locale" content="de_DE"> <meta property="og:locale" content="de_DE">
<!-- Main Stylesheets -->
<link rel="stylesheet" href="/assets/css/style.d01979e8c871.css">
<link rel="stylesheet" href="/assets/css/custom.d35eb3499212.css">
<link rel="stylesheet" href="/assets/css/style.css"> <!-- Fonts -->
<link rel="stylesheet" href="/assets/css/custom.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Russo+One&family=Source+Sans+Pro:wght@300;400;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Russo+One&family=Source+Sans+Pro:wght@300;400;600;700&display=swap" rel="stylesheet">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="apple-touch-icon" href="/favicon.svg"> <link rel="apple-touch-icon" href="/favicon.svg">
<!-- Canonical URL (falls gesetzt) -->
<?php if (isset($canonical_url)): ?> <?php if (isset($canonical_url)): ?>
<link rel="canonical" href="<?php echo htmlspecialchars($canonical_url); ?>"> <link rel="canonical" href="<?php echo htmlspecialchars($canonical_url); ?>">
<?php endif; ?> <?php endif; ?>
@@ -59,12 +59,12 @@
<ul class="nav-menu"> <ul class="nav-menu">
<li><a href="/" class="nav-link <?php echo ($current_page === 'home') ? 'active' : ''; ?>">Home</a></li> <li><a href="/" class="nav-link <?php echo ($current_page === 'home') ? 'active' : ''; ?>">Home</a></li>
<li class="nav-dropdown"> <li class="nav-dropdown">
<a href="#" class="nav-link <?php echo (in_array($current_page, ['vpc', 'vps', 'mail-gateway', 'webhosting'])) ? 'active' : ''; ?>">Produkte</a> <a href="#" class="nav-link <?php echo (in_array($current_page, getVisibleProductPageIds(), true)) ? 'active' : ''; ?>">Produkte</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="/vpc" class="<?php echo ($current_page === 'vpc') ? 'active' : ''; ?>">Virtual Private Container</a></li> <li<?php echo productHiddenAttr('vpc'); ?>><a href="/vpc" class="<?php echo ($current_page === 'vpc') ? 'active' : ''; ?>">Virtual Private Container</a></li>
<li><a href="/vps" class="<?php echo ($current_page === 'vps') ? 'active' : ''; ?>">Virtual Private Server</a></li> <li<?php echo productHiddenAttr('vps'); ?>><a href="/vps" class="<?php echo ($current_page === 'vps') ? 'active' : ''; ?>">Virtual Private Server</a></li>
<li><a href="/mail-gateway" class="<?php echo ($current_page === 'mail-gateway') ? 'active' : ''; ?>">Mail Gateway</a></li> <li<?php echo productHiddenAttr('mail-gateway'); ?>><a href="/mail-gateway" class="<?php echo ($current_page === 'mail-gateway') ? 'active' : ''; ?>">Mail Gateway</a></li>
<li><a href="/webhosting" class="<?php echo ($current_page === 'webhosting') ? 'active' : ''; ?>">Webhosting</a></li> <li<?php echo productHiddenAttr('webhosting'); ?>><a href="/webhosting" class="<?php echo ($current_page === 'webhosting') ? 'active' : ''; ?>">Webhosting</a></li>
</ul> </ul>
</li> </li>
<li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li> <li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li>

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- About Hero -->
<section class="about-hero"> <section class="about-hero">
<div class="container"> <div class="container">
<div class="about-hero-content"> <div class="about-hero-content">
@@ -33,7 +33,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Company Story -->
<section class="company-story"> <section class="company-story">
<div class="container"> <div class="container">
<div class="story-content"> <div class="story-content">
@@ -78,7 +78,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Values -->
<section class="values"> <section class="values">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -128,9 +128,53 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!--
<section class="team">
<div class="container">
<div class="section-header">
<h2 class="section-title">Mein Team</h2>
<p class="section-description">
Die Menschen, die hinter HexaHost.de stehen
</p>
</div>
<div class="team-content">
<div class="team-text">
<p>
Mein Team besteht aus erfahrenen IT-Experten, die langjährige Erfahrung
im Bereich Hosting und Server-Management. Ich bin leidenschaftlich
daran interessiert, Ihnen die bestmöglichen Hosting- und IT-Lösungen zu bieten.
</p>
<p>
Als regionales Unternehmen aus Niederbayern kennen wir die lokalen
Bedürfnisse und bieten persönlichen Support in deutscher Sprache.
Unser Ziel ist es, Ihnen nicht nur technische Lösungen, sondern
auch eine echte Partnerschaft zu bieten.
</p>
</div>
<div class="team-stats glass-card">
<div class="stat-item">
<span class="stat-number">5+</span>
<span class="stat-label">Jahre Erfahrung</span>
</div>
<div class="stat-item">
<span class="stat-number">500+</span>
<span class="stat-label">Zufriedene Kunden</span>
</div>
<div class="stat-item">
<span class="stat-number">99.9%</span>
<span class="stat-label">Uptime</span>
</div>
<div class="stat-item">
<span class="stat-number">24/7</span>
<span class="stat-label">Support</span>
</div>
</div>
</div>
</div>
</section>
-->
<!-- Technology -->
<section class="technology"> <section class="technology">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -185,7 +229,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- CTA Section -->
<section class="cta"> <section class="cta">
<div class="container"> <div class="container">
<div class="cta-content glass-card"> <div class="cta-content glass-card">

View File

@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- AGB Hero -->
<section class="legal-hero"> <section class="legal-hero">
<div class="container"> <div class="container">
<div class="legal-hero-content"> <div class="legal-hero-content">
@@ -29,12 +29,12 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- AGB Content -->
<section class="legal-content"> <section class="legal-content">
<div class="container"> <div class="container">
<div class="legal-container"> <div class="legal-container">
<!-- § 1 Geltungsbereich -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 1 Geltungsbereich</h2> <h2>§ 1 Geltungsbereich</h2>
<div class="legal-block"> <div class="legal-block">
@@ -67,7 +67,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 2 Vertragsschluss -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 2 Vertragsschluss</h2> <h2>§ 2 Vertragsschluss</h2>
<div class="legal-block"> <div class="legal-block">
@@ -98,7 +98,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 3 Leistungsbeschreibung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 3 Leistungsbeschreibung</h2> <h2>§ 3 Leistungsbeschreibung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -129,7 +129,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 4 Preise und Zahlung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 4 Preise und Zahlungsbedingungen</h2> <h2>§ 4 Preise und Zahlungsbedingungen</h2>
<div class="legal-block"> <div class="legal-block">
@@ -168,7 +168,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 5 Vertragslaufzeit und Kündigung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 5 Vertragslaufzeit und Kündigung</h2> <h2>§ 5 Vertragslaufzeit und Kündigung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -207,7 +207,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 6 Widerrufsrecht für Verbraucher -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 6 Widerrufsrecht für Verbraucher</h2> <h2>§ 6 Widerrufsrecht für Verbraucher</h2>
<div class="legal-block"> <div class="legal-block">
@@ -252,7 +252,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 7 Pflichten des Kunden -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 7 Pflichten des Kunden</h2> <h2>§ 7 Pflichten des Kunden</h2>
<div class="legal-block"> <div class="legal-block">
@@ -286,7 +286,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 8 Verbotene Nutzung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 8 Verbotene Nutzung</h2> <h2>§ 8 Verbotene Nutzung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -320,7 +320,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 9 Sperrung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 9 Sperrung von Diensten</h2> <h2>§ 9 Sperrung von Diensten</h2>
<div class="legal-block"> <div class="legal-block">
@@ -350,7 +350,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 10 Haftung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 10 Haftung</h2> <h2>§ 10 Haftung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -384,7 +384,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 11 Datensicherung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 11 Datensicherung</h2> <h2>§ 11 Datensicherung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -406,7 +406,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 12 Datenschutz -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 12 Datenschutz</h2> <h2>§ 12 Datenschutz</h2>
<div class="legal-block"> <div class="legal-block">
@@ -430,7 +430,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 13 Geheimhaltung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 13 Geheimhaltung</h2> <h2>§ 13 Geheimhaltung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -455,7 +455,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 14 Änderungen der AGB -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 14 Änderungen der AGB</h2> <h2>§ 14 Änderungen der AGB</h2>
<div class="legal-block"> <div class="legal-block">
@@ -478,7 +478,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- § 15 Schlussbestimmungen -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>§ 15 Schlussbestimmungen</h2> <h2>§ 15 Schlussbestimmungen</h2>
<div class="legal-block"> <div class="legal-block">
@@ -506,7 +506,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Kontakt -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Kontakt bei Fragen</h2> <h2>Kontakt bei Fragen</h2>
<div class="legal-block"> <div class="legal-block">

View File

@@ -1 +0,0 @@
.btn-tertiary{color:var(--text-primary);background:0 0;border:1px solid rgba(255,255,255,.25);transition:.3s}.btn-tertiary:hover{border-color:var(--primary-color);color:var(--primary-color);background:rgba(255,81,249,.08)}.it-services-actions{justify-content:center;margin-top:2rem}

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
<?php
require_once __DIR__ . '/mail-config.php';
?>

View File

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

View File

@@ -2,134 +2,4 @@
define('SMTP_HOST', 'smtp.ihre-domain.de'); require_once __DIR__ . '/../../backend/config/mail-config.php';
define('SMTP_PORT', 587);
define('SMTP_USERNAME', 'kontakt@ihre-domain.de');
define('SMTP_PASSWORD', 'ihr-smtp-passwort');
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
define('SMTP_TO_EMAIL', 'info@hexahost.de');
define('ENABLE_CSRF_PROTECTION', true);
define('ENABLE_RATE_LIMITING', true);
define('MAX_REQUESTS_PER_HOUR', 10);
define('ENABLE_SPAM_PROTECTION', true);
define('MAX_MESSAGE_LENGTH', 5000);
define('MIN_MESSAGE_LENGTH', 10);
define('DEBUG_MODE', false);
define('LOG_EMAILS', true);
define('ADDITIONAL_HEADERS', [
'X-Mailer' => 'HexaHost.de Contact Form',
'X-Priority' => '3',
'X-MSMail-Priority' => 'Normal',
'Importance' => 'Normal',
'X-Report-Abuse' => 'Please report abuse here: abuse@hexahost.de',
'List-Unsubscribe' => '<mailto:unsubscribe@hexahost.de>',
'Precedence' => 'bulk'
]);
define('ALLOWED_EMAIL_DOMAINS', [
]);
define('BLACKLISTED_EMAILS', [
]);
if (!defined('SMTP_HOST') || !defined('SMTP_USERNAME') || !defined('SMTP_PASSWORD')) {
die('SMTP-Konfiguration ist unvollständig. Bitte überprüfen Sie die mail-config.php');
}
if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) {
die('Ungültige SMTP_FROM_EMAIL Adresse');
}
if (!filter_var(SMTP_TO_EMAIL, FILTER_VALIDATE_EMAIL)) {
die('Ungültige SMTP_TO_EMAIL Adresse');
}
function logEmail($type, $data) {
if (!LOG_EMAILS) return;
$logFile = __DIR__ . '/../logs/email.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$logEntry = "[$timestamp] $type: " . json_encode($data) . "\n";
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
}
function isValidEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false;
}
if (in_array($email, BLACKLISTED_EMAILS)) {
return false;
}
if (!empty(ALLOWED_EMAIL_DOMAINS)) {
$domain = substr(strrchr($email, "@"), 1);
if (!in_array($domain, ALLOWED_EMAIL_DOMAINS)) {
return false;
}
}
return true;
}
function getHexaHostConfig($key = null) {
$config = [
'smtp_host' => SMTP_HOST,
'smtp_port' => SMTP_PORT,
'smtp_username' => SMTP_USERNAME,
'smtp_password' => SMTP_PASSWORD,
'smtp_encryption' => 'tls',
'from_email' => SMTP_FROM_EMAIL,
'from_name' => 'HexaHost.de Kontaktformular',
'to_email' => SMTP_TO_EMAIL,
'to_name' => 'HexaHost Support',
'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR,
'honeypot_field' => 'website',
'enable_csrf' => ENABLE_CSRF_PROTECTION,
'min_message_length' => MIN_MESSAGE_LENGTH,
'max_message_length' => MAX_MESSAGE_LENGTH,
'debug_mode' => DEBUG_MODE,
'log_errors' => LOG_EMAILS,
];
if ($key === null) {
return $config;
}
return $config[$key] ?? null;
}
?>

View File

@@ -1,522 +0,0 @@
<?php
$PRODUCTS['vpc'] = [
'name' => 'Virtual Private Container',
'short_name' => 'VPC',
'description' => 'Effiziente LXC-Container auf Proxmox-Basis',
'min_price' => '4,99',
'hero_highlight' => 'auf Proxmox LXC',
'hero_description' => 'Erleben Sie die Effizienz von Linux-Containern mit der Zuverlässigkeit von Proxmox. Unsere VPC-Lösungen bieten optimale Performance bei minimalem Ressourcenverbrauch.',
'packages_title' => 'VPC Pakete',
'packages_description' => 'Wählen Sie das perfekte Container-Paket für Ihre Anforderungen',
'cta_title' => 'Bereit für Ihren VPC?',
'cta_description' => 'Starten Sie noch heute mit einem Virtual Private Container',
'page_title' => 'Virtual Private Container - Effiziente LXC Container | HexaHost.de',
'page_description' => 'Virtual Private Container auf Proxmox LXC-Basis. Effiziente und preiswerte Container-Lösungen ab 4,99€/Monat bei HexaHost.de',
'packages' => [
'starter' => [
'name' => 'VPC Starter',
'price' => '4,99',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '1 vCore'],
['label' => 'RAM', 'value' => '1 GB'],
['label' => 'SSD Speicher', 'value' => '20 GB'],
['label' => 'Traffic', 'value' => '1 TB'],
['label' => 'IPv4 Adressen', 'value' => '1'],
],
'features' => [
'Proxmox LXC Container',
'Root-Zugriff',
'SSH-Zugang',
'Backup inklusive',
'24/7 Monitoring',
],
],
'business' => [
'name' => 'VPC Business',
'price' => '9,99',
'featured' => true,
'specs' => [
['label' => 'CPU Kerne', 'value' => '2 vCores'],
['label' => 'RAM', 'value' => '4 GB'],
['label' => 'SSD Speicher', 'value' => '80 GB'],
['label' => 'Traffic', 'value' => '3 TB'],
['label' => 'IPv4 Adressen', 'value' => '1'],
],
'features' => [
'Proxmox LXC Container',
'Root-Zugriff',
'SSH-Zugang',
'Tägliches Backup',
'24/7 Monitoring',
'Snapshot-Funktion',
],
],
'professional' => [
'name' => 'VPC Professional',
'price' => '19,99',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '4 vCores'],
['label' => 'RAM', 'value' => '8 GB'],
['label' => 'SSD Speicher', 'value' => '160 GB'],
['label' => 'Traffic', 'value' => '5 TB'],
['label' => 'IPv4 Adressen', 'value' => '2'],
],
'features' => [
'Proxmox LXC Container',
'Root-Zugriff',
'SSH-Zugang',
'Stündliches Backup',
'24/7 Monitoring',
'Snapshot-Funktion',
'Priority Support',
],
],
'enterprise' => [
'name' => 'VPC Enterprise',
'price' => '39,99',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '8 vCores'],
['label' => 'RAM', 'value' => '16 GB'],
['label' => 'SSD Speicher', 'value' => '320 GB'],
['label' => 'Traffic', 'value' => '10 TB'],
['label' => 'IPv4 Adressen', 'value' => '3'],
],
'features' => [
'Proxmox LXC Container',
'Root-Zugriff',
'SSH-Zugang',
'Stündliches Backup',
'24/7 Monitoring',
'Snapshot-Funktion',
'Priority Support',
'Individuelle Konfiguration',
],
],
],
];
$PRODUCTS['vps'] = [
'name' => 'Virtual Private Server',
'short_name' => 'VPS',
'description' => 'Vollwertige KVM-Virtualisierung mit Root-Zugriff',
'min_price' => '9,99',
'hero_highlight' => 'auf Proxmox KVM',
'hero_description' => 'Maximale Flexibilität und Kontrolle mit vollwertiger KVM-Virtualisierung. Installieren Sie jedes Betriebssystem und genießen Sie vollständigen Root-Zugriff.',
'packages_title' => 'VPS Pakete',
'packages_description' => 'Wählen Sie das perfekte VPS-Paket für Ihre Anforderungen',
'cta_title' => 'Bereit für Ihren VPS?',
'cta_description' => 'Starten Sie noch heute mit einem Virtual Private Server',
'page_title' => 'Virtual Private Server - KVM Virtualisierung | HexaHost.de',
'page_description' => 'Virtual Private Server auf Proxmox KVM-Basis. Vollwertige Virtualisierung mit Root-Zugriff ab 9,99€/Monat bei HexaHost.de',
'packages' => [
'starter' => [
'name' => 'VPS Starter',
'price' => '9,99',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '1 vCore'],
['label' => 'RAM', 'value' => '2 GB'],
['label' => 'SSD Speicher', 'value' => '40 GB'],
['label' => 'Traffic', 'value' => '2 TB'],
['label' => 'IPv4 Adressen', 'value' => '1'],
],
'features' => [
'Proxmox KVM Virtualisierung',
'Root-Zugriff',
'SSH-Zugang',
'Backup inklusive',
'24/7 Monitoring',
],
],
'business' => [
'name' => 'VPS Business',
'price' => '19,99',
'featured' => true,
'specs' => [
['label' => 'CPU Kerne', 'value' => '2 vCores'],
['label' => 'RAM', 'value' => '4 GB'],
['label' => 'SSD Speicher', 'value' => '80 GB'],
['label' => 'Traffic', 'value' => '4 TB'],
['label' => 'IPv4 Adressen', 'value' => '1'],
],
'features' => [
'Proxmox KVM Virtualisierung',
'Root-Zugriff',
'SSH-Zugang',
'Tägliches Backup',
'24/7 Monitoring',
'Snapshot-Funktion',
],
],
'professional' => [
'name' => 'VPS Professional',
'price' => '39,99',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '4 vCores'],
['label' => 'RAM', 'value' => '8 GB'],
['label' => 'SSD Speicher', 'value' => '160 GB'],
['label' => 'Traffic', 'value' => '8 TB'],
['label' => 'IPv4 Adressen', 'value' => '2'],
],
'features' => [
'Proxmox KVM Virtualisierung',
'Root-Zugriff',
'SSH-Zugang',
'Stündliches Backup',
'24/7 Monitoring',
'Snapshot-Funktion',
'Priority Support',
],
],
'enterprise' => [
'name' => 'VPS Enterprise',
'price' => '79,99',
'featured' => false,
'specs' => [
['label' => 'CPU Kerne', 'value' => '8 vCores'],
['label' => 'RAM', 'value' => '16 GB'],
['label' => 'SSD Speicher', 'value' => '320 GB'],
['label' => 'Traffic', 'value' => '15 TB'],
['label' => 'IPv4 Adressen', 'value' => '3'],
],
'features' => [
'Proxmox KVM Virtualisierung',
'Root-Zugriff',
'SSH-Zugang',
'Stündliches Backup',
'24/7 Monitoring',
'Snapshot-Funktion',
'Priority Support',
'Individuelle Konfiguration',
],
],
],
];
$PRODUCTS['mail-gateway'] = [
'name' => 'Mail Gateway',
'short_name' => 'Mail',
'description' => 'Professioneller E-Mail-Schutz für Unternehmen',
'min_price' => '4,99',
'hero_highlight' => 'für Unternehmen',
'hero_description' => 'Professionelle E-Mail-Infrastruktur mit maximalem Schutz vor Spam und Malware. Sichern Sie Ihre geschäftliche Kommunikation mit unseren Mail Gateway Lösungen.',
'packages_title' => 'Mail Gateway Pakete',
'packages_description' => 'Wählen Sie das passende Mail Gateway Paket für Ihr Unternehmen',
'cta_title' => 'Bereit für professionelle E-Mail-Kommunikation?',
'cta_description' => 'Starten Sie noch heute mit unserem Mail Gateway',
'page_title' => 'Mail Gateway - Professionelle E-Mail-Lösungen | HexaHost.de',
'page_description' => 'Professionelle Mail Gateway Lösungen für Unternehmen. Spam-Schutz, E-Mail-Archivierung und sichere E-Mail-Kommunikation bei HexaHost.de',
'packages' => [
'starter' => [
'name' => 'Mail Starter',
'price' => '4,99',
'featured' => false,
'specs' => [
['label' => 'Postfächer', 'value' => '5'],
['label' => 'Speicher/Postfach', 'value' => '5 GB'],
['label' => 'Domains', 'value' => '1'],
['label' => 'E-Mails/Tag', 'value' => '500'],
],
'features' => [
'Spam-Filter',
'Virus-Schutz',
'Webmail',
'IMAP/POP3',
'SSL/TLS Verschlüsselung',
],
],
'business' => [
'name' => 'Mail Business',
'price' => '14,99',
'featured' => true,
'specs' => [
['label' => 'Postfächer', 'value' => '25'],
['label' => 'Speicher/Postfach', 'value' => '10 GB'],
['label' => 'Domains', 'value' => '3'],
['label' => 'E-Mails/Tag', 'value' => '2.000'],
],
'features' => [
'Spam-Filter (erweitert)',
'Virus-Schutz',
'Webmail',
'IMAP/POP3',
'SSL/TLS Verschlüsselung',
'E-Mail Archivierung (30 Tage)',
'Kalender & Kontakte',
],
],
'professional' => [
'name' => 'Mail Professional',
'price' => '29,99',
'featured' => false,
'specs' => [
['label' => 'Postfächer', 'value' => '100'],
['label' => 'Speicher/Postfach', 'value' => '25 GB'],
['label' => 'Domains', 'value' => '10'],
['label' => 'E-Mails/Tag', 'value' => '10.000'],
],
'features' => [
'Spam-Filter (KI-gestützt)',
'Virus-Schutz',
'Webmail',
'IMAP/POP3',
'SSL/TLS Verschlüsselung',
'E-Mail Archivierung (1 Jahr)',
'Kalender & Kontakte',
'ActiveSync für Mobile',
],
],
'enterprise' => [
'name' => 'Mail Enterprise',
'price' => '59,99',
'featured' => false,
'specs' => [
['label' => 'Postfächer', 'value' => 'Unbegrenzt'],
['label' => 'Speicher/Postfach', 'value' => '50 GB'],
['label' => 'Domains', 'value' => 'Unbegrenzt'],
['label' => 'E-Mails/Tag', 'value' => 'Unbegrenzt'],
],
'features' => [
'Spam-Filter (KI-gestützt)',
'Virus-Schutz',
'Webmail',
'IMAP/POP3',
'SSL/TLS Verschlüsselung',
'E-Mail Archivierung (10 Jahre)',
'Kalender & Kontakte',
'ActiveSync für Mobile',
'Dedizierte IP',
'Priority Support',
],
],
],
];
$PRODUCTS['webhosting'] = [
'name' => 'Webhosting',
'short_name' => 'Webhosting',
'description' => 'Klassisches Hosting mit PHP, MySQL und SSL',
'min_price' => '4,99',
'hero_highlight' => 'Alles für Ihre Website',
'hero_description' => 'Klassisches Webhosting mit allem, was Sie für eine erfolgreiche Website benötigen. Plesk, PHP, SSL-Zertifikate und E-Mail-Postfächer - alles inklusive.',
'packages_title' => 'Webhosting Pakete',
'packages_description' => 'Von der ersten Website bis zum professionellen Online-Shop',
'cta_title' => 'Bereit für Ihr Webhosting?',
'cta_description' => 'Starten Sie noch heute mit professionellem Webhosting',
'page_title' => 'Webhosting - Klassisches Hosting für Websites | HexaHost.de',
'page_description' => 'Webhosting mit Plesk, PHP und SSL-Zertifikaten. Klassisches Hosting für Websites ab 4,99€/Monat bei HexaHost.de',
'packages' => [
'starter' => [
'name' => 'Webhosting Starter',
'price' => '4,99',
'featured' => false,
'specs' => [
['label' => 'Webspace', 'value' => '10 GB'],
['label' => 'Domains inkl.', 'value' => '1'],
['label' => 'Subdomains', 'value' => '5'],
['label' => 'Domain-Alias', 'value' => '2'],
['label' => 'E-Mail-Postfächer', 'value' => '10'],
['label' => 'Datenbanken', 'value' => '2 MySQL'],
['label' => 'Traffic', 'value' => '100 GB'],
],
'features' => [
'Perfekt für kleine Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
],
],
'business' => [
'name' => 'Webhosting Business',
'price' => '7,99',
'featured' => true,
'specs' => [
['label' => 'Webspace', 'value' => '30 GB'],
['label' => 'Domains inkl.', 'value' => '1'],
['label' => 'Subdomains', 'value' => '10'],
['label' => 'Domain-Alias', 'value' => '2'],
['label' => 'E-Mail-Postfächer', 'value' => '20'],
['label' => 'Datenbanken', 'value' => '5 MySQL'],
['label' => 'Traffic', 'value' => '100 GB'],
],
'features' => [
'Perfekt für mittlere Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
],
],
'professional' => [
'name' => 'Webhosting Professional',
'price' => '9,99',
'featured' => false,
'specs' => [
['label' => 'Webspace', 'value' => '50 GB'],
['label' => 'Domains inkl.', 'value' => '3'],
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => '100'],
['label' => 'Datenbanken', 'value' => '20 MySQL'],
['label' => 'Traffic', 'value' => '100 GB'],
],
'features' => [
'Perfekt für größere Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
],
],
'enterprise' => [
'name' => 'Webhosting Enterprise',
'price' => '29,99',
'featured' => false,
'specs' => [
['label' => 'Webspace', 'value' => '200 GB'],
['label' => 'Domains inkl.', 'value' => '5'],
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => 'Unbegrenzt'],
['label' => 'Datenbanken', 'value' => '50 MySQL'],
['label' => 'Traffic', 'value' => '1 TB'],
],
'features' => [
'Perfekt für Enterprise-Websites und Blogs',
'Plesk',
'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'SSL-Zertifikat (Let\'s Encrypt)',
'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
'Priority Support',
'Individuelle Konfiguration',
],
],
],
];
function getAllProducts() {
global $PRODUCTS;
return $PRODUCTS;
}
function getProduct($productId) {
global $PRODUCTS;
return $PRODUCTS[$productId] ?? null;
}
function getProductPackages($productId) {
global $PRODUCTS;
return $PRODUCTS[$productId]['packages'] ?? [];
}
function getPackage($productId, $packageId) {
global $PRODUCTS;
return $PRODUCTS[$productId]['packages'][$packageId] ?? null;
}
function getPackagePrice($productId, $packageId) {
$package = getPackage($productId, $packageId);
return $package['price'] ?? null;
}
function getMinPrice($productId) {
global $PRODUCTS;
return $PRODUCTS[$productId]['min_price'] ?? null;
}
function formatPrice($price, $withCurrency = true) {
return $withCurrency ? $price . '€' : $price;
}
function renderPackageCard($productId, $packageId, $package) {
$featuredClass = $package['featured'] ? ' featured' : '';
$featuredBadge = $package['featured'] ? '<div class="featured-badge">Beliebt</div>' : '';
$specsHtml = '';
foreach ($package['specs'] as $spec) {
$specsHtml .= sprintf(
'<div class="spec-item"><span class="spec-label">%s:</span><span class="spec-value">%s</span></div>',
htmlspecialchars($spec['label']),
htmlspecialchars($spec['value'])
);
}
$featuresHtml = '';
foreach ($package['features'] as $feature) {
$featuresHtml .= sprintf('<div class="feature">✓ %s</div>', htmlspecialchars($feature));
}
return sprintf('
<div class="package-card glass-card%s">
%s
<div class="package-header">
<h3 class="package-name">%s</h3>
<div class="package-price">
<span class="price">%s€</span>
<span class="period">/Monat</span>
</div>
</div>
<div class="package-specs">
%s
</div>
<div class="package-features">
%s
</div>
<a href="contact.php?package=%s-%s" class="btn btn-primary">Jetzt bestellen</a>
</div>',
$featuredClass,
$featuredBadge,
htmlspecialchars($package['name']),
$package['price'],
$specsHtml,
$featuresHtml,
$productId,
$packageId
);
}
function renderAllPackages($productId) {
$packages = getProductPackages($productId);
$html = '';
foreach ($packages as $packageId => $package) {
$html .= renderPackageCard($productId, $packageId, $package);
}
return $html;
}
?>

View File

@@ -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));
} }

View File

@@ -8,14 +8,14 @@ $preselected_subject = getPreselectedContactSubject();
$page_title = 'Kontakt - HexaHost.de | Hosting aus Niederbayern'; $page_title = 'Kontakt - HexaHost.de | Hosting aus Niederbayern';
$page_description = 'Kontaktieren Sie HexaHost.de - Ihr Hosting-Partner aus Niederbayern. Persönlicher Support und kompetente Beratung.'; $page_description = 'Kontaktieren Sie HexaHost.de - Ihr Hosting-Partner aus Niederbayern. Persönlicher Support und kompetente Beratung.';
$current_page = 'contact'; $current_page = 'contact';
$additional_scripts = ['assets/js/contact.js']; $additional_scripts = ['assets/js/contact.c2399194863d.js'];
includeHeader($page_title, $page_description, $current_page, $additional_scripts); includeHeader($page_title, $page_description, $current_page, $additional_scripts);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Contact Hero -->
<section class="contact-hero"> <section class="contact-hero">
<div class="container"> <div class="container">
<div class="contact-hero-content"> <div class="contact-hero-content">
@@ -36,7 +36,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
</div> </div>
</section> </section>
<!-- Contact Options -->
<section class="contact-options"> <section class="contact-options">
<div class="container"> <div class="container">
<div class="contact-grid"> <div class="contact-grid">
@@ -88,7 +88,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
</div> </div>
</section> </section>
<!-- Contact Form -->
<section class="contact-form-section"> <section class="contact-form-section">
<div class="container"> <div class="container">
<div class="form-container"> <div class="form-container">
@@ -100,7 +100,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
</div> </div>
<form class="contact-form glass-card" id="contactForm" action="contact-handler.php" method="POST"> <form class="contact-form glass-card" id="contactForm" action="contact-handler.php" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>"> <input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<!-- Honeypot-Feld für Bot-Schutz (versteckt via CSS) -->
<div style="position: absolute; left: -9999px;" aria-hidden="true"> <div style="position: absolute; left: -9999px;" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input type="text" name="website" tabindex="-1" autocomplete="off">
</div> </div>
@@ -156,7 +156,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
</div> </div>
</section> </section>
<!-- FAQ Section -->
<section class="faq-section"> <section class="faq-section">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -224,7 +224,7 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
</div> </div>
</section> </section>
<!-- Response Time -->
<section class="response-time"> <section class="response-time">
<div class="container"> <div class="container">
<div class="response-content glass-card"> <div class="response-content glass-card">

View File

@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Datenschutz Hero -->
<section class="legal-hero"> <section class="legal-hero">
<div class="container"> <div class="container">
<div class="legal-hero-content"> <div class="legal-hero-content">
@@ -29,12 +29,12 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Datenschutz Content -->
<section class="legal-content"> <section class="legal-content">
<div class="container"> <div class="container">
<div class="legal-container"> <div class="legal-container">
<!-- Verantwortlicher -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>1. Verantwortlicher</h2> <h2>1. Verantwortlicher</h2>
<div class="legal-block"> <div class="legal-block">
@@ -53,7 +53,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Allgemeine Hinweise -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>2. Allgemeine Hinweise zur Datenverarbeitung</h2> <h2>2. Allgemeine Hinweise zur Datenverarbeitung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -75,7 +75,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Rechtsgrundlagen -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>3. Rechtsgrundlagen der Verarbeitung</h2> <h2>3. Rechtsgrundlagen der Verarbeitung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -89,7 +89,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Server-Logfiles -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>4. Server-Logfiles</h2> <h2>4. Server-Logfiles</h2>
<div class="legal-block"> <div class="legal-block">
@@ -119,7 +119,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Cookies -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>5. Cookies</h2> <h2>5. Cookies</h2>
<div class="legal-block"> <div class="legal-block">
@@ -146,7 +146,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Kontaktformular -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>6. Kontaktformular</h2> <h2>6. Kontaktformular</h2>
<div class="legal-block"> <div class="legal-block">
@@ -168,7 +168,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Hosting und Vertragsabwicklung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>7. Hosting-Dienstleistungen und Vertragsabwicklung</h2> <h2>7. Hosting-Dienstleistungen und Vertragsabwicklung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -193,7 +193,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- SSL-Verschlüsselung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>8. SSL- bzw. TLS-Verschlüsselung</h2> <h2>8. SSL- bzw. TLS-Verschlüsselung</h2>
<div class="legal-block"> <div class="legal-block">
@@ -212,7 +212,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Speicherdauer -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>9. Speicherdauer</h2> <h2>9. Speicherdauer</h2>
<div class="legal-block"> <div class="legal-block">
@@ -229,7 +229,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Betroffenenrechte -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>10. Ihre Rechte als betroffene Person</h2> <h2>10. Ihre Rechte als betroffene Person</h2>
<div class="legal-block"> <div class="legal-block">
@@ -281,7 +281,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Beschwerderecht -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>11. Beschwerderecht bei einer Aufsichtsbehörde</h2> <h2>11. Beschwerderecht bei einer Aufsichtsbehörde</h2>
<div class="legal-block"> <div class="legal-block">
@@ -302,7 +302,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Datensicherheit -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>12. Datensicherheit</h2> <h2>12. Datensicherheit</h2>
<div class="legal-block"> <div class="legal-block">
@@ -320,7 +320,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Änderungen -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>13. Aktualität und Änderung dieser Datenschutzerklärung</h2> <h2>13. Aktualität und Änderung dieser Datenschutzerklärung</h2>
<div class="legal-block"> <div class="legal-block">

View File

@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Impressum Hero -->
<section class="legal-hero"> <section class="legal-hero">
<div class="container"> <div class="container">
<div class="legal-hero-content"> <div class="legal-hero-content">
@@ -29,12 +29,12 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Impressum Content -->
<section class="legal-content"> <section class="legal-content">
<div class="container"> <div class="container">
<div class="legal-container"> <div class="legal-container">
<!-- Anbieterkennung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Angaben gemäß § 5 DDG</h2> <h2>Angaben gemäß § 5 DDG</h2>
@@ -50,7 +50,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Kontakt -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Kontakt</h2> <h2>Kontakt</h2>
<div class="legal-block"> <div class="legal-block">
@@ -61,7 +61,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Umsatzsteuer-ID (falls vorhanden) -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Umsatzsteuer-ID</h2> <h2>Umsatzsteuer-ID</h2>
<div class="legal-block"> <div class="legal-block">
@@ -72,7 +72,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Verantwortlich für den Inhalt -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Redaktionell verantwortlich</h2> <h2>Redaktionell verantwortlich</h2>
<div class="legal-block"> <div class="legal-block">
@@ -85,14 +85,14 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- EU-Streitschlichtung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>EU-Streitschlichtung</h2> <h2>EU-Streitschlichtung</h2>
<div class="legal-block"> <div class="legal-block">
<p> <p>
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener noreferrer"> <a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener noreferrer">
https://ec.europa.eu/consumers/odr/ https:
</a> </a>
</p> </p>
<p> <p>
@@ -101,7 +101,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Verbraucherstreitbeilegung -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Verbraucherstreitbeilegung / Universalschlichtungsstelle</h2> <h2>Verbraucherstreitbeilegung / Universalschlichtungsstelle</h2>
<div class="legal-block"> <div class="legal-block">
@@ -112,7 +112,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Haftungsausschluss -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Haftung für Inhalte</h2> <h2>Haftung für Inhalte</h2>
<div class="legal-block"> <div class="legal-block">
@@ -133,7 +133,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Haftung für Links -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Haftung für Links</h2> <h2>Haftung für Links</h2>
<div class="legal-block"> <div class="legal-block">
@@ -153,7 +153,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</div> </div>
<!-- Urheberrecht -->
<div class="legal-section glass-card"> <div class="legal-section glass-card">
<h2>Urheberrecht</h2> <h2>Urheberrecht</h2>
<div class="legal-block"> <div class="legal-block">

View File

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

View File

@@ -1,167 +0,0 @@
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h4>HexaHost.de</h4>
<p>Zuverlässiges Hosting aus Niederbayern</p>
<div class="footer-location">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
<circle cx="12" cy="10" r="3"/>
</svg>
<span>Niederbayern, Deutschland</span>
</div>
</div>
<div class="footer-section">
<h4>Produkte</h4>
<ul>
<li><a href="/vpc">Virtual Private Container</a></li>
<li><a href="/vps">Virtual Private Server</a></li>
<li><a href="/mail-gateway">Mail Gateway</a></li>
<li><a href="/webhosting">Webhosting</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Unternehmen</h4>
<ul>
<li><a href="/about">Über mich</a></li>
<li><a href="/contact">Kontakt</a></li>
<li><a href="/impressum">Impressum</a></li>
<li><a href="/datenschutz">Datenschutz</a></li>
<li><a href="/agb">AGB</a></li>
<li><a href="/widerruf">Widerrufsbelehrung</a></li>
<li><a href="#" id="openCookieSettings" onclick="CookieConsent.resetConsent(); return false;">Cookie-Einstellungen</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Support</h4>
<ul>
<li><a href="https://shop.hexahost.de/clientarea.php">Kunden-Center</a></li>
<li><a href="https://shop.hexahost.de/serverstatus.php">Status</a></li>
<li><a href="https://shop.hexahost.de/supporttickets.php">Support-Ticket</a></li>
<li><a href="#">FAQ</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>&copy; <?php echo date('Y'); ?> HexaHost.de - Alle Rechte vorbehalten</p>
</div>
</div>
</footer>
<div id="cookieConsent" class="cookie-consent" role="dialog" aria-labelledby="cookieConsentTitle" aria-describedby="cookieConsentDesc">
<div class="cookie-consent-container">
<div class="cookie-consent-content">
<div class="cookie-consent-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<circle cx="8" cy="9" r="1" fill="currentColor"/>
<circle cx="15" cy="8" r="1" fill="currentColor"/>
<circle cx="10" cy="14" r="1" fill="currentColor"/>
<circle cx="16" cy="13" r="1" fill="currentColor"/>
<circle cx="13" cy="17" r="1" fill="currentColor"/>
</svg>
</div>
<div class="cookie-consent-text">
<h3 id="cookieConsentTitle">Cookie-Einstellungen</h3>
<p id="cookieConsentDesc">
Wir verwenden Cookies, um Ihnen die bestmögliche Erfahrung auf unserer Website zu bieten.
Technisch notwendige Cookies sind für die Funktionalität erforderlich.
<a href="/datenschutz">Mehr erfahren</a>
</p>
</div>
</div>
<div class="cookie-consent-actions">
<button type="button" id="cookieAcceptAll" class="btn btn-primary">Alle akzeptieren</button>
<button type="button" id="cookieAcceptEssential" class="btn btn-secondary">Nur notwendige</button>
<button type="button" id="cookieSettings" class="btn btn-text">Einstellungen</button>
</div>
</div>
<div id="cookieSettingsPanel" class="cookie-settings-panel" style="display: none;">
<div class="cookie-settings-content">
<h4>Cookie-Einstellungen</h4>
<div class="cookie-option">
<div class="cookie-option-info">
<strong>Notwendige Cookies</strong>
<p>Diese Cookies sind für die Grundfunktionen der Website erforderlich.</p>
</div>
<label class="cookie-toggle disabled">
<input type="checkbox" checked disabled>
<span class="cookie-toggle-slider"></span>
</label>
</div>
<div class="cookie-option">
<div class="cookie-option-info">
<strong>Analyse-Cookies</strong>
<p>Helfen uns zu verstehen, wie Besucher unsere Website nutzen.</p>
</div>
<label class="cookie-toggle">
<input type="checkbox" id="cookieAnalytics">
<span class="cookie-toggle-slider"></span>
</label>
</div>
<div class="cookie-option">
<div class="cookie-option-info">
<strong>Marketing-Cookies</strong>
<p>Werden verwendet, um relevante Werbung anzuzeigen.</p>
</div>
<label class="cookie-toggle">
<input type="checkbox" id="cookieMarketing">
<span class="cookie-toggle-slider"></span>
</label>
</div>
<div class="cookie-settings-actions">
<button type="button" id="cookieSaveSettings" class="btn btn-primary">Einstellungen speichern</button>
<button type="button" id="cookieCloseSettings" class="btn btn-secondary">Abbrechen</button>
</div>
</div>
</div>
</div>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// Standard: keine Analyse/Marketing-Cookies bis zur Einwilligung
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied'
});
gtag('js', new Date());
gtag('config', 'G-EF0E9VPMTD', {
anonymize_ip: true
});
// Übergibt Consent-Änderungen aus dem eigenen Cookie-Banner an GA
window.addEventListener('cookieConsentUpdated', function (event) {
var payload = event && event.detail ? event.detail : {};
var consent = payload.consent ? payload.consent : payload;
var analyticsGranted = !!(consent && consent.analytics);
var marketingGranted = !!(consent && consent.marketing);
gtag('consent', 'update', {
analytics_storage: analyticsGranted ? 'granted' : 'denied',
ad_storage: marketingGranted ? 'granted' : 'denied',
ad_user_data: marketingGranted ? 'granted' : 'denied',
ad_personalization: marketingGranted ? 'granted' : 'denied'
});
});
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-EF0E9VPMTD"></script>
<script src="/assets/js/main.js" defer></script>
<script src="/assets/js/cookie-consent.js" defer></script>
<?php if (isset($additional_scripts)): ?>
<?php foreach ($additional_scripts as $script): ?>
<script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>
<?php endforeach; ?>
<?php endif; ?>
</body>
</html>

View File

@@ -1,125 +0,0 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0);
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', 1);
ini_set('session.use_only_cookies', 1);
session_start();
if (!isset($_SESSION['initiated'])) {
session_regenerate_id(true);
$_SESSION['initiated'] = true;
}
}
if (!defined('DEBUG_MODE') || !DEBUG_MODE) {
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
error_reporting(E_ALL);
ini_set('log_errors', 1);
}
function includeHeader($title = '', $description = '', $page = '', $scripts = []) {
global $page_title, $page_description, $current_page, $additional_scripts;
$page_title = !empty($title)
? $title
: 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern';
$page_description = !empty($description)
? $description
: 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.';
$current_page = $page;
$additional_scripts = $scripts;
include __DIR__ . '/header.php';
}
function includeFooter() {
include __DIR__ . '/footer.php';
}
function generateBreadcrumbs($breadcrumbs) {
echo '<div class="breadcrumb">';
$last_index = count($breadcrumbs) - 1;
foreach ($breadcrumbs as $index => $item) {
if ($index === $last_index) {
echo '<span>' . htmlspecialchars($item['title']) . '</span>';
} else {
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
echo '<span>/</span>';
}
}
echo '</div>';
}
function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function validateCSRFToken($token) {
if (!isset($_SESSION['csrf_token']) || !is_string($token)) {
return false;
}
if (!hash_equals($_SESSION['csrf_token'], $token)) {
return false;
}
unset($_SESSION['csrf_token']);
return true;
}
function sanitizeHeaderValue(string $value): string {
return str_replace(["\r", "\n", "\0"], '', trim($value));
}
function getClientIP(): string {
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$isTrustedProxy = filter_var(
$remoteAddr,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === false;
if ($isTrustedProxy) {
foreach (['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
if (empty($_SERVER[$header])) {
continue;
}
$ip = trim(explode(',', $_SERVER[$header])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $remoteAddr;
}
?>

View File

@@ -1,81 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link rel="dns-prefetch" href="//cdn.hexahost.de">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.hexahost.de" crossorigin>
<link rel="preload" href="/assets/css/style.css" as="style">
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" as="style">
<title><?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de - Zuverlässiges Hosting aus Niederbayern'; ?></title>
<meta name="description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'HexaHost.de - Zuverlässiges und preiswertes Hosting aus Niederbayern. VPS, VPC, Mail Gateway und Webhosting Lösungen.'; ?>">
<meta name="robots" content="index, follow">
<meta name="author" content="HexaHost.de">
<meta name="theme-color" content="#0d0821">
<meta property="og:type" content="website">
<meta property="og:site_name" content="HexaHost.de">
<meta property="og:title" content="<?php echo isset($page_title) ? htmlspecialchars($page_title) : 'HexaHost.de'; ?>">
<meta property="og:description" content="<?php echo isset($page_description) ? htmlspecialchars($page_description) : 'Zuverlässiges Hosting aus Niederbayern'; ?>">
<meta property="og:locale" content="de_DE">
<link rel="stylesheet" href="/assets/css/style.css">
<link rel="stylesheet" href="/assets/css/custom.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Russo+One&family=Source+Sans+Pro:wght@300;400;600;700&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="apple-touch-icon" href="/favicon.svg">
<?php if (isset($canonical_url)): ?>
<link rel="canonical" href="<?php echo htmlspecialchars($canonical_url); ?>">
<?php endif; ?>
</head>
<body>
<header class="header">
<nav class="nav">
<div class="nav-container">
<div class="nav-logo">
<a href="/">
<img src="https://cdn.hexahost.de/assets/img/logo/8iFs123BynHQWHI5.png" alt="HexaHost.de Logo" class="logo-image">
</a>
</div>
<ul class="nav-menu">
<li><a href="/" class="nav-link <?php echo ($current_page === 'home') ? 'active' : ''; ?>">Home</a></li>
<li class="nav-dropdown">
<a href="#" class="nav-link <?php echo (in_array($current_page, ['vpc', 'vps', 'mail-gateway', 'webhosting'])) ? 'active' : ''; ?>">Produkte</a>
<ul class="dropdown-menu">
<li><a href="/vpc" class="<?php echo ($current_page === 'vpc') ? 'active' : ''; ?>">Virtual Private Container</a></li>
<li><a href="/vps" class="<?php echo ($current_page === 'vps') ? 'active' : ''; ?>">Virtual Private Server</a></li>
<li><a href="/mail-gateway" class="<?php echo ($current_page === 'mail-gateway') ? 'active' : ''; ?>">Mail Gateway</a></li>
<li><a href="/webhosting" class="<?php echo ($current_page === 'webhosting') ? 'active' : ''; ?>">Webhosting</a></li>
</ul>
</li>
<li><a href="/it-dienstleistungen" class="nav-link <?php echo ($current_page === 'it-dienstleistungen') ? 'active' : ''; ?>">IT-Dienstleistungen</a></li>
<li><a href="/about" class="nav-link <?php echo ($current_page === 'about') ? 'active' : ''; ?>">Über mich</a></li>
<li><a href="/contact" class="nav-link <?php echo ($current_page === 'contact') ? 'active' : ''; ?>">Kontakt</a></li>
</ul>
<div class="nav-toggle">
<span></span>
<span></span>
<span></span>
</div>
</div>
</nav>
</header>

View File

@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Hero Section -->
<section class="hero"> <section class="hero">
<div class="hero-container"> <div class="hero-container">
<div class="hero-content"> <div class="hero-content">
@@ -44,7 +44,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Products Section -->
<section id="products" class="products"> <section id="products" class="products">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -54,7 +54,7 @@ includeHeader($page_title, $page_description, $current_page);
</p> </p>
</div> </div>
<div class="products-grid"> <div class="products-grid">
<div class="product-card glass-card"> <div class="product-card glass-card"<?php echo productHiddenAttr('vpc'); ?>>
<div class="product-icon"> <div class="product-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 7V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v3"/> <path d="M4 7V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v3"/>
@@ -74,7 +74,7 @@ includeHeader($page_title, $page_description, $current_page);
</ul> </ul>
<a href="/vpc" class="btn btn-primary">Mehr erfahren</a> <a href="/vpc" class="btn btn-primary">Mehr erfahren</a>
</div> </div>
<div class="product-card glass-card"> <div class="product-card glass-card"<?php echo productHiddenAttr('vps'); ?>>
<div class="product-icon"> <div class="product-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/> <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
@@ -92,7 +92,7 @@ includeHeader($page_title, $page_description, $current_page);
</ul> </ul>
<a href="/vps" class="btn btn-primary">Mehr erfahren</a> <a href="/vps" class="btn btn-primary">Mehr erfahren</a>
</div> </div>
<div class="product-card glass-card"> <div class="product-card glass-card"<?php echo productHiddenAttr('mail-gateway'); ?>>
<div class="product-icon"> <div class="product-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/> <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
@@ -150,7 +150,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- IT Services Section -->
<section class="features"> <section class="features">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -192,7 +192,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Features Section -->
<section class="features"> <section class="features">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -248,7 +248,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- CTA Section -->
<section class="cta"> <section class="cta">
<div class="container"> <div class="container">
<div class="cta-content glass-card"> <div class="cta-content glass-card">

View File

@@ -11,7 +11,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Services Hero -->
<section class="about-hero"> <section class="about-hero">
<div class="container"> <div class="container">
<div class="about-hero-content"> <div class="about-hero-content">
@@ -32,7 +32,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Target Groups -->
<section class="values"> <section class="values">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -60,7 +60,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Services Grid -->
<section class="products"> <section class="products">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -128,7 +128,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- CTA -->
<section class="cta"> <section class="cta">
<div class="container"> <div class="container">
<div class="cta-content glass-card"> <div class="cta-content glass-card">

View File

@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Product Hero -->
<section class="product-hero"> <section class="product-hero">
<div class="container"> <div class="container">
<div class="product-hero-content"> <div class="product-hero-content">
@@ -59,7 +59,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Mail Gateway Packages -->
<section class="packages"> <section class="packages">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -74,7 +74,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Security Features -->
<section class="technical-details"> <section class="technical-details">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -129,7 +129,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Use Cases -->
<section class="use-cases"> <section class="use-cases">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -159,15 +159,15 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- CTA Section -->
<section class="cta"> <section class="cta">
<div class="container"> <div class="container">
<div class="cta-content glass-card"> <div class="cta-content glass-card">
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2> <h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p> <p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions"> <div class="cta-actions">
<a href="contact.php?product=mail-gateway" class="btn btn-primary">Jetzt bestellen</a> <a href="<?php echo htmlspecialchars(getProductOrderUrl('mail-gateway'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a> <a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -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

View File

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

View File

@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Product Hero -->
<section class="product-hero"> <section class="product-hero">
<div class="container"> <div class="container">
<div class="product-hero-content"> <div class="product-hero-content">
@@ -58,7 +58,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- VPC Packages -->
<section class="packages"> <section class="packages">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -73,7 +73,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Technical Details -->
<section class="technical-details"> <section class="technical-details">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -129,7 +129,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Use Cases -->
<section class="use-cases"> <section class="use-cases">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -159,15 +159,15 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- CTA Section -->
<section class="cta"> <section class="cta">
<div class="container"> <div class="container">
<div class="cta-content glass-card"> <div class="cta-content glass-card">
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2> <h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p> <p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions"> <div class="cta-actions">
<a href="contact.php?product=vpc" class="btn btn-primary">Jetzt bestellen</a> <a href="<?php echo htmlspecialchars(getProductOrderUrl('vpc'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a> <a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Product Hero -->
<section class="product-hero"> <section class="product-hero">
<div class="container"> <div class="container">
<div class="product-hero-content"> <div class="product-hero-content">
@@ -63,7 +63,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- VPS Packages -->
<section class="packages"> <section class="packages">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -78,7 +78,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Technical Details -->
<section class="technical-details"> <section class="technical-details">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -134,7 +134,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Use Cases -->
<section class="use-cases"> <section class="use-cases">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -164,15 +164,15 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- CTA Section -->
<section class="cta"> <section class="cta">
<div class="container"> <div class="container">
<div class="cta-content glass-card"> <div class="cta-content glass-card">
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2> <h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p> <p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions"> <div class="cta-actions">
<a href="contact.php?product=vps" class="btn btn-primary">Jetzt bestellen</a> <a href="<?php echo htmlspecialchars(getProductOrderUrl('vps'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a> <a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -16,7 +16,7 @@ includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content">
<!-- Product Hero -->
<section class="product-hero"> <section class="product-hero">
<div class="container"> <div class="container">
<div class="product-hero-content"> <div class="product-hero-content">
@@ -60,7 +60,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Webhosting Packages -->
<section class="packages"> <section class="packages">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -75,7 +75,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Technical Details -->
<section class="technical-details"> <section class="technical-details">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -95,7 +95,7 @@ includeHeader($page_title, $page_description, $current_page);
<polyline points="10,9 9,9 8,9"/> <polyline points="10,9 9,9 8,9"/>
</svg> </svg>
</div> </div>
<h3>cPanel/Webmin</h3> <h3>Plesk</h3>
<p>Benutzerfreundliche Verwaltungsoberfläche für einfache Website-Verwaltung und E-Mail-Konfiguration.</p> <p>Benutzerfreundliche Verwaltungsoberfläche für einfache Website-Verwaltung und E-Mail-Konfiguration.</p>
</div> </div>
<div class="detail-card glass-card"> <div class="detail-card glass-card">
@@ -133,7 +133,7 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- Use Cases -->
<section class="use-cases"> <section class="use-cases">
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
@@ -163,15 +163,15 @@ includeHeader($page_title, $page_description, $current_page);
</div> </div>
</section> </section>
<!-- CTA Section -->
<section class="cta"> <section class="cta">
<div class="container"> <div class="container">
<div class="cta-content glass-card"> <div class="cta-content glass-card">
<h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2> <h2><?php echo htmlspecialchars($product['cta_title'] ?? ('Bereit für ' . $product['name'] . '?')); ?></h2>
<p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p> <p><?php echo htmlspecialchars($product['cta_description'] ?? ('Starten Sie noch heute mit ' . $product['name'])); ?></p>
<div class="cta-actions"> <div class="cta-actions">
<a href="contact.php?product=webhosting" class="btn btn-primary">Jetzt bestellen</a> <a href="<?php echo htmlspecialchars(getProductOrderUrl('webhosting'), ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-primary">Jetzt bestellen</a>
<a href="/contact" class="btn btn-secondary">Beratung anfordern</a> <a href="contact.php" class="btn btn-secondary">Beratung anfordern</a>
</div> </div>
</div> </div>
</div> </div>

Binary file not shown.

View File

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

View File

@@ -1,185 +0,0 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Erstellt einen Production-Build und veroeffentlicht ihn auf den Branch main.
.DESCRIPTION
1. Wechselt auf main und setzt ihn auf den Stand von dev
2. Entfernt Kommentare, minifiziert CSS, obfuskiert JavaScript
3. Committet und pusht main (optional)
4. Wechselt zurueck auf dev (Quellcode bleibt unveraendert)
.PARAMETER Push
Pusht main nach origin (Standard: nur lokaler Commit)
.PARAMETER DryRun
Fuehrt Git-Schritte nur simuliert aus (Build wird trotzdem erstellt)
.PARAMETER AllowDirty
Erlaubt uncommittete Aenderungen
.PARAMETER Message
Commit-Nachricht fuer den Production-Build
.EXAMPLE
.\scripts\publish-to-main.ps1
.EXAMPLE
.\scripts\publish-to-main.ps1 -Push
#>
[CmdletBinding()]
param(
[switch]$Push,
[switch]$DryRun,
[switch]$AllowDirty,
[string]$Message = ""
)
$ErrorActionPreference = "Stop"
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$BuildDir = Join-Path $Root "scripts\build"
$OriginalBranch = ""
function Write-Step([string]$Text) {
Write-Host ""
Write-Host "==> $Text" -ForegroundColor Cyan
}
function Ensure-GitClean {
$status = git -C $Root status --porcelain
if ($status) {
throw "Uncommittete Aenderungen im Repository. Bitte zuerst committen oder stashen."
}
}
function Resolve-NodeTool([string]$ToolName) {
$command = Get-Command $ToolName -ErrorAction SilentlyContinue
if ($command) {
return $command.Source
}
$candidates = @(
(Join-Path $env:ProgramFiles "nodejs\$ToolName.cmd"),
(Join-Path ${env:ProgramFiles(x86)} "nodejs\$ToolName.cmd"),
(Join-Path $env:LOCALAPPDATA "Programs\nodejs\$ToolName.cmd"),
"c:\Program Files\cursor\resources\app\resources\helpers\node.exe"
)
foreach ($candidate in $candidates) {
if ($ToolName -eq "node" -and (Test-Path $candidate)) {
return $candidate
}
if ($ToolName -ne "node" -and (Test-Path $candidate)) {
return $candidate
}
}
return $null
}
function Ensure-Node {
$script:NodeExe = Resolve-NodeTool "node"
$script:NpmExe = Resolve-NodeTool "npm"
if (-not $script:NodeExe) {
throw "Node.js ist nicht installiert. Bitte Node.js 18+ installieren: https://nodejs.org/"
}
if (-not $script:NpmExe) {
throw "npm wurde nicht gefunden. Bitte Node.js inkl. npm installieren und PATH setzen."
}
}
try {
Set-Location $Root
Ensure-Node
if (-not $AllowDirty) {
Ensure-GitClean
} else {
Write-Warning "AllowDirty aktiv - uncommittete Aenderungen werden mit veroeffentlicht."
}
$OriginalBranch = (git branch --show-current).Trim()
if ($OriginalBranch -ne "dev") {
Write-Warning "Empfohlen: Auf Branch 'dev' starten (aktuell: $OriginalBranch)"
}
if ([string]::IsNullOrWhiteSpace($Message)) {
$Message = "chore(release): production build $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
}
Write-Step "Installiere Build-Abhaengigkeiten"
Set-Location $BuildDir
if (-not $DryRun) {
& $NpmExe ci --no-fund --no-audit
if ($LASTEXITCODE -ne 0) { throw "npm ci fehlgeschlagen" }
}
Write-Step "Wechsle auf main und synchronisiere mit dev"
Set-Location $Root
if ($DryRun) {
Write-Host '[DryRun] git checkout main'
Write-Host '[DryRun] git reset --hard dev'
} else {
git checkout main
git reset --hard dev
}
Write-Step "Production-Build (Kommentare entfernen, JS obfuscaten)"
Set-Location $BuildDir
if ($DryRun) {
Write-Host '[DryRun] npm run build:in-place'
} else {
& $NpmExe run build:in-place
if ($LASTEXITCODE -ne 0) { throw "Production-Build fehlgeschlagen" }
}
Write-Step "Production-Build committen"
Set-Location $Root
if ($DryRun) {
Write-Host '[DryRun] git add -A'
Write-Host ('[DryRun] git commit -m "' + $Message + '"')
} else {
git add -A
$null = git diff --cached --quiet
if ($LASTEXITCODE -eq 0) {
Write-Warning "Keine Build-Aenderungen - nichts zu committen."
} else {
git commit -m $Message
}
}
if ($Push) {
Write-Step "Push nach origin/main"
if ($DryRun) {
Write-Host '[DryRun] git push origin main'
} else {
git push origin main
}
} else {
Write-Host "Hinweis: Ohne -Push wurde nur lokal auf main gebaut." -ForegroundColor Yellow
}
Write-Step "Zurueck auf $OriginalBranch"
if (-not $DryRun) {
if ([string]::IsNullOrWhiteSpace($OriginalBranch)) {
git checkout dev
} else {
git checkout $OriginalBranch
}
}
Write-Host ""
Write-Host "Production-Release abgeschlossen." -ForegroundColor Green
if (-not $Push -and -not $DryRun) {
Write-Host "Zum Veroeffentlichen: git push origin main" -ForegroundColor Yellow
}
}
catch {
Write-Host ""
Write-Host ('FEHLER: ' + $_.Exception.Message) -ForegroundColor Red
Set-Location $Root
if ($OriginalBranch -and -not $DryRun) {
git checkout $OriginalBranch 2>$null
}
exit 1
}

View File

@@ -1,187 +0,0 @@
#!/usr/bin/env bash
#
# Erstellt einen Production-Build und veröffentlicht ihn auf den Branch main.
#
# 1. Wechselt auf main und setzt ihn auf den Stand von dev
# 2. Entfernt Kommentare, minifiziert CSS, obfuskiert JavaScript
# 3. Committet und pusht main (optional)
# 4. Wechselt zurück auf den ursprünglichen Branch (dev bleibt unverändert)
#
# Nutzung:
# ./scripts/publish-to-main.sh
# ./scripts/publish-to-main.sh --push
# ./scripts/publish-to-main.sh --dry-run
# ./scripts/publish-to-main.sh --allow-dirty --message "chore(release): v1.2"
#
set -euo pipefail
PUSH=false
DRY_RUN=false
ALLOW_DIRTY=false
MESSAGE=""
usage() {
cat <<'EOF'
Usage: publish-to-main.sh [OPTIONS]
Options:
--push Push nach origin/main
--dry-run Git-Schritte nur anzeigen (Build wird ausgeführt)
--allow-dirty Uncommittete Änderungen erlauben
--message TEXT Commit-Nachricht
-h, --help Hilfe anzeigen
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--push)
PUSH=true
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
--allow-dirty)
ALLOW_DIRTY=true
shift
;;
--message)
MESSAGE="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unbekannte Option: $1" >&2
usage >&2
exit 1
;;
esac
done
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BUILD_DIR="$ROOT/scripts/build"
ORIGINAL_BRANCH=""
step() {
echo ""
echo "==> $1"
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "FEHLER: '$1' nicht gefunden. Bitte installieren und PATH setzen." >&2
exit 1
fi
}
ensure_git_clean() {
if [[ -n "$(git -C "$ROOT" status --porcelain)" ]]; then
echo "FEHLER: Uncommittete Änderungen. Bitte zuerst committen oder stashen." >&2
exit 1
fi
}
cleanup_on_error() {
echo ""
echo "FEHLER: Abgebrochen." >&2
cd "$ROOT" || true
if [[ -n "$ORIGINAL_BRANCH" && "$DRY_RUN" == false ]]; then
git checkout "$ORIGINAL_BRANCH" 2>/dev/null || true
fi
}
trap cleanup_on_error ERR
require_command node
require_command npm
require_command git
cd "$ROOT"
if [[ "$ALLOW_DIRTY" == false ]]; then
ensure_git_clean
else
echo "WARNUNG: --allow-dirty aktiv uncommittete Änderungen werden mit veröffentlicht." >&2
fi
ORIGINAL_BRANCH="$(git branch --show-current | tr -d '[:space:]')"
if [[ "$ORIGINAL_BRANCH" != "dev" ]]; then
echo "WARNUNG: Empfohlen auf Branch 'dev' zu starten (aktuell: ${ORIGINAL_BRANCH:-detached})" >&2
fi
if [[ -z "$MESSAGE" ]]; then
MESSAGE="chore(release): production build $(date '+%Y-%m-%d %H:%M')"
fi
step "Installiere Build-Abhängigkeiten"
cd "$BUILD_DIR"
if [[ "$DRY_RUN" == false ]]; then
npm ci --no-fund --no-audit
fi
step "Wechsle auf main und synchronisiere mit dev"
cd "$ROOT"
if [[ "$DRY_RUN" == true ]]; then
echo "[DryRun] git checkout main"
echo "[DryRun] git reset --hard dev"
else
git checkout main
git reset --hard dev
fi
step "Production-Build (Kommentare entfernen, JS obfuscaten)"
cd "$BUILD_DIR"
if [[ "$DRY_RUN" == true ]]; then
echo "[DryRun] npm run build:in-place"
else
npm run build:in-place
fi
step "Production-Build committen"
cd "$ROOT"
if [[ "$DRY_RUN" == true ]]; then
echo "[DryRun] git add -A"
echo "[DryRun] git commit -m \"$MESSAGE\""
else
git add -A
if git diff --cached --quiet; then
echo "WARNUNG: Keine Build-Änderungen nichts zu committen." >&2
else
git commit -m "$MESSAGE"
fi
fi
if [[ "$PUSH" == true ]]; then
step "Push nach origin/main"
if [[ "$DRY_RUN" == true ]]; then
echo "[DryRun] git push origin main"
else
git push origin main
fi
else
echo "Hinweis: Ohne --push wurde nur lokal auf main gebaut."
fi
step "Zurück auf ${ORIGINAL_BRANCH:-dev}"
if [[ "$DRY_RUN" == false ]]; then
if [[ -n "$ORIGINAL_BRANCH" ]]; then
git checkout "$ORIGINAL_BRANCH"
else
git checkout dev
fi
fi
trap - ERR
echo ""
echo "Production-Release abgeschlossen."
if [[ "$PUSH" == false && "$DRY_RUN" == false ]]; then
echo "Zum Veröffentlichen: git push origin main"
fi

View File

@@ -1,46 +0,0 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Erstellt ein Production-Bundle unter dist/ (ohne Branch-Wechsel).
#>
[CmdletBinding()]
param(
[switch]$InPlace
)
$ErrorActionPreference = "Stop"
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$BuildDir = Join-Path $Root "scripts\build"
function Resolve-NodeTool([string]$ToolName) {
$command = Get-Command $ToolName -ErrorAction SilentlyContinue
if ($command) { return $command.Source }
$candidates = @(
(Join-Path $env:ProgramFiles "nodejs\$ToolName.cmd"),
(Join-Path ${env:ProgramFiles(x86)} "nodejs\$ToolName.cmd")
)
foreach ($candidate in $candidates) {
if (Test-Path $candidate) { return $candidate }
}
return $null
}
$npm = Resolve-NodeTool "npm"
if (-not $npm) {
throw "npm nicht gefunden. Bitte Node.js installieren."
}
Set-Location $BuildDir
& $npm ci --no-fund --no-audit
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
if ($InPlace) {
& $npm run build:in-place
} else {
& $npm run build
}
exit $LASTEXITCODE

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env bash
#
# Erstellt ein Production-Bundle unter dist/ (ohne Branch-Wechsel).
#
# Nutzung:
# ./scripts/run-build.sh
# ./scripts/run-build.sh --in-place
#
set -euo pipefail
IN_PLACE=false
usage() {
cat <<'EOF'
Usage: run-build.sh [OPTIONS]
Options:
--in-place Build direkt im Repository (statt dist/)
-h, --help Hilfe anzeigen
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--in-place)
IN_PLACE=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unbekannte Option: $1" >&2
usage >&2
exit 1
;;
esac
done
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BUILD_DIR="$ROOT/scripts/build"
if ! command -v npm >/dev/null 2>&1; then
echo "FEHLER: npm nicht gefunden. Bitte Node.js installieren." >&2
exit 1
fi
cd "$BUILD_DIR"
npm ci --no-fund --no-audit
if [[ "$IN_PLACE" == true ]]; then
npm run build:in-place
else
npm run build
fi

View 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
}