Compare commits

19 Commits

Author SHA1 Message Date
smueller
56f3f90d95 Merge branch 'dev' into ci
All checks were successful
Release Build (ci → main) / release-build (push) Successful in 25s
2026-06-02 16:23:30 +02:00
smueller
c6b483ca25 fix(it-dienstleistungen): zentriere zielgruppen-karten mit 1:1 layout
stabilisiert das layout der zielgruppen-karten auf der it-dienstleistungen-seite durch seitenlokale overrides ohne auswirkungen auf andere seiten.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 16:22:21 +02:00
smueller
0df5dc9b57 fix(footer): keep footer sections in one desktop row
All checks were successful
Release Build (ci → main) / release-build (push) Successful in 28s
Adjust footer grid and spacing so all footer sections stay in a single row on desktop while preserving responsive breakpoints for tablets and mobile.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 16:01:00 +02:00
smueller
9c92df4ae4 feat(footer): add andere dienste section with hexadns and hexa-mail
All checks were successful
Release Build (ci → main) / release-build (push) Successful in 24s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 08:49:35 +02:00
smueller
e0bcf15121 fix(styles): scope legal breadcrumb colors to legal pages
All checks were successful
Release Build (ci → main) / release-build (push) Successful in 45s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 08:36:00 +02:00
smueller
1d4b751316 chore(git): add conventional commit hooks and template
All checks were successful
Release Build (ci → main) / release-build (push) Successful in 27s
Add commit-msg hook, .gitmessage, and setup script. Ignore .cursor/ in gitignore and lower contact rate limit to 5 per hour.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 11:03:09 +02:00
smueller
186b5ae199 Implement product visibility management: Added configuration for product visibility in navigation and footer, along with helper functions to determine visibility and generate HTML attributes. Updated footer, header, and index files to utilize these new functions, enhancing the user interface by conditionally displaying products based on their visibility settings. 2026-05-29 10:52:56 +02:00
smueller
bbc3cbae4e Update README and workflows for release process: Clarified branch usage and workflow steps in README.md, emphasizing the new ci branch for integration. Adjusted Gitea and GitHub workflows to reflect the change from dev to ci for triggering release builds, ensuring a streamlined and consistent obfuscation process for production assets.
All checks were successful
Release Build (ci → main) / release-build (push) Successful in 31s
2026-05-29 10:42:08 +02: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
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
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
67fbc68d45 Refactor email handling in contact form: Transitioned from PHPMailer to native PHP mail() function, removing Composer dependencies. Updated documentation to reflect changes in email configuration and setup. Enhanced security features including CSRF protection and input validation. Adjusted product pricing and specifications in backend configuration files. 2026-05-27 13:04:52 +02:00
smueller
d44aaa197b Update publish-to-main.ps1 for improved clarity and consistency: Corrected German translations for various parameters and messages, ensuring uniformity in language usage. Added AllowDirty parameter to enable handling of uncommitted changes during the production build process. 2026-05-27 12:36:20 +02:00
smueller
ebf6f82bb6 Enhance API functionality and security: Added rate limiting and domain validation across multiple API endpoints, improved error handling for missing or invalid parameters, and refactored email handling in contact form for better security and maintainability. Updated README.md with production build instructions and prerequisites. 2026-05-22 14:50:20 +02:00
𝓜𝓪𝓬𝓮™
5f5be4a4cb Enhance Content Security Policy in .htaccess: Updated img-src and connect-src directives to include Google Tag Manager and additional Google services, improving security while ensuring compatibility with analytics tools. 2026-04-09 15:49:03 +02:00
𝓜𝓪𝓬𝓮™
73ba422655 Update .htaccess for enhanced security and Google Tag Assistant compatibility: Removed X-Frame-Options header and adjusted Content Security Policy to allow Google Tag Manager and Google Analytics, ensuring compliance with security standards while maintaining functionality. 2026-04-09 15:46:11 +02:00
43 changed files with 2285 additions and 868 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

@@ -46,7 +46,7 @@ Eine moderne und umfangreiche Website für das Hosting-Unternehmen HexaHost.de a
- **HTML5** - Semantisches Markup - **HTML5** - Semantisches Markup
- **CSS3** - Moderne Styles mit Custom Properties - **CSS3** - Moderne Styles mit Custom Properties
- **Vanilla JavaScript** - Keine Framework-Dependencies - **Vanilla JavaScript** - Keine Framework-Dependencies
- **PHPMailer** - E-Mail-Versand via SMTP - **Native PHP mail()** - E-Mail-Versand ohne externe Abhängigkeiten
- **Glassmorphism Design** - Moderne Glaseffekte - **Glassmorphism Design** - Moderne Glaseffekte
- **CSS Grid & Flexbox** - Responsive Layouts - **CSS Grid & Flexbox** - Responsive Layouts
- **Inter Font** - Moderne Typografie - **Inter Font** - Moderne Typografie
@@ -73,7 +73,6 @@ HexaHost-Frontend/
│ ├── sitemap.xml # SEO Sitemap │ ├── sitemap.xml # SEO Sitemap
│ ├── favicon.svg # Website Icon │ ├── favicon.svg # Website Icon
│ ├── .htaccess # Apache Konfiguration │ ├── .htaccess # Apache Konfiguration
│ ├── composer.json # PHP Dependencies
│ ├── config/ # ⬅ vom Backend │ ├── config/ # ⬅ vom Backend
│ │ ├── config.php # Allgemeine Konfiguration │ │ ├── config.php # Allgemeine Konfiguration
│ │ └── mail-config.php # E-Mail-Konfiguration │ │ └── mail-config.php # E-Mail-Konfiguration
@@ -127,7 +126,6 @@ HexaHost-Frontend/
### Voraussetzungen ### Voraussetzungen
- PHP 8.0 oder höher - PHP 8.0 oder höher
- Composer (für PHPMailer)
- Apache mit mod_rewrite (für .htaccess) - Apache mit mod_rewrite (für .htaccess)
- [HexaHost-Backend](../HexaHost-Backend) Repository - [HexaHost-Backend](../HexaHost-Backend) Repository
@@ -147,24 +145,18 @@ HexaHost-Frontend/
cp -r HexaHost-Backend/includes/* HexaHost-Frontend/public/includes/ cp -r HexaHost-Backend/includes/* HexaHost-Frontend/public/includes/
``` ```
3. **PHP Dependencies installieren** 3. **Konfiguration anpassen**
```bash
cd HexaHost-Frontend/public
composer install
```
4. **Konfiguration anpassen**
```bash ```bash
# mail-config.php mit SMTP-Daten bearbeiten # mail-config.php mit SMTP-Daten bearbeiten
nano config/mail-config.php nano config/mail-config.php
``` ```
5. **Lokaler Development Server** 4. **Lokaler Development Server**
```bash ```bash
php -S localhost:8000 -t public php -S localhost:8000 -t public
``` ```
6. **Website öffnen** 5. **Website öffnen**
``` ```
http://localhost:8000 http://localhost:8000
``` ```
@@ -172,6 +164,53 @@ HexaHost-Frontend/
### Produktion ### Produktion
Für den Produktivbetrieb `public/` als Webroot konfigurieren. Für den Produktivbetrieb `public/` als Webroot konfigurieren.
### Production-Build & Veröffentlichung
| Branch | Zweck |
|--------|--------|
| **`dev`** | Entwicklung (lesbarer Code, Kommentare) |
| **`ci`** | Integration (du mergst `dev` hierher) |
| **`main`** | Release/Produktion (obfuskiert, gehashte Assets — nur per Pipeline) |
**Ablauf: `dev` → `ci` → `main`**
1. Auf **`dev`** entwickeln und pushen
2. **`dev` nach `ci` mergen** (manuell, z. B. in Gitea oder lokal)
3. **`ci` pushen** → startet `.gitea/workflows/obfuscate-main.yml`
4. Pipeline obfuskiert im Runner-Workspace und publiziert nach **`main`**
```powershell
# Nach fertigen Änderungen auf dev:
git checkout ci
git pull origin ci
git merge dev
git push origin ci
```
Bei jedem Push auf **`ci`**:
1. Checkout von `ci` im temporären Runner-Workspace
2. Obfuscation-Build (`scripts/obfuscate_release.py --hash-assets`)
3. Ergebnis nach `main` pushen (Bot-Commit mit `[skip ci]`)
**Nicht** `dev` oder `ci` direkt nach `main` mergen. Der Branch **`ci` bleibt lesbar** — Obfuscation wird nur nach `main` publiziert.
`ci`-Branch einmalig anlegen (falls noch nicht vorhanden): `git checkout -b ci dev && git push -u origin ci`
Der Build führt aus:
- Entfernen von Kommentaren (inkl. Block-Kommentaren) in PHP/JS/CSS
- Minify + Obfuscate für JavaScript
- Minify für CSS
- Kein Source-Map-Output
- Hashing von JS/CSS-Dateinamen + automatische Referenz-Anpassung
Lokal testen (nur in Kopie, nicht committen):
```bash
python scripts/obfuscate_release.py --root . --hash-assets
```
## 🔗 Backend-Integration ## 🔗 Backend-Integration
Das Backend-Repository enthält folgende wiederverwendbare Komponenten: Das Backend-Repository enthält folgende wiederverwendbare Komponenten:
@@ -187,13 +226,11 @@ Detaillierte Informationen zu den Backend-Komponenten finden Sie in der [Backend
## 📧 E-Mail-Konfiguration ## 📧 E-Mail-Konfiguration
Die E-Mail-Funktionalität benötigt eine SMTP-Konfiguration in `public/config/mail-config.php`: Die E-Mail-Funktionalität nutzt die native PHP-`mail()`-Funktion. In `public/config/mail-config.php` müssen mindestens Absender und Empfänger gesetzt werden:
```php ```php
define('SMTP_HOST', 'mail.example.com'); define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
define('SMTP_PORT', 587); define('SMTP_TO_EMAIL', 'info@hexahost.de');
define('SMTP_USER', 'noreply@hexahost.de');
define('SMTP_PASS', 'your-password');
``` ```
Siehe `docs/README-EMAIL-SETUP.md` für detaillierte Anweisungen. Siehe `docs/README-EMAIL-SETUP.md` für detaillierte Anweisungen.
@@ -220,7 +257,7 @@ Siehe `docs/README-EMAIL-SETUP.md` für detaillierte Anweisungen.
### Kontaktformular ### Kontaktformular
- Server-seitige Validierung - Server-seitige Validierung
- E-Mail-Versand via SMTP - E-Mail-Versand via native PHP mail()
- CSRF-Schutz - CSRF-Schutz
- Auto-Fill basierend auf URL-Parametern - Auto-Fill basierend auf URL-Parametern
- FAQ-Sektion mit Accordion - FAQ-Sektion mit Accordion

8
backend/.htaccess Normal file
View File

@@ -0,0 +1,8 @@
# Direkten Zugriff auf Backend-Dateien verhindern (Document Root = public/)
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>

View File

@@ -7,6 +7,8 @@
* Verwendung: GET /api/dns-lookup.php?domain=example.com * Verwendung: GET /api/dns-lookup.php?domain=example.com
*/ */
require_once __DIR__ . '/../includes/api-helpers.php';
// CORS Headers für Frontend-Zugriff // 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: *');
@@ -19,26 +21,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit; exit;
} }
// Nur GET-Anfragen erlauben
if ($_SERVER['REQUEST_METHOD'] !== 'GET') { if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405); http_response_code(405);
echo json_encode(['error' => 'Nur GET-Anfragen erlaubt']); echo json_encode(['error' => 'Nur GET-Anfragen erlaubt']);
exit; exit;
} }
// Domain-Parameter prüfen if (!checkApiRateLimit('dns-lookup')) {
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : ''; rejectApiRateLimit();
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => 'Domain-Parameter fehlt']);
exit;
} }
// Domain validieren (einfache Prüfung) $domain = getValidatedDomainParam();
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
if ($domain === null) {
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Ungültiges Domain-Format']); echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
exit; exit;
} }

View File

@@ -7,6 +7,8 @@
* Verwendung: GET /api/dns-propagation.php?domain=example.com&type=A * Verwendung: GET /api/dns-propagation.php?domain=example.com&type=A
*/ */
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8'); header('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');
@@ -17,6 +19,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit; exit;
} }
if (!checkApiRateLimit('dns-propagation')) {
rejectApiRateLimit();
}
// Öffentliche DNS-Server für Propagation-Check // Ö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'],
@@ -29,18 +35,12 @@ $dnsServers = [
['name' => 'Level3', 'ip' => '4.2.2.1', 'location' => 'USA'], ['name' => 'Level3', 'ip' => '4.2.2.1', 'location' => 'USA'],
]; ];
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : ''; $domain = getValidatedDomainParam();
$type = isset($_GET['type']) ? strtoupper(trim($_GET['type'])) : 'A'; $type = isset($_GET['type']) ? strtoupper(trim($_GET['type'])) : 'A';
if (empty($domain)) { if ($domain === null) {
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Domain-Parameter fehlt']); echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
exit;
}
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) {
http_response_code(400);
echo json_encode(['error' => 'Ungültiges Domain-Format']);
exit; exit;
} }
@@ -100,7 +100,12 @@ function queryDnsServer(string $domain, string $type, string $server): array {
$records = []; $records = [];
// Versuche zuerst dig zu verwenden (genauer) // Versuche zuerst dig zu verwenden (genauer)
$digResult = @shell_exec("dig @{$server} {$domain} {$type} +short +time=2 +tries=1 2>/dev/null"); $digResult = @shell_exec(
'dig @' . escapeshellarg($server) . ' '
. escapeshellarg($domain) . ' '
. escapeshellarg($type)
. ' +short +time=2 +tries=1 2>/dev/null'
);
if ($digResult !== null && trim($digResult) !== '') { if ($digResult !== null && trim($digResult) !== '') {
$lines = array_filter(explode("\n", trim($digResult))); $lines = array_filter(explode("\n", trim($digResult)));

View File

@@ -7,6 +7,8 @@
* Verwendung: GET /api/ping-check.php?domain=example.com * Verwendung: GET /api/ping-check.php?domain=example.com
*/ */
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8'); header('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');
@@ -17,21 +19,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit; exit;
} }
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : ''; if (!checkApiRateLimit('ping-check')) {
rejectApiRateLimit();
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => 'Domain-Parameter fehlt']);
exit;
} }
// Protokoll und Pfad entfernen $domain = getValidatedDomainParam();
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
$domain = explode('/', $domain)[0];
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) { if ($domain === null) {
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Ungültiges Domain-Format']); echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
exit; exit;
} }
@@ -99,7 +95,8 @@ function checkIcmpPing(string $domain): array {
]; ];
// Versuche ping-Kommando // Versuche ping-Kommando
$pingResult = @shell_exec("ping -c 3 -W 2 {$domain} 2>/dev/null"); $safeDomain = escapeshellarg($domain);
$pingResult = @shell_exec("ping -c 3 -W 2 {$safeDomain} 2>/dev/null");
if ($pingResult) { if ($pingResult) {
// Prüfe auf erfolgreiche Antworten // Prüfe auf erfolgreiche Antworten

View File

@@ -7,6 +7,8 @@
* Verwendung: GET /api/reverse-dns.php?ip=8.8.8.8 * Verwendung: GET /api/reverse-dns.php?ip=8.8.8.8
*/ */
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8'); header('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');
@@ -17,6 +19,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit; exit;
} }
if (!checkApiRateLimit('reverse-dns')) {
rejectApiRateLimit();
}
$ip = isset($_GET['ip']) ? trim($_GET['ip']) : ''; $ip = isset($_GET['ip']) ? trim($_GET['ip']) : '';
if (empty($ip)) { if (empty($ip)) {

View File

@@ -7,6 +7,8 @@
* Verwendung: GET /api/ssl-check.php?domain=example.com * Verwendung: GET /api/ssl-check.php?domain=example.com
*/ */
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8'); header('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');
@@ -17,22 +19,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit; exit;
} }
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : ''; if (!checkApiRateLimit('ssl-check')) {
rejectApiRateLimit();
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => 'Domain-Parameter fehlt']);
exit;
} }
// Protokoll und Pfad entfernen $domain = getValidatedDomainParam();
$domain = preg_replace('/^(https?:\/\/)?/', '', $domain);
$domain = explode('/', $domain)[0];
$domain = explode(':', $domain)[0]; // Port entfernen
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$/', $domain)) { if ($domain === null) {
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Ungültiges Domain-Format']); echo json_encode(['error' => empty($_GET['domain']) ? 'Domain-Parameter fehlt' : 'Ungültiges Domain-Format']);
exit; exit;
} }

View File

@@ -7,6 +7,8 @@
* Verwendung: GET /api/whois-lookup.php?domain=example.com * Verwendung: GET /api/whois-lookup.php?domain=example.com
*/ */
require_once __DIR__ . '/../includes/api-helpers.php';
header('Content-Type: application/json; charset=utf-8'); header('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');
@@ -17,7 +19,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit; exit;
} }
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : ''; if (!checkApiRateLimit('whois-lookup')) {
rejectApiRateLimit();
}
$domain = isset($_GET['domain']) ? trim((string) $_GET['domain']) : '';
if (empty($domain)) { if (empty($domain)) {
http_response_code(400); http_response_code(400);

View File

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

View File

@@ -2,55 +2,18 @@
/** /**
* HexaHost.de Mail Configuration * HexaHost.de Mail Configuration
* *
* Bitte passen Sie die folgenden SMTP-Einstellungen an Ihre E-Mail-Provider an. * Dieses Projekt versendet E-Mails nativ über PHP mail().
* * Es sind keine externen Bibliotheken oder Composer-Installationen erforderlich.
* Beispiele für gängige Provider:
*
* Gmail:
* - SMTP_HOST = 'smtp.gmail.com'
* - SMTP_PORT = 587
* - SMTP_USERNAME = 'ihre-email@gmail.com'
* - SMTP_PASSWORD = 'ihr-app-passwort'
*
* Outlook/Hotmail:
* - SMTP_HOST = 'smtp-mail.outlook.com'
* - SMTP_PORT = 587
*
* GMX:
* - SMTP_HOST = 'mail.gmx.net'
* - SMTP_PORT = 587
*
* Web.de:
* - SMTP_HOST = 'smtp.web.de'
* - SMTP_PORT = 587
*
* 1&1:
* - SMTP_HOST = 'smtp.1und1.de'
* - SMTP_PORT = 587
*
* Strato:
* - SMTP_HOST = 'smtp.strato.de'
* - SMTP_PORT = 587
*
* Ionos:
* - SMTP_HOST = 'smtp.ionos.de'
* - SMTP_PORT = 587
*/ */
// SMTP Server Einstellungen
define('SMTP_HOST', 'smtp.ihre-domain.de'); // Ihr SMTP-Server
define('SMTP_PORT', 587); // SMTP-Port (meist 587 oder 465)
define('SMTP_USERNAME', 'kontakt@ihre-domain.de'); // Ihr SMTP-Benutzername
define('SMTP_PASSWORD', 'ihr-smtp-passwort'); // Ihr SMTP-Passwort
// E-Mail Adressen // E-Mail Adressen
define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de'); // Absender-E-Mail (muss zu SMTP_USERNAME passen) define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de'); // Absender-E-Mail
define('SMTP_TO_EMAIL', 'info@hexahost.de'); // Empfänger-E-Mail für Kontaktformular define('SMTP_TO_EMAIL', 'info@hexahost.de'); // Empfänger-E-Mail für Kontaktformular
// Sicherheitseinstellungen // Sicherheitseinstellungen
define('ENABLE_CSRF_PROTECTION', true); // CSRF-Schutz aktivieren define('ENABLE_CSRF_PROTECTION', true); // CSRF-Schutz aktivieren
define('ENABLE_RATE_LIMITING', true); // Rate-Limiting aktivieren define('ENABLE_RATE_LIMITING', true); // Rate-Limiting aktivieren
define('MAX_REQUESTS_PER_HOUR', 10); // Max. Anfragen pro Stunde define('MAX_REQUESTS_PER_HOUR', 5); // Max. Anfragen pro Stunde
// Spam-Schutz Einstellungen // Spam-Schutz Einstellungen
define('ENABLE_SPAM_PROTECTION', true); // Spam-Schutz aktivieren define('ENABLE_SPAM_PROTECTION', true); // Spam-Schutz aktivieren
@@ -87,11 +50,6 @@ define('BLACKLISTED_EMAILS', [
// 'test@test.com' // 'test@test.com'
]); ]);
// Validierung der Konfiguration
if (!defined('SMTP_HOST') || !defined('SMTP_USERNAME') || !defined('SMTP_PASSWORD')) {
die('SMTP-Konfiguration ist unvollständig. Bitte überprüfen Sie die mail-config.php');
}
// Überprüfung der E-Mail-Adressen // Überprüfung der E-Mail-Adressen
if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) { if (!filter_var(SMTP_FROM_EMAIL, FILTER_VALIDATE_EMAIL)) {
die('Ungültige SMTP_FROM_EMAIL Adresse'); die('Ungültige SMTP_FROM_EMAIL Adresse');
@@ -140,24 +98,6 @@ function isValidEmail($email) {
return true; return true;
} }
// CSRF Token generieren (wird in functions.php verwendet)
// Hinweis: Diese Funktion existiert auch in functions.php - hier nur als Fallback
if (!function_exists('generateCSRFToken')) {
function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
}
// CSRF Token validieren
if (!function_exists('validateCSRFToken')) {
function validateCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
}
/** /**
* Hilfsfunktion zum Abrufen der Konfiguration als Array * Hilfsfunktion zum Abrufen der Konfiguration als Array
* Kompatibilität mit contact-handler.php * Kompatibilität mit contact-handler.php
@@ -167,13 +107,6 @@ if (!function_exists('validateCSRFToken')) {
*/ */
function getHexaHostConfig($key = null) { function getHexaHostConfig($key = null) {
$config = [ $config = [
// SMTP Server-Einstellungen
'smtp_host' => SMTP_HOST,
'smtp_port' => SMTP_PORT,
'smtp_username' => SMTP_USERNAME,
'smtp_password' => SMTP_PASSWORD,
'smtp_encryption' => 'tls',
// Absender/Empfänger // Absender/Empfänger
'from_email' => SMTP_FROM_EMAIL, 'from_email' => SMTP_FROM_EMAIL,
'from_name' => 'HexaHost.de Kontaktformular', 'from_name' => 'HexaHost.de Kontaktformular',
@@ -183,6 +116,9 @@ function getHexaHostConfig($key = null) {
// Sicherheit // Sicherheit
'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR, 'max_requests_per_hour' => MAX_REQUESTS_PER_HOUR,
'honeypot_field' => 'website', 'honeypot_field' => 'website',
'enable_csrf' => ENABLE_CSRF_PROTECTION,
'min_message_length' => MIN_MESSAGE_LENGTH,
'max_message_length' => MAX_MESSAGE_LENGTH,
// Debug // Debug
'debug_mode' => DEBUG_MODE, 'debug_mode' => DEBUG_MODE,

View File

@@ -2,7 +2,8 @@
/** /**
* HexaHost.de Produkt-Konfiguration * HexaHost.de Produkt-Konfiguration
* *
* Hier können Sie alle Preise und Produktinformationen zentral verwalten. * Hier können Sie alle Preise, Shop-Links und Produktinformationen zentral verwalten.
* Pro Paket: shop_url (WHMCS/Warenkorb-Link, z. B. https://shop.hexahost.de/cart.php?a=add&pid=123)
* Nach Änderungen: npm run build && npm run deploy * Nach Änderungen: npm run build && npm run deploy
* *
* Verwendung in PHP-Seiten: * Verwendung in PHP-Seiten:
@@ -30,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'],
@@ -49,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'],
@@ -69,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'],
@@ -90,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'],
@@ -132,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'],
@@ -151,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'],
@@ -171,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'],
@@ -192,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'],
@@ -234,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'],
@@ -252,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'],
@@ -272,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'],
@@ -293,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'],
@@ -323,94 +336,106 @@ $PRODUCTS['webhosting'] = [
'name' => 'Webhosting', 'name' => 'Webhosting',
'short_name' => 'Webhosting', 'short_name' => 'Webhosting',
'description' => 'Klassisches Hosting mit PHP, MySQL und SSL', 'description' => 'Klassisches Hosting mit PHP, MySQL und SSL',
'min_price' => '1,99', 'min_price' => '4,99',
'hero_highlight' => 'Alles für Ihre Website', 'hero_highlight' => 'Alles für Ihre Website',
'hero_description' => 'Klassisches Webhosting mit allem, was Sie für eine erfolgreiche Website benötigen. PHP, MySQL, SSL-Zertifikate und E-Mail-Postfächer - alles inklusive.', 'hero_description' => 'Klassisches Webhosting mit allem, was Sie für eine erfolgreiche Website benötigen. Plesk, PHP, SSL-Zertifikate und E-Mail-Postfächer - alles inklusive.',
'packages_title' => 'Webhosting Pakete', 'packages_title' => 'Webhosting Pakete',
'packages_description' => 'Von der ersten Website bis zum professionellen Online-Shop', 'packages_description' => 'Von der ersten Website bis zum professionellen Online-Shop',
'cta_title' => 'Bereit für Ihr Webhosting?', 'cta_title' => 'Bereit für Ihr Webhosting?',
'cta_description' => 'Starten Sie noch heute mit professionellem Webhosting', 'cta_description' => 'Starten Sie noch heute mit professionellem Webhosting',
'page_title' => 'Webhosting - Klassisches Hosting für Websites | HexaHost.de', 'page_title' => 'Webhosting - Klassisches Hosting für Websites | HexaHost.de',
'page_description' => 'Webhosting mit PHP, MySQL und SSL-Zertifikaten. Klassisches Hosting für Websites ab 1,99€/Monat bei HexaHost.de', 'page_description' => 'Webhosting mit Plesk, PHP und SSL-Zertifikaten. Klassisches Hosting für Websites ab 4,99€/Monat bei HexaHost.de',
'packages' => [ 'packages' => [
'starter' => [ 'starter' => [
'name' => 'Webhosting Starter', 'name' => 'Webhosting Starter',
'price' => '1,99', 'price' => '4,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-starter',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Webspace', 'value' => '5 GB'], ['label' => 'Webspace', 'value' => '10 GB'],
['label' => 'Domains', 'value' => '1'], ['label' => 'Domains inkl.', 'value' => '1'],
['label' => 'E-Mail-Postfächer', 'value' => '5'], ['label' => 'Subdomains', 'value' => '5'],
['label' => 'Datenbanken', 'value' => '1 MySQL'], ['label' => 'Domain-Alias', 'value' => '2'],
['label' => 'Traffic', 'value' => '10 GB'], ['label' => 'E-Mail-Postfächer', 'value' => '10'],
['label' => 'Datenbanken', 'value' => '2 MySQL'],
['label' => 'Traffic', 'value' => '100 GB'],
], ],
'features' => [ 'features' => [
'cPanel/Webmin', 'Perfekt für kleine Websites und Blogs',
'PHP 8.1', 'Plesk',
'SSL-Zertifikat', 'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'E-Mail-Postfächer', 'SSL-Zertifikat (Let\'s Encrypt)',
'MySQL Datenbank', 'E-Mail-Postfächer à 100MB',
'1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
], ],
], ],
'business' => [ 'business' => [
'name' => 'Webhosting Business', 'name' => 'Webhosting Business',
'price' => '4,99', 'price' => '7,99',
'shop_url' => 'https://shop.hexahost.de/store/webserver/webhosting-business',
'featured' => true, 'featured' => true,
'specs' => [ 'specs' => [
['label' => 'Webspace', 'value' => '20 GB'], ['label' => 'Webspace', 'value' => '30 GB'],
['label' => 'Domains', 'value' => '5'], ['label' => 'Domains inkl.', 'value' => '1'],
['label' => 'E-Mail-Postfächer', 'value' => '25'], ['label' => 'Subdomains', 'value' => '10'],
['label' => 'Domain-Alias', 'value' => '2'],
['label' => 'E-Mail-Postfächer', 'value' => '20'],
['label' => 'Datenbanken', 'value' => '5 MySQL'], ['label' => 'Datenbanken', 'value' => '5 MySQL'],
['label' => 'Traffic', 'value' => '50 GB'], ['label' => 'Traffic', 'value' => '100 GB'],
], ],
'features' => [ 'features' => [
'cPanel/Webmin', 'Perfekt für mittlere Websites und Blogs',
'PHP 8.1', 'Plesk',
'SSL-Zertifikat', 'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'E-Mail-Postfächer', 'SSL-Zertifikat (Let\'s Encrypt)',
'MySQL Datenbanken', 'E-Mail-Postfächer à 100MB',
'Backup-Service', '1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
], ],
], ],
'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'],
['label' => 'Domains', 'value' => 'Unbegrenzt'], ['label' => 'Domains inkl.', 'value' => '3'],
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => '100'], ['label' => 'E-Mail-Postfächer', 'value' => '100'],
['label' => 'Datenbanken', 'value' => 'Unbegrenzt'], ['label' => 'Datenbanken', 'value' => '20 MySQL'],
['label' => 'Traffic', 'value' => '200 GB'], ['label' => 'Traffic', 'value' => '100 GB'],
], ],
'features' => [ 'features' => [
'cPanel/Webmin', 'Perfekt für größere Websites und Blogs',
'PHP 8.1', 'Plesk',
'SSL-Zertifikat', 'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'E-Mail-Postfächer', 'SSL-Zertifikat (Let\'s Encrypt)',
'MySQL Datenbanken', 'E-Mail-Postfächer à 100MB',
'Backup-Service', '1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
'Priority Support',
], ],
], ],
'enterprise' => [ 'enterprise' => [
'name' => 'Webhosting Enterprise', 'name' => 'Webhosting Enterprise',
'price' => '19,99', 'price' => '29,99',
'shop_url' => '',
'featured' => false, 'featured' => false,
'specs' => [ 'specs' => [
['label' => 'Webspace', 'value' => '100 GB'], ['label' => 'Webspace', 'value' => '200 GB'],
['label' => 'Domains', 'value' => 'Unbegrenzt'], ['label' => 'Domains inkl.', 'value' => '5'],
['label' => 'Subdomains', 'value' => 'Unbegrenzt'],
['label' => 'Domain-Alias', 'value' => 'Unbegrenzt'],
['label' => 'E-Mail-Postfächer', 'value' => 'Unbegrenzt'], ['label' => 'E-Mail-Postfächer', 'value' => 'Unbegrenzt'],
['label' => 'Datenbanken', 'value' => 'Unbegrenzt'], ['label' => 'Datenbanken', 'value' => '50 MySQL'],
['label' => 'Traffic', 'value' => '500 GB'], ['label' => 'Traffic', 'value' => '1 TB'],
], ],
'features' => [ 'features' => [
'cPanel/Webmin', 'Perfekt für Enterprise-Websites und Blogs',
'PHP 8.1', 'Plesk',
'SSL-Zertifikat', 'PHP 8.4 (FastCGI), Git, WP Toolkit, Composer',
'E-Mail-Postfächer', 'SSL-Zertifikat (Let\'s Encrypt)',
'MySQL Datenbanken', 'E-Mail-Postfächer à 100MB',
'Backup-Service', '1-Klick-Apps - WordPress, Joomla, TYPO3, MediaWiki u. v. m.',
'Priority Support', 'Priority Support',
'Individuelle Konfiguration', 'Individuelle Konfiguration',
], ],
@@ -418,10 +443,43 @@ $PRODUCTS['webhosting'] = [
], ],
]; ];
// Sichtbarkeit in Navigation, Footer und auf der Startseite (Seiten bleiben per URL erreichbar)
$PRODUCT_VISIBILITY = [
'vpc' => false,
'vps' => false,
'mail-gateway' => false,
'webhosting' => true,
];
// ============================================================================ // ============================================================================
// HILFSFUNKTIONEN // HILFSFUNKTIONEN
// ============================================================================ // ============================================================================
/**
* Prüft, ob eine Produktkategorie in der Navigation angezeigt wird
*/
function isProductVisible(string $productId): bool {
global $PRODUCT_VISIBILITY;
return $PRODUCT_VISIBILITY[$productId] ?? true;
}
/**
* HTML hidden-Attribut für ausgeblendete Produktkategorien
*/
function productHiddenAttr(string $productId): string {
return isProductVisible($productId) ? '' : ' hidden';
}
/**
* Aktive Navigationsseiten für sichtbare Produktkategorien
*
* @return string[]
*/
function getVisibleProductPageIds(): array {
global $PRODUCT_VISIBILITY;
return array_keys(array_filter($PRODUCT_VISIBILITY, static fn(bool $visible): bool => $visible));
}
/** /**
* Alle Produkte abrufen * Alle Produkte abrufen
*/ */
@@ -477,6 +535,38 @@ function formatPrice($price, $withCurrency = true) {
return $withCurrency ? $price . '€' : $price; return $withCurrency ? $price . '€' : $price;
} }
/**
* Bestell-Link für ein Paket (Online-Shop oder Kontaktformular)
*/
function getOrderUrl($productId, $packageId) {
$package = getPackage($productId, $packageId);
if ($package && !empty($package['shop_url'])) {
return $package['shop_url'];
}
return sprintf('contact.php?package=%s-%s', $productId, $packageId);
}
/**
* Bestell-Link für CTA (beliebtes Paket oder erstes Paket)
*/
function getProductOrderUrl($productId) {
$packages = getProductPackages($productId);
foreach ($packages as $packageId => $package) {
if (!empty($package['featured'])) {
return getOrderUrl($productId, $packageId);
}
}
$firstPackageId = array_key_first($packages);
if ($firstPackageId !== null) {
return getOrderUrl($productId, $firstPackageId);
}
return sprintf('contact.php?product=%s', $productId);
}
/** /**
* Generiert HTML für eine Paket-Karte * Generiert HTML für eine Paket-Karte
*/ */
@@ -514,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,
@@ -522,8 +612,7 @@ function renderPackageCard($productId, $packageId, $package) {
$package['price'], $package['price'],
$specsHtml, $specsHtml,
$featuresHtml, $featuresHtml,
$productId, htmlspecialchars(getOrderUrl($productId, $packageId), ENT_QUOTES, 'UTF-8')
$packageId
); );
} }

View File

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

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">
@@ -160,7 +167,7 @@
<script src="/assets/js/cookie-consent.js" defer></script> <script src="/assets/js/cookie-consent.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 $script; ?>" defer></script> <script src="<?php echo htmlspecialchars($script, ENT_QUOTES, 'UTF-8'); ?>" defer></script>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?php endif; ?>
</body> </body>

View File

@@ -3,6 +3,8 @@
* Helper functions for HexaHost.de * Helper functions for HexaHost.de
*/ */
require_once __DIR__ . '/../config/products-config.php';
// Sichere Session-Konfiguration // Sichere Session-Konfiguration
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
// Session-Cookie-Sicherheit // Session-Cookie-Sicherheit
@@ -95,4 +97,56 @@ function generateCSRFToken() {
} }
return $_SESSION['csrf_token']; return $_SESSION['csrf_token'];
} }
/**
* CSRF-Token prüfen und nach Erfolg invalidieren (Replay-Schutz)
*/
function validateCSRFToken($token) {
if (!isset($_SESSION['csrf_token']) || !is_string($token)) {
return false;
}
if (!hash_equals($_SESSION['csrf_token'], $token)) {
return false;
}
unset($_SESSION['csrf_token']);
return true;
}
/**
* Werte für E-Mail-Header bereinigen (Header-Injection verhindern)
*/
function sanitizeHeaderValue(string $value): string {
return str_replace(["\r", "\n", "\0"], '', trim($value));
}
/**
* Client-IP für Logging (Cloudflare / vertrauenswürdiger Reverse-Proxy)
*/
function getClientIP(): string {
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])
&& filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$isTrustedProxy = filter_var(
$remoteAddr,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === false;
if ($isTrustedProxy) {
foreach (['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
if (empty($_SERVER[$header])) {
continue;
}
$ip = trim(explode(',', $_SERVER[$header])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $remoteAddr;
}
?> ?>

View File

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

@@ -1,145 +1,34 @@
# HexaHost.de Kontaktformular - Status-Überprüfung # HexaHost.de Kontaktformular - Status
## ✅ Behobene Probleme ## Aktueller Stand
### 1. Merge-Konflikt in contact-handler.php - Kontaktformular und Handler sind funktionsfähig
- **Problem**: Git-Merge-Konflikt machte die Datei unbrauchbar - Versand erfolgt nativ über PHP `mail()`
- **Lösung**: Konflikt aufgelöst, saubere Version erstellt - Keine PHPMailer-/Composer-Abhängigkeit mehr
- **Status**: ✅ Behoben
### 2. CSRF-Token Problem ## Sicherheitsfunktionen
- **Problem**: HTML-Formular versuchte PHP-Code zu verwenden
- **Lösung**: CSRF-Token durch Honeypot-Feld ersetzt
- **Status**: ✅ Behoben
### 3. JavaScript-Merge-Konflikt - CSRF-Token-Prüfung
- **Problem**: Merge-Konflikt in contact.js - Rate Limiting pro IP
- **Lösung**: Konflikt aufgelöst - Honeypot gegen Bots
- **Status**: ✅ Behoben - E-Mail-Validierung und Input-Sanitization
## ⚠️ Noch zu behebende Probleme ## Konfiguration für Produktivbetrieb
### 1. SMTP-Konfiguration Datei: `backend/config/mail-config.php`
- **Problem**: SMTP-Einstellungen sind noch auf Testwerte
- **Aktueller Status**:
```php
'smtp_host' => 'smtp.gmail.com',
'smtp_username' => 'test@hexahost.de',
'smtp_password' => 'your-app-password',
```
- **Erforderlich**: Echte SMTP-Daten eintragen
- **Status**: ⚠️ Zu konfigurieren
### 2. PHPMailer-Installation Zu prüfen:
- **Problem**: Composer ist nicht installiert - `SMTP_FROM_EMAIL` ist eine gültige Absenderadresse
- **Aktueller Status**: Fallback auf native PHP mail() Funktion - `SMTP_TO_EMAIL` ist das richtige Zielpostfach
- **Erforderlich**: Composer installieren und PHPMailer einrichten - `mail()` ist beim Hoster aktiv
- **Status**: ⚠️ Optional (Fallback funktioniert)
## 📧 E-Mail-Funktionalität ## Testempfehlung
### Aktuelle Konfiguration 1. `scripts/test-email.php` ausführen
- **SMTP-Host**: smtp.gmail.com 2. Kontaktformular über `contact.php` absenden
- **Port**: 587 3. Empfang und Darstellung der E-Mail prüfen
- **Verschlüsselung**: TLS
- **Fallback**: Native PHP mail() Funktion
### Sicherheitsfeatures ## Hinweis
- ✅ Rate Limiting (5 Anfragen/Stunde)
- ✅ Honeypot-Schutz
- ✅ Input-Sanitization
- ✅ E-Mail-Validierung
- ✅ Anti-Spam-Headers
### E-Mail-Templates Falls der Versand nicht funktioniert, liegt die Ursache in der Regel an der Server-Mailkonfiguration (MTA/`mail()`), nicht am Formular-Code.
- ✅ HTML-Template mit HexaHost-Design
- ✅ Text-Version als Fallback
- ✅ Responsive Design
- ✅ Strukturierte Darstellung aller Daten
## 🧪 Test-Möglichkeiten
### 1. Test-Datei
- **Datei**: `test-email.php`
- **Zweck**: E-Mail-Funktionalität ohne Formular testen
- **Verwendung**: Im Browser öffnen und "Test-E-Mail senden" klicken
### 2. Kontaktformular
- **Datei**: `contact.html`
- **Zweck**: Vollständiges Formular testen
- **Verwendung**: Formular ausfüllen und absenden
## 🔧 Konfiguration erforderlich
### Für Produktivbetrieb:
1. **SMTP-Daten eintragen** in `config.php`:
```php
'smtp_username' => 'ihre-echte-email@gmail.com',
'smtp_password' => 'ihr-echtes-app-passwort',
'from_email' => 'ihre-echte-email@gmail.com',
'to_email' => 'info@hexahost.de',
```
2. **Composer installieren** (optional):
```bash
# Windows: Composer-Installer herunterladen
# Linux/macOS:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
```
3. **PHPMailer installieren** (optional):
```bash
cd public
composer install
```
## 📊 Funktionsfähigkeit
### ✅ Funktioniert
- Kontaktformular-HTML
- JavaScript-Validierung
- PHP-Backend-Verarbeitung
- Rate Limiting
- Spam-Schutz
- E-Mail-Templates
- Fallback auf native mail() Funktion
### ⚠️ Benötigt Konfiguration
- SMTP-Einstellungen
- PHPMailer (optional)
### ❌ Nicht funktioniert
- E-Mail-Versand ohne SMTP-Konfiguration
## 🚀 Nächste Schritte
1. **SMTP-Konfiguration anpassen**
- Echte SMTP-Daten in `config.php` eintragen
- Test mit `test-email.php`
2. **E-Mail-Funktionalität testen**
- Kontaktformular ausfüllen
- E-Mail-Empfang prüfen
3. **PHPMailer installieren** (optional)
- Composer installieren
- PHPMailer einrichten
4. **DNS-Einträge konfigurieren**
- SPF Record
- DMARC Record
- DKIM (über Mail-Server)
## 📞 Support
Bei Problemen:
1. `test-email.php` verwenden
2. PHP-Error-Logs prüfen
3. SMTP-Konfiguration überprüfen
4. Hosting-Provider kontaktieren
---
**Status**: Kontaktformular ist funktionsfähig, benötigt nur SMTP-Konfiguration für E-Mail-Versand.

View File

@@ -2,172 +2,54 @@
## Übersicht ## Übersicht
Das Kontaktformular von HexaHost.de benötigt eine korrekte SMTP-Konfiguration, um E-Mails zu versenden. Diese Anleitung erklärt, wie Sie die E-Mail-Funktionalität einrichten. Das Kontaktformular nutzt den nativen PHP-Mailversand über `mail()`.
Es wird keine zusätzliche Bibliothek und keine Composer-Installation benötigt.
## Aktuelle Probleme ## Erforderliche Konfiguration
### 1. ✅ Behoben: Merge-Konflikt in contact-handler.php Datei: `backend/config/mail-config.php`
- Der Git-Merge-Konflikt wurde aufgelöst
- Die Datei ist jetzt funktionsfähig
### 2. ⚠️ Zu beheben: SMTP-Konfiguration Mindestens diese Werte müssen korrekt gesetzt sein:
- Die SMTP-Einstellungen sind noch auf Testwerte gesetzt
- Sie müssen mit echten SMTP-Daten konfiguriert werden
### 3. ⚠️ Zu beheben: PHPMailer-Installation
- Composer ist nicht installiert
- PHPMailer ist nicht verfügbar
- Fallback auf native PHP mail() Funktion ist aktiv
## SMTP-Konfiguration
### Option 1: Gmail SMTP (Empfohlen für Tests)
1. **Gmail-Konto einrichten:**
- Gehen Sie zu Ihren Google-Kontoeinstellungen
- Aktivieren Sie "2-Schritt-Verifizierung"
- Erstellen Sie ein "App-Passwort"
2. **config.php bearbeiten:**
```php ```php
'smtp_host' => 'smtp.gmail.com', define('SMTP_FROM_EMAIL', 'kontakt@hexahost.de');
'smtp_port' => 587, define('SMTP_TO_EMAIL', 'info@hexahost.de');
'smtp_username' => 'ihre-email@gmail.com',
'smtp_password' => 'ihr-app-passwort',
'smtp_encryption' => 'tls',
'from_email' => 'ihre-email@gmail.com',
'to_email' => 'info@hexahost.de',
``` ```
### Option 2: Eigener Mail-Server ## Voraussetzungen auf dem Server
1. **SMTP-Daten von Ihrem Hosting-Provider erhalten** - `mail()` muss in der PHP-Umgebung aktiviert sein
2. **config.php bearbeiten:** - Ein Mail Transfer Agent (MTA) bzw. Mailversand beim Hoster muss funktionieren
```php
'smtp_host' => 'mail.ihre-domain.de',
'smtp_port' => 587,
'smtp_username' => 'kontakt@ihre-domain.de',
'smtp_password' => 'ihr-smtp-passwort',
'smtp_encryption' => 'tls',
'from_email' => 'kontakt@ihre-domain.de',
'to_email' => 'info@hexahost.de',
```
### Option 3: Andere E-Mail-Provider ## Test der E-Mail-Funktion
#### Outlook/Hotmail: 1. Per Script testen:
```php - `scripts/test-email.php`
'smtp_host' => 'smtp-mail.outlook.com', 2. Kontaktformular testen:
'smtp_port' => 587, - Seite `contact.php` öffnen
'smtp_encryption' => 'tls', - Formular absenden
``` - Empfang im Zielpostfach prüfen
#### GMX:
```php
'smtp_host' => 'mail.gmx.net',
'smtp_port' => 587,
'smtp_encryption' => 'tls',
```
#### Web.de:
```php
'smtp_host' => 'smtp.web.de',
'smtp_port' => 587,
'smtp_encryption' => 'tls',
```
## PHPMailer-Installation
### Composer installieren:
1. **Windows:**
- Laden Sie Composer von https://getcomposer.org/download/
- Führen Sie den Installer aus
2. **Linux/macOS:**
```bash
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
```
### PHPMailer installieren:
```bash
cd public
composer install
```
## Test der E-Mail-Funktionalität
### 1. Test-Datei verwenden:
- Öffnen Sie `test-email.php` im Browser
- Klicken Sie auf "Test-E-Mail senden"
### 2. Kontaktformular testen:
- Öffnen Sie `contact.html`
- Füllen Sie das Formular aus
- Überprüfen Sie die Antwort
## Sicherheitseinstellungen
### DNS-Einträge für Spam-Schutz:
1. **SPF Record (TXT):**
```
v=spf1 include:_spf.hexahost.de ~all
```
2. **DMARC Record (TXT):**
```
v=DMARC1; p=quarantine; rua=mailto:dmarc@hexahost.de
```
3. **DKIM (wird vom Mail-Server konfiguriert)**
## Fehlerbehebung ## Fehlerbehebung
### Häufige Probleme: ### Meldung: "Mail function not available"
- `mail()` ist auf dem Server deaktiviert
- Hoster kontaktieren und Mailfunktion aktivieren lassen
1. **"SMTP connect() failed"** ### Nachricht kommt nicht an
- Überprüfen Sie Host und Port - Spam-Ordner prüfen
- Prüfen Sie Firewall-Einstellungen - Absenderadresse (`SMTP_FROM_EMAIL`) auf gültige Domain setzen
- PHP-Error-Log prüfen
2. **"Authentication failed"** ### Versand funktioniert lokal nicht
- Überprüfen Sie Benutzername und Passwort - Unter Windows/Lokalumgebung ist oft kein SMTP in `php.ini` konfiguriert
- Bei Gmail: App-Passwort verwenden - Auf dem echten Webserver testen
3. **"Connection timeout"** ## Sicherheit
- Prüfen Sie Internetverbindung
- Überprüfen Sie SMTP-Host
4. **"Mail function not available"** Das Kontaktformular beinhaltet bereits:
- PHP mail() Funktion ist deaktiviert - CSRF-Schutz
- Kontaktieren Sie Ihren Hosting-Provider - Rate Limiting
- Honeypot-Feld
- Serverseitige Validierung und Sanitization
## Debug-Modus aktivieren
In `config.php` setzen Sie:
```php
'debug_mode' => true,
```
## Logs überprüfen
E-Mail-Fehler werden in den PHP-Error-Logs gespeichert:
- Windows: Event Viewer
- Linux: `/var/log/php_errors.log`
## Nächste Schritte
1. ✅ Merge-Konflikt behoben
2. ⚠️ SMTP-Konfiguration anpassen
3. ⚠️ PHPMailer installieren (optional)
4. ⚠️ E-Mail-Funktionalität testen
5. ⚠️ DNS-Einträge für Spam-Schutz konfigurieren
## Support
Bei Problemen:
- Überprüfen Sie die PHP-Error-Logs
- Testen Sie mit `test-email.php`
- Kontaktieren Sie Ihren Hosting-Provider

View File

@@ -4,13 +4,14 @@
# Sicherheitsheader # Sicherheitsheader
<IfModule mod_headers.c> <IfModule mod_headers.c>
Header always set X-Content-Type-Options nosniff Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY # X-Frame-Options entfernt, da sonst Tag Assistant (Iframe) nicht funktionieren kann.
# Absicherung erfolgt stattdessen granular über CSP frame-ancestors.
Header always set X-XSS-Protection "1; mode=block" Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()" Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Content Security Policy - Schutz vor XSS und Code-Injection # Content Security Policy - Schutz vor XSS und Code-Injection
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://cdn.hexahost.de data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://cdn.hexahost.de https://www.google-analytics.com https://www.googletagmanager.com data:; connect-src 'self' https://www.googletagmanager.com https://www.google.com https://www.google-analytics.com https://region1.google-analytics.com https://stats.g.doubleclick.net; frame-ancestors 'self' https://tagassistant.google.com; base-uri 'self'; form-action 'self'"
# Strict-Transport-Security (HSTS) - Erzwingt HTTPS # Strict-Transport-Security (HSTS) - Erzwingt HTTPS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
@@ -51,15 +52,6 @@
Deny from all Deny from all
</Files> </Files>
<Files "composer.json">
Order allow,deny
Deny from all
</Files>
<Files "composer.lock">
Order allow,deny
Deny from all
</Files>
# Config-Verzeichnis schützen # Config-Verzeichnis schützen
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
@@ -76,11 +68,6 @@
RewriteRule ^logs/ - [F,L] RewriteRule ^logs/ - [F,L]
</IfModule> </IfModule>
# Vendor-Verzeichnis schützen
<IfModule mod_rewrite.c>
RewriteRule ^vendor/ - [F,L]
</IfModule>
# Cache-Header für statische Dateien # Cache-Header für statische Dateien
<IfModule mod_expires.c> <IfModule mod_expires.c>
ExpiresActive On ExpiresActive On

View File

@@ -41,9 +41,9 @@ includeHeader($page_title, $page_description, $current_page);
<h2 class="section-title">Unsere Geschichte</h2> <h2 class="section-title">Unsere Geschichte</h2>
<p> <p>
HexaHost.de wurde von mir, Samuel Müller, mit der Vision gegründet, zuverlässiges HexaHost.de wurde von mir, Samuel Müller, mit der Vision gegründet, zuverlässiges
und preiswertes Hosting und IT-Lösungen direkt aus Deutschland anzubieten. Als regionales und preiswertes Hosting und IT-Lösungen direkt aus Bayern anzubieten. Als regionales
Unternehmen aus Niederbayern verstehe ich die Bedürfnisse meiner Kunden Unternehmen aus Niederbayern verstehe ich die Bedürfnisse meiner Kunden
und bieten persönlichen Support. und biete persönlichen Support.
</p> </p>
<p> <p>
Meine Expertise liegt in der Bereitstellung moderner Hosting- und IT-Lösungen Meine Expertise liegt in der Bereitstellung moderner Hosting- und IT-Lösungen

View File

@@ -15,3 +15,156 @@
justify-content: center; justify-content: center;
margin-top: 2rem; margin-top: 2rem;
} }
/* IT services page: center target-group cards without affecting other pages */
.it-services-page .values .values-grid {
display: flex !important;
flex-wrap: wrap !important;
justify-content: center !important;
align-items: stretch;
grid-template-columns: none !important;
gap: 1.5rem !important;
}
.it-services-page .values .value-item {
flex: 0 1 320px !important;
max-width: 360px;
aspect-ratio: 1 / 1;
margin: 0 !important;
display: flex;
flex-direction: column;
justify-content: center;
}
/* Legal pages: plain white content with black text */
.legal-hero,
.legal-content {
background: #ffffff;
color: #000000;
}
.legal-hero {
margin-top: 70px;
padding: 2rem 0 1.5rem;
border-bottom: 1px solid #e5e5e5;
}
.legal-content {
padding-top: 2rem;
}
.legal-hero-title {
background: none;
-webkit-text-fill-color: #000000;
color: #000000;
margin-bottom: 0.5rem;
}
.legal-hero-description,
.legal-section h2,
.legal-section h3,
.legal-block p,
.legal-block li,
.legal-hero .breadcrumb,
.legal-hero .breadcrumb span,
.legal-content .breadcrumb,
.legal-content .breadcrumb span {
color: #000000;
}
.legal-section,
.legal-section.glass-card {
background: transparent;
border: none;
box-shadow: none;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border-radius: 0;
padding: 0;
}
.legal-section:hover,
.legal-section.glass-card:hover {
transform: none;
box-shadow: none;
border: none;
background: transparent;
}
.legal-content .glass-card:hover {
transform: none;
box-shadow: none;
}
.legal-section h2 {
border-bottom: 1px solid #e5e5e5;
padding-bottom: 0.5rem;
margin-bottom: 0.8rem;
}
.legal-block a,
.legal-hero .breadcrumb a,
.legal-content .breadcrumb a {
color: #0b57d0;
}
.legal-block a:hover,
.legal-hero .breadcrumb a:hover,
.legal-content .breadcrumb a:hover {
color: #0b57d0;
text-decoration: none;
}
/* Ensure absolutely no hover effects on legal text/content */
.legal-hero *,
.legal-content *,
.legal-hero *:hover,
.legal-content *:hover,
.legal-hero *:focus,
.legal-content *:focus,
.legal-hero *:active,
.legal-content *:active {
transform: none !important;
box-shadow: none !important;
text-shadow: none !important;
transition: none !important;
animation: none !important;
}
/* Keep footer sections in one row on desktop */
.footer-content {
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 1.5rem 1.25rem;
align-items: start;
}
.footer-section h4 {
margin-bottom: 0.65rem;
}
.footer-section ul li {
margin-bottom: 0.4rem;
}
@media (max-width: 1100px) {
.footer-content {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.footer-content {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 520px) {
.footer-content {
grid-template-columns: 1fr;
}
.it-services-page .values .value-item {
flex-basis: 100%;
max-width: 100%;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,19 +0,0 @@
{
"name": "hexahost/contact-form",
"description": "HexaHost.de Contact Form with PHPMailer",
"type": "project",
"require": {
"phpmailer/phpmailer": "^6.8"
},
"autoload": {
"psr-4": {
"HexaHost\\": "src/"
}
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"minimum-stability": "stable",
"prefer-stable": true
}

View File

@@ -0,0 +1,5 @@
<?php
/**
* Kompatibilitäts-Wrapper leitet auf die zentrale Backend-Konfiguration um.
*/
require_once __DIR__ . '/../../backend/config/mail-config.php';

View File

@@ -1,58 +1,26 @@
<?php <?php
/** /**
* HexaHost.de Contact Form Handler * HexaHost.de Contact Form Handler
* E-Mail-Verarbeitung mit SMTP-Integration und Spam-Schutz * E-Mail-Verarbeitung mit nativer PHP-mail()-Funktion und Spam-Schutz
*/ */
// Session starten für CSRF-Validierung require_once __DIR__ . '/../backend/includes/functions.php';
if (session_status() === PHP_SESSION_NONE) { require_once __DIR__ . '/../backend/config/mail-config.php';
session_start(); require_once __DIR__ . '/../backend/config/contact-config.php';
}
// Konfiguration laden
require_once 'config/mail-config.php';
// PHPMailer Autoload (falls via Composer installiert)
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
}
// Konfiguration verwenden
$config = getHexaHostConfig(); $config = getHexaHostConfig();
// Betreff-Mapping (zentral definiert)
const SUBJECT_MAP = [
'allgemeine-anfrage' => 'Allgemeine Anfrage',
'vpc-anfrage' => 'Virtual Private Container Anfrage',
'vps-anfrage' => 'Virtual Private Server Anfrage',
'mail-gateway-anfrage' => 'Mail Gateway Anfrage',
'webhosting-anfrage' => 'Webhosting Anfrage',
'support' => 'Technischer Support',
'beratung' => 'Persönliche Beratung',
'migration' => 'Migration/Umzug',
'sonstiges' => 'Sonstige Anfrage'
];
// CSRF-Token validieren und invalidieren (verhindert Replay-Attacks)
function validateCSRFToken($token) {
if (isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token)) {
// Token nach erfolgreicher Validierung invalidieren
unset($_SESSION['csrf_token']);
return true;
}
return false;
}
// CORS Headers für AJAX-Requests (nur eigene Domain erlauben) // 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',
'http://localhost', // Für Entwicklung 'https://dev.hexahost.de',
'http://127.0.0.1' // Für Entwicklung 'http://localhost',
'http://127.0.0.1',
]; ];
$origin = $_SERVER['HTTP_ORIGIN'] ?? ''; $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowed_origins)) { if (in_array($origin, $allowed_origins, true)) {
header('Access-Control-Allow-Origin: ' . $origin); header('Access-Control-Allow-Origin: ' . $origin);
} }
@@ -60,178 +28,97 @@ header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type'); header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
// Nur POST-Requests erlauben
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405); http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']); echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit; exit;
} }
// Rate Limiting
function checkRateLimit($ip) { function checkRateLimit($ip) {
global $config; global $config;
$cache_file = sys_get_temp_dir() . '/hexahost_contact_' . md5($ip) . '.txt'; $cache_file = sys_get_temp_dir() . '/hexahost_contact_' . md5($ip) . '.txt';
$current_time = time(); $current_time = time();
$data = ['requests' => []];
if (file_exists($cache_file)) { $handle = @fopen($cache_file, 'c+');
$data = json_decode(file_get_contents($cache_file), true); if ($handle === false) {
if ($data && isset($data['requests'])) { return true;
// Entferne alte Einträge (älter als 1 Stunde) }
$data['requests'] = array_filter($data['requests'], function($timestamp) use ($current_time) {
return ($current_time - $timestamp) < 3600; 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) => ($current_time - (int) $timestamp) < 3600
));
if (count($data['requests']) >= $config['max_requests_per_hour']) { if (count($data['requests']) >= $config['max_requests_per_hour']) {
return false; return false;
} }
}
}
// Füge aktuellen Request hinzu
$data = isset($data) ? $data : ['requests' => []];
$data['requests'][] = $current_time; $data['requests'][] = $current_time;
file_put_contents($cache_file, json_encode($data)); ftruncate($handle, 0);
rewind($handle);
fwrite($handle, json_encode($data));
} finally {
flock($handle, LOCK_UN);
fclose($handle);
}
return true; return true;
} }
// Honeypot Check
function checkHoneypot($data) { function checkHoneypot($data) {
global $config; global $config;
$honeypot_field = $config['honeypot_field']; $honeypot_field = $config['honeypot_field'];
return empty($data[$honeypot_field]);
// Das Honeypot-Feld sollte leer sein (verstecktes Feld)
if (!empty($data[$honeypot_field])) {
return false;
}
return true;
} }
// E-Mail-Validierung function sanitizeFormField($input) {
function validateEmail($email) { return strip_tags(trim((string) $input));
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
} }
// Input-Sanitization function getSubjectLabel($subjectKey) {
function sanitizeInput($input) { $map = getContactSubjectMap();
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8'); return $map[$subjectKey] ?? 'Neue Kontaktanfrage';
} }
// Sichere IP-Adressen-Erkennung (auch hinter Proxies/Cloudflare)
function getClientIP() {
$ip_keys = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR', // Proxy
'HTTP_X_REAL_IP', // Nginx Proxy
'REMOTE_ADDR' // Standard
];
foreach ($ip_keys as $key) {
if (!empty($_SERVER[$key])) {
// Bei X-Forwarded-For kann eine Liste von IPs kommen
$ip = explode(',', $_SERVER[$key])[0];
$ip = trim($ip);
// Validiere IP-Format
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
// Fallback auf REMOTE_ADDR (auch private IPs für lokale Entwicklung)
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
// SMTP E-Mail-Versand mit PHPMailer
function sendEmail($data) { function sendEmail($data) {
global $config; global $config;
// PHPMailer laden (falls verfügbar) $subject = '[HexaHost.de] ' . getSubjectLabel($data['subject']);
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) { $replyName = sanitizeHeaderValue($data['firstName'] . ' ' . $data['lastName']);
// Fallback: Native PHP mail() Funktion $replyEmail = sanitizeHeaderValue($data['email']);
return sendEmailNative($data);
}
try {
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
// Server-Einstellungen
$mail->isSMTP();
$mail->Host = $config['smtp_host'];
$mail->SMTPAuth = true;
$mail->Username = $config['smtp_username'];
$mail->Password = $config['smtp_password'];
$mail->SMTPSecure = $config['smtp_encryption'];
$mail->Port = $config['smtp_port'];
$mail->CharSet = 'UTF-8';
// Absender
$mail->setFrom($config['from_email'], $config['from_name']);
$mail->addReplyTo($data['email'], $data['firstName'] . ' ' . $data['lastName']);
// Empfänger
$mail->addAddress($config['to_email'], $config['to_name']);
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
$subject = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$mail->Subject = '[HexaHost.de] ' . $subject;
// HTML E-Mail-Inhalt
$html_content = generateEmailHTML($data);
$mail->isHTML(true);
$mail->Body = $html_content;
$mail->AltBody = generateEmailText($data);
// Anti-Spam Headers
$mail->addCustomHeader('X-Mailer', 'HexaHost Contact Form');
$mail->addCustomHeader('X-Priority', '3');
$mail->addCustomHeader('X-MSMail-Priority', 'Normal');
$mail->addCustomHeader('Importance', 'Normal');
$mail->addCustomHeader('X-Report-Abuse', 'Please report abuse here: abuse@hexahost.de');
// DKIM, SPF, DMARC werden über DNS konfiguriert
$mail->send();
return true;
} catch (Exception $e) {
error_log('HexaHost Contact Form Error: ' . $e->getMessage());
return false;
}
}
// Fallback: Native PHP mail() Funktion
function sendEmailNative($data) {
global $config;
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
$subject = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$subject = '[HexaHost.de] ' . $subject;
// Headers für Spam-Schutz
$headers = [ $headers = [
'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>', 'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>',
'Reply-To: ' . $data['firstName'] . ' ' . $data['lastName'] . ' <' . $data['email'] . '>', 'Reply-To: ' . $replyName . ' <' . $replyEmail . '>',
'MIME-Version: 1.0', 'MIME-Version: 1.0',
'Content-Type: text/html; charset=UTF-8', 'Content-Type: text/html; charset=UTF-8',
'X-Mailer: HexaHost Contact Form', 'X-Mailer: HexaHost Contact Form',
'X-Priority: 3', 'X-Priority: 3',
'X-MSMail-Priority: Normal', 'X-MSMail-Priority: Normal',
'Importance: Normal', 'Importance: Normal',
'X-Report-Abuse: Please report abuse here: abuse@hexahost.de' 'X-Report-Abuse: Please report abuse here: abuse@hexahost.de',
]; ];
$message = generateEmailHTML($data); // Native PHP Mailversand ohne externe Libraries
return mail($config['to_email'], $subject, generateEmailHTML($data), implode("\r\n", $headers));
return mail($config['to_email'], $subject, $message, implode("\r\n", $headers));
} }
// HTML E-Mail-Template
function generateEmailHTML($data) { function generateEmailHTML($data) {
// Betreff (nutzt zentrale SUBJECT_MAP Konstante) $subject_text = htmlspecialchars(getSubjectLabel($data['subject']), ENT_QUOTES, 'UTF-8');
$subject_text = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$html = ' $html = '
<!DOCTYPE html> <!DOCTYPE html>
@@ -267,19 +154,19 @@ function generateEmailHTML($data) {
<div class="field"> <div class="field">
<div class="label">Name:</div> <div class="label">Name:</div>
<div class="value">' . $data['firstName'] . ' ' . $data['lastName'] . '</div> <div class="value">' . htmlspecialchars($data['firstName'] . ' ' . $data['lastName'], ENT_QUOTES, 'UTF-8') . '</div>
</div> </div>
<div class="field"> <div class="field">
<div class="label">E-Mail:</div> <div class="label">E-Mail:</div>
<div class="value">' . $data['email'] . '</div> <div class="value">' . htmlspecialchars($data['email'], ENT_QUOTES, 'UTF-8') . '</div>
</div>'; </div>';
if (!empty($data['phone'])) { if (!empty($data['phone'])) {
$html .= ' $html .= '
<div class="field"> <div class="field">
<div class="label">Telefon:</div> <div class="label">Telefon:</div>
<div class="value">' . $data['phone'] . '</div> <div class="value">' . htmlspecialchars($data['phone'], ENT_QUOTES, 'UTF-8') . '</div>
</div>'; </div>';
} }
@@ -287,19 +174,19 @@ function generateEmailHTML($data) {
$html .= ' $html .= '
<div class="field"> <div class="field">
<div class="label">Unternehmen:</div> <div class="label">Unternehmen:</div>
<div class="value">' . $data['company'] . '</div> <div class="value">' . htmlspecialchars($data['company'], ENT_QUOTES, 'UTF-8') . '</div>
</div>'; </div>';
} }
$html .= ' $html .= '
<div class="field"> <div class="field">
<div class="label">Nachricht:</div> <div class="label">Nachricht:</div>
<div class="message">' . nl2br($data['message']) . '</div> <div class="message">' . nl2br(htmlspecialchars($data['message'], ENT_QUOTES, 'UTF-8')) . '</div>
</div> </div>
<div class="field"> <div class="field">
<div class="label">IP-Adresse:</div> <div class="label">IP-Adresse:</div>
<div class="value">' . htmlspecialchars(getClientIP()) . '</div> <div class="value">' . htmlspecialchars(getClientIP(), ENT_QUOTES, 'UTF-8') . '</div>
</div> </div>
<div class="field"> <div class="field">
@@ -319,14 +206,10 @@ function generateEmailHTML($data) {
return $html; return $html;
} }
// Text-Version der E-Mail
function generateEmailText($data) { function generateEmailText($data) {
// Betreff (nutzt zentrale SUBJECT_MAP Konstante)
$subject_text = SUBJECT_MAP[$data['subject']] ?? 'Neue Kontaktanfrage';
$text = "NEUE KONTAKTANFRAGE - HexaHost.de\n"; $text = "NEUE KONTAKTANFRAGE - HexaHost.de\n";
$text .= "=====================================\n\n"; $text .= "=====================================\n\n";
$text .= "Betreff: " . $subject_text . "\n"; $text .= "Betreff: " . getSubjectLabel($data['subject']) . "\n";
$text .= "Name: " . $data['firstName'] . " " . $data['lastName'] . "\n"; $text .= "Name: " . $data['firstName'] . " " . $data['lastName'] . "\n";
$text .= "E-Mail: " . $data['email'] . "\n"; $text .= "E-Mail: " . $data['email'] . "\n";
@@ -341,10 +224,8 @@ function generateEmailText($data) {
$text .= "\nNachricht:\n"; $text .= "\nNachricht:\n";
$text .= "----------\n"; $text .= "----------\n";
$text .= $data['message'] . "\n\n"; $text .= $data['message'] . "\n\n";
$text .= "IP-Adresse: " . getClientIP() . "\n"; $text .= "IP-Adresse: " . getClientIP() . "\n";
$text .= "Zeitstempel: " . date('d.m.Y H:i:s') . "\n\n"; $text .= "Zeitstempel: " . date('d.m.Y H:i:s') . "\n\n";
$text .= "---\n"; $text .= "---\n";
$text .= "Diese E-Mail wurde automatisch vom HexaHost.de Kontaktformular generiert.\n"; $text .= "Diese E-Mail wurde automatisch vom HexaHost.de Kontaktformular generiert.\n";
$text .= "© " . date('Y') . " HexaHost.de - Alle Rechte vorbehalten"; $text .= "© " . date('Y') . " HexaHost.de - Alle Rechte vorbehalten";
@@ -352,40 +233,33 @@ function generateEmailText($data) {
return $text; return $text;
} }
// Hauptverarbeitung
try { try {
// CSRF-Token validieren if (!empty($config['enable_csrf'])) {
if (empty($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) { if (empty($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
http_response_code(403); http_response_code(403);
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'message' => 'Ungültige Sitzung. Bitte laden Sie die Seite neu und versuchen Sie es erneut.' 'message' => 'Ungültige Sitzung. Bitte laden Sie die Seite neu und versuchen Sie es erneut.',
]); ]);
exit; exit;
} }
}
// Rate Limiting Check if (!checkRateLimit(getClientIP())) {
$client_ip = getClientIP();
if (!checkRateLimit($client_ip)) {
http_response_code(429); http_response_code(429);
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'message' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.' 'message' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.',
]); ]);
exit; exit;
} }
// Honeypot Check
if (!checkHoneypot($_POST)) { if (!checkHoneypot($_POST)) {
http_response_code(400); http_response_code(400);
echo json_encode([ echo json_encode(['success' => false, 'message' => 'Ungültige Anfrage.']);
'success' => false,
'message' => 'Ungültige Anfrage.'
]);
exit; exit;
} }
// Pflichtfelder prüfen
$required_fields = ['firstName', 'lastName', 'email', 'subject', 'message', 'privacy']; $required_fields = ['firstName', 'lastName', 'email', 'subject', 'message', 'privacy'];
$missing_fields = []; $missing_fields = [];
@@ -400,53 +274,87 @@ try {
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'message' => 'Bitte füllen Sie alle Pflichtfelder aus.', 'message' => 'Bitte füllen Sie alle Pflichtfelder aus.',
'missing_fields' => $missing_fields 'missing_fields' => $missing_fields,
]); ]);
exit; exit;
} }
// E-Mail-Validierung $subjectKey = trim((string) $_POST['subject']);
if (!validateEmail($_POST['email'])) { if (!isAllowedContactSubject($subjectKey)) {
http_response_code(400); http_response_code(400);
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'message' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein.' 'message' => 'Bitte wählen Sie einen gültigen Betreff.',
]);
exit;
}
$email = trim((string) $_POST['email']);
if (!isValidEmail($email)) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein.',
]);
exit;
}
$message = trim((string) $_POST['message']);
$messageLength = mb_strlen($message, 'UTF-8');
if ($messageLength < $config['min_message_length']) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Ihre Nachricht ist zu kurz.',
]);
exit;
}
if ($messageLength > $config['max_message_length']) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Ihre Nachricht ist zu lang.',
]); ]);
exit; exit;
} }
// Daten sanitieren
$data = [ $data = [
'firstName' => sanitizeInput($_POST['firstName']), 'firstName' => sanitizeFormField($_POST['firstName']),
'lastName' => sanitizeInput($_POST['lastName']), 'lastName' => sanitizeFormField($_POST['lastName']),
'email' => sanitizeInput($_POST['email']), 'email' => sanitizeHeaderValue($email),
'phone' => sanitizeInput($_POST['phone'] ?? ''), 'phone' => sanitizeFormField($_POST['phone'] ?? ''),
'company' => sanitizeInput($_POST['company'] ?? ''), 'company' => sanitizeFormField($_POST['company'] ?? ''),
'subject' => sanitizeInput($_POST['subject']), 'subject' => $subjectKey,
'message' => sanitizeInput($_POST['message']), 'message' => sanitizeFormField($message),
'privacy' => isset($_POST['privacy']) ? true : false 'privacy' => isset($_POST['privacy']),
]; ];
// E-Mail senden
if (sendEmail($data)) { if (sendEmail($data)) {
if (LOG_EMAILS) {
logEmail('sent', [
'subject' => $subjectKey,
'email' => $data['email'],
'ip' => getClientIP(),
]);
}
echo json_encode([ echo json_encode([
'success' => true, 'success' => true,
'message' => 'Ihre Nachricht wurde erfolgreich gesendet! Wir melden uns in Kürze bei Ihnen.' 'message' => 'Ihre Nachricht wurde erfolgreich gesendet! Wir melden uns in Kürze bei Ihnen.',
]); ]);
} else { } else {
http_response_code(500); http_response_code(500);
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'message' => 'Beim Senden der Nachricht ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.' 'message' => 'Beim Senden der Nachricht ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.',
]); ]);
} }
} catch (Exception $e) { } catch (Exception $e) {
error_log('HexaHost Contact Form Error: ' . $e->getMessage()); error_log('HexaHost Contact Form Error: ' . $e->getMessage());
http_response_code(500); http_response_code(500);
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'message' => 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.' 'message' => 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.',
]); ]);
} }
?>

View File

@@ -1,5 +1,8 @@
<?php <?php
require_once __DIR__ . '/../backend/includes/functions.php'; require_once __DIR__ . '/../backend/includes/functions.php';
require_once __DIR__ . '/../backend/config/contact-config.php';
$preselected_subject = getPreselectedContactSubject();
// Page configuration // Page configuration
$page_title = 'Kontakt - HexaHost.de | Hosting aus Niederbayern'; $page_title = 'Kontakt - HexaHost.de | Hosting aus Niederbayern';
@@ -129,21 +132,9 @@ includeHeader($page_title, $page_description, $current_page, $additional_scripts
<label for="subject">Betreff *</label> <label for="subject">Betreff *</label>
<select id="subject" name="subject" required> <select id="subject" name="subject" required>
<option value="">Bitte wählen...</option> <option value="">Bitte wählen...</option>
<option value="allgemeine-anfrage">Allgemeine Anfrage</option> <?php foreach (getContactSubjectMap() as $subjectKey => $subjectLabel): ?>
<option value="vpc-anfrage">Virtual Private Container</option> <option value="<?php echo htmlspecialchars($subjectKey, ENT_QUOTES, 'UTF-8'); ?>"<?php echo $preselected_subject === $subjectKey ? ' selected' : ''; ?>><?php echo htmlspecialchars($subjectLabel, ENT_QUOTES, 'UTF-8'); ?></option>
<option value="vps-anfrage">Virtual Private Server</option> <?php endforeach; ?>
<option value="mail-gateway-anfrage">Mail Gateway</option>
<option value="webhosting-anfrage">Webhosting</option>
<option value="it-beratung">IT-Beratung</option>
<option value="it-support">IT-Support & Fehlerbehebung</option>
<option value="netzwerk-wlan">Netzwerk & WLAN-Einrichtung</option>
<option value="it-sicherheit-backup">IT-Sicherheit & Backup</option>
<option value="webseiten-hosting-service">Webseiten- & Hosting-Service</option>
<option value="wartung-betreuung">Wartung & Betreuung</option>
<option value="support">Technischer Support</option>
<option value="beratung">Persönliche Beratung</option>
<option value="migration">Migration/Umzug</option>
<option value="sonstiges">Sonstiges</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

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

View File

@@ -10,7 +10,7 @@ $current_page = 'it-dienstleistungen';
includeHeader($page_title, $page_description, $current_page); includeHeader($page_title, $page_description, $current_page);
?> ?>
<main id="main-content"> <main id="main-content" class="it-services-page">
<!-- Services Hero --> <!-- Services Hero -->
<section class="about-hero"> <section class="about-hero">
<div class="container"> <div class="container">

View File

@@ -166,8 +166,8 @@ includeHeader($page_title, $page_description, $current_page);
<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

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

@@ -166,8 +166,8 @@ includeHeader($page_title, $page_description, $current_page);
<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

@@ -171,8 +171,8 @@ includeHeader($page_title, $page_description, $current_page);
<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

@@ -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">
@@ -170,8 +170,8 @@ includeHeader($page_title, $page_description, $current_page);
<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

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

View File

@@ -1,72 +1,48 @@
<?php <?php
/** /**
* HexaHost.de E-Mail Test * HexaHost.de E-Mail Test (nur CLI oder lokale Entwicklung)
* Testet die E-Mail-Funktionalität ohne PHPMailer
*/ */
// Konfiguration laden if (PHP_SAPI !== 'cli') {
require_once 'config.php'; $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
$isLocal = in_array($remoteAddr, ['127.0.0.1', '::1'], true)
|| filter_var($remoteAddr, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false;
if (!$isLocal) {
http_response_code(403);
exit('Forbidden');
}
}
require_once __DIR__ . '/../backend/config/mail-config.php';
// Test-E-Mail senden
function testEmail() { function testEmail() {
$config = getHexaHostConfig(); $config = getHexaHostConfig();
// Test-Daten
$test_data = [
'firstName' => 'Test',
'lastName' => 'Benutzer',
'email' => 'test@example.com',
'phone' => '+49 123 456789',
'company' => 'Test GmbH',
'subject' => 'test-email',
'message' => 'Dies ist eine Test-E-Mail vom HexaHost.de Kontaktformular.'
];
// E-Mail-Inhalt erstellen
$subject = '[HexaHost.de] Test-E-Mail'; $subject = '[HexaHost.de] Test-E-Mail';
$message = "Test-E-Mail von HexaHost.de\n\n"; $message = "Test-E-Mail von HexaHost.de\n\n";
$message .= "Name: " . $test_data['firstName'] . " " . $test_data['lastName'] . "\n";
$message .= "E-Mail: " . $test_data['email'] . "\n";
$message .= "Telefon: " . $test_data['phone'] . "\n";
$message .= "Unternehmen: " . $test_data['company'] . "\n";
$message .= "Nachricht: " . $test_data['message'] . "\n\n";
$message .= "Zeitstempel: " . date('d.m.Y H:i:s') . "\n"; $message .= "Zeitstempel: " . date('d.m.Y H:i:s') . "\n";
$message .= "IP-Adresse: " . $_SERVER['REMOTE_ADDR'] . "\n";
// Headers
$headers = [ $headers = [
'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>', 'From: ' . $config['from_name'] . ' <' . $config['from_email'] . '>',
'Reply-To: ' . $test_data['firstName'] . ' ' . $test_data['lastName'] . ' <' . $test_data['email'] . '>',
'MIME-Version: 1.0', 'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8', 'Content-Type: text/plain; charset=UTF-8',
'X-Mailer: HexaHost Test Email' 'X-Mailer: HexaHost Test Email',
]; ];
// E-Mail senden return mail($config['to_email'], $subject, $message, implode("\r\n", $headers));
$result = mail($config['to_email'], $subject, $message, implode("\r\n", $headers)); }
return $result; if (PHP_SAPI === 'cli') {
echo testEmail() ? "Test-E-Mail gesendet.\n" : "Fehler beim Senden.\n";
exit;
} }
// Test ausführen
if (isset($_GET['test'])) { if (isset($_GET['test'])) {
$result = testEmail(); echo testEmail()
? 'Test-E-Mail wurde gesendet.'
if ($result) { : 'Fehler beim Senden der Test-E-Mail.';
echo "✅ Test-E-Mail wurde erfolgreich gesendet!";
} else {
echo "❌ Fehler beim Senden der Test-E-Mail.";
}
} else { } else {
echo "<h1>HexaHost.de E-Mail Test</h1>"; echo '<h1>HexaHost.de E-Mail Test</h1>';
echo "<p>Klicken Sie auf den Link, um eine Test-E-Mail zu senden:</p>"; echo '<p><a href="?test=1">Test-E-Mail senden</a></p>';
echo "<a href='?test=1'>Test-E-Mail senden</a>";
// Konfiguration anzeigen
echo "<h2>Aktuelle Konfiguration:</h2>";
$config = getHexaHostConfig();
echo "<pre>";
print_r($config);
echo "</pre>";
} }
?>