warum bekomme ich die mails wenn neueinträge da sind, aber ein freund der bei IONOS seine domain hat bekommt keine mailBearbeitenDas Problem liegt sehr wahrscheinlich an der fest codierten Absenderadresse in Ihrem PHP-Code. Schauen Sie sich diese Zeile an:
php$from_address = "noreply@domain.de"; // ← hier eine echte Domain eintragen!
Das Problem:
Wenn Ihr Freund bei IONOS hostet und Sie als Absenderadresse noreply@domain.de verwenden (was nicht seine echte Domain ist), dann wird IONOS diese E-Mails sehr wahrscheinlich als Spam einstufen oder komplett ablehnen. Das liegt an SPF-, DKIM- und DMARC-Richtlinien, die verhindern, dass E-Mails von fremden Domains gesendet werden.
Die Lösung:
Ihr Freund muss in der Datei eintragsformular.php diese Zeile ändern:
php$from_address = "noreply@seinedomain.de"; // ← Seine echte Domain verwenden!
Zusätzliche Empfehlungen:
Domain prüfen: Stellen Sie sicher, dass die Domain in $from_address exakt der Domain entspricht, auf der das Gästebuch läuft.
E-Mail-Logs prüfen: Ihr Freund kann bei IONOS in seinem Control Panel die E-Mail-Logs einsehen, um zu sehen, ob die E-Mails ankommen aber als Spam gefiltert werden.
Alternative: Falls das nicht hilft, kann er auch seine echte E-Mail-Adresse als Absender verwenden:
php$from_address = $admin_email; // Verwendet die Admin-E-Mail als Absender
Spam-Ordner prüfen: Lassen Sie Ihren Freund auch den Spam-Ordner kontrollieren.
Das ist ein typisches Problem beim E-Mail-Versand über Webhosting-Provider - sie sind sehr strikt bei der Überprüfung der Absenderadressen.
Gästebuch ohne Datenbank
Re: Gästebuch ohne Datenbank
Gruß Volker
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Ich frage mal Elenor (ChatGPT), sie schuldet mir ohnehin noch was 

- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
keine Ahnung - muss erst gucken und testen:
ChatGPT hat geschrieben:Klar, Tommy!
Ich habe nur die Stellen korrigiert, die den E-Mail-Versand (IONOS) betreffen – inkl. Kommentare, warum es vorher schiefgehen konnte. Der Rest deines Codes bleibt unberührt.
Was konkret falsch/problematisch war
Empfänger hartkodiert: In sendAdminNotification() wurde immer an "Tommy@..." gesendet – nicht an $admin_email.
Kein Envelope-Sender (-f): IONOS erwartet einen gesetzten Return-Path/Envelope-Sender, sonst wird oft geblockt oder landet im Spam.
From-Header & Anzeige-Name: Der Anzeigename sollte MIME-encoded sein (UTF-8), sonst gibt’s Darstellungs-/Filterprobleme.
Reply-To: Sinnvoll ist, auf die Absender-Mail des Eintragenden zu setzen (falls gültig), sonst auf die Admin-Adresse.
CRLF & Pflicht-Header: Für mail() bei vielen Providern (auch IONOS) stabiler mit \r\n, plus MIME-Version.
Re: Gästebuch ohne Datenbank
Ich kann ja jetzt nicht extra noch den PHPMailer dazu packen und SMTP Versand ermöglichen, nur wegen der kleinen Mail das ein neuer Beitrag da ist
Bestimmt hat IONOS dich besonders auf dem Kicker nach der Millionen Spam Mails


Bestimmt hat IONOS dich besonders auf dem Kicker nach der Millionen Spam Mails



Gruß Volker
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Geht - hier ist das angepasste Skript von ChatGPT:
Am besten Du vergleichst es mit "WinMerge":
https://winmerge.org/downloads/?lang=de
Am besten Du vergleichst es mit "WinMerge":
https://winmerge.org/downloads/?lang=de
Code: Alles auswählen
<?php
/*******************************************************
* Gästebuch – Eintrag + Admin-Benachrichtigung (IONOS)
* Stand: 2025-09-04
*******************************************************/
session_start();
date_default_timezone_set('Europe/Berlin');
/* =========================
KONFIGURATION
========================= */
$SITE_NAME = 'Gästebuch-Volker';
$ADMIN_EMAIL = 'mail@example.com'; // ← Empfänger (Admin)
$EMAIL_NOTIFICATIONS = true;
// IONOS: From/Envelope-Sender MUSS eine echte Mailbox derselben Domain sein!
$FROM_ADDRESS = 'mail@example.com'; // ← deine IONOS-Mailbox
$FROM_NAME = $SITE_NAME; // Anzeigename im From-Header
// Datendateien
$entries_file = __DIR__ . '/entries.json';
$pending_file = __DIR__ . '/pending.json';
/* =========================
HILFSFUNKTIONEN
========================= */
function loadEntries(string $file): array {
if (!file_exists($file)) return [];
$content = @file_get_contents($file);
if ($content === false || $content === '') return [];
$data = json_decode($content, true);
return is_array($data) ? $data : [];
}
function saveEntries(array $entries, string $file): void {
$json = json_encode($entries, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$fp = @fopen($file, 'c+');
if ($fp === false) {
error_log("Gästebuch: Datei kann nicht geöffnet werden: $file");
return;
}
// Datei sperren, Inhalt ersetzen
if (flock($fp, LOCK_EX)) {
ftruncate($fp, 0);
fwrite($fp, $json);
fflush($fp);
flock($fp, LOCK_UN);
}
fclose($fp);
}
function sanitizeInput(?string $input): string {
return htmlspecialchars(trim((string)$input), ENT_QUOTES, 'UTF-8');
}
/* =========================
MAIL-FUNKTION (IONOS-Fix)
========================= */
function sendAdminNotification(array $entry, string $admin_email, string $site_name, string $from_address, string $from_name): bool
{
// Empfänger/From prüfen
if (!filter_var($admin_email, FILTER_VALIDATE_EMAIL)) {
error_log("Gästebuch: Ungültige Admin E-Mail-Adresse: $admin_email");
return false;
}
if (!filter_var($from_address, FILTER_VALIDATE_EMAIL)) {
error_log("Gästebuch: Ungültige FROM-Adresse: $from_address");
return false;
}
// Betreff (UTF-8, MIME)
$raw_subject = "[$site_name] Neuer Gästebucheintrag wartet auf Freischaltung";
if (function_exists('mb_encode_mimeheader')) {
$subject = mb_encode_mimeheader($raw_subject, 'UTF-8', 'B', "\r\n");
} else {
$subject = "=?UTF-8?B?" . base64_encode($raw_subject) . "?=";
}
// Admin-URL bauen
$is_https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on');
$protocol = $is_https ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$baseDir = rtrim(dirname($_SERVER['REQUEST_URI'] ?? '/'), '/\\');
$admin_url = $protocol . '://' . $host . $baseDir . '/admin.php';
// Nachricht (CRLF verwenden)
$message =
"Hallo Administrator,\r\n\r\n" .
"ein neuer Eintrag in Ihr Gästebuch wartet auf Freischaltung:\r\n\r\n" .
"═══════════════════════════════════════════════════════════════\r\n\r\n" .
"Name: " . ($entry['name'] ?? '-') . "\r\n" .
"E-Mail: " . (!empty($entry['email']) ? $entry['email'] : 'Nicht angegeben') . "\r\n" .
"Datum: " . ($entry['date'] ?? '-') . "\r\n\r\n" .
"Kommentar:\r\n" . ($entry['comment'] ?? '-') . "\r\n\r\n" .
"═══════════════════════════════════════════════════════════════\r\n\r\n" .
"Freischalten können Sie den Eintrag hier:\r\n" . $admin_url . "\r\n\r\n" .
"Viele Grüße\r\nIhr Gästebuch-System";
// Reply-To: wenn Nutzer-Mail valide, dorthin – sonst From
$reply_to = (!empty($entry['email']) && filter_var($entry['email'], FILTER_VALIDATE_EMAIL))
? $entry['email']
: $from_address;
// Anzeigename MIME-encoden
$from_display = function_exists('mb_encode_mimeheader')
? mb_encode_mimeheader($from_name, 'UTF-8', 'B', "\r\n")
: "=?UTF-8?B?" . base64_encode($from_name) . "?=";
// Header (reine Textmail, UTF-8)
$headers = [
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=UTF-8",
"From: {$from_display} <{$from_address}>",
"Reply-To: {$reply_to}",
"X-Mailer: PHP/" . phpversion(),
];
$headers_str = implode("\r\n", $headers);
// IONOS: Envelope-Sender (Return-Path) setzen – MUSS echte Mailbox sein!
$additional_params = '-f' . $from_address;
// Tatsächlich an $admin_email senden (nicht hartkodieren!)
$success = @mail($admin_email, $subject, $message, $headers_str, $additional_params);
if ($success) {
error_log("Gästebuch: E-Mail erfolgreich gesendet an $admin_email");
} else {
error_log("Gästebuch: FEHLER beim E-Mail-Versand an $admin_email (From: $from_address)");
}
return $success;
}
/* =========================
FORMULARVERARBEITUNG
========================= */
$message = '';
$error = '';
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && ($_POST['action'] ?? '') === 'add_entry') {
// CAPTCHA prüfen
$captcha_input = sanitizeInput($_POST['captcha'] ?? '');
$captcha_correct = isset($_SESSION['captcha']) && (mb_strtolower($captcha_input) === mb_strtolower($_SESSION['captcha']));
// Spam-Schutz (Honeypot + Mindestzeit)
$honeypot_ok = empty($_POST['website'] ?? '');
$time_ok = (isset($_POST['form_start_time']) && (time() - (int)$_POST['form_start_time'] >= 3));
if (!$honeypot_ok || !$time_ok || !$captcha_correct) {
$error = 'Ihre Anfrage konnte nicht verarbeitet werden. Bitte stellen Sie sicher, dass das CAPTCHA korrekt ausgefüllt ist.';
} else {
$name = sanitizeInput($_POST['name'] ?? '');
$email = sanitizeInput($_POST['email'] ?? '');
$comment = sanitizeInput($_POST['comment'] ?? '');
if ($name === '' || $comment === '') {
$error = 'Name und Kommentar sind Pflichtfelder.';
} else {
$pending = loadEntries($pending_file);
$newEntry = [
'id' => bin2hex(random_bytes(8)),
'name' => $name,
'email' => $email,
'comment' => $comment,
'date' => date('d.m.Y H:i:s'),
];
$pending[] = $newEntry;
saveEntries($pending, $pending_file);
// E-Mail an Admin
if ($EMAIL_NOTIFICATIONS && !empty($ADMIN_EMAIL)) {
$email_sent = sendAdminNotification($newEntry, $ADMIN_EMAIL, $SITE_NAME, $FROM_ADDRESS, $FROM_NAME);
if ($email_sent) {
$message = 'Ihr Eintrag wurde übermittelt und der Administrator wurde per E-Mail benachrichtigt. Ihr Eintrag wartet auf Freischaltung.';
} else {
$message = 'Ihr Eintrag wurde übermittelt und wartet auf Freischaltung. (E-Mail-Benachrichtigung fehlgeschlagen - bitte Admin kontaktieren)';
error_log("Gästebuch: E-Mail-Benachrichtigung konnte nicht gesendet werden");
}
} else {
$message = 'Ihr Eintrag wurde übermittelt und wartet auf Freischaltung durch den Administrator.';
}
}
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gästebuch - Eintrag verfassen</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.0/font/bootstrap-icons.min.css" rel="stylesheet">
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@1/index.js"></script>
<style>
body { margin-bottom: 50px; }
.form-card { box-shadow: 0 10px 30px rgba(0,0,0,0.1); border: none; }
.honeypot { position: absolute; left: -9999px; visibility: hidden; }
</style>
<!-- Resize-Skript -->
<script>
window.addEventListener("load", fenster);
window.addEventListener("resize", fenster);
function fenster() {
if (parent.document.getElementsByTagName("iframe")[0]) {
parent.document.getElementsByTagName("iframe")[0].style.height =
document.getElementsByTagName("html")[0].offsetHeight +'px';
}
}
</script>
</head>
<body class="bg-light">
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="d-flex justify-content-between mb-4">
<a href="index.php" class="btn btn-outline-primary">
<i class="bi bi-arrow-left"></i> Zurück zum Gästebuch
</a>
<a href="admin.php" class="btn btn-outline-secondary">
<i class="bi bi-shield-lock"></i> Admin-Bereich
</a>
</div>
<?php if (!empty($message)): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle"></i> <?= $message ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (!empty($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-circle"></i> <?= $error ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="card mb-5 form-card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-plus-circle"></i> Neuen Eintrag verfassen</h5>
</div>
<div class="card-body p-4">
<form method="POST" novalidate>
<input type="hidden" name="action" value="add_entry">
<input type="hidden" name="form_start_time" value="<?= time() ?>">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label fw-semibold">Name *</label>
<input type="text" class="form-control form-control-lg" id="name" name="name" required>
</div>
<div class="col-md-6 mb-3">
<label for="email" class="form-label fw-semibold">E-Mail (optional)</label>
<input type="email" class="form-control form-control-lg" id="email" name="email">
</div>
</div>
<div class="honeypot">
<label for="website">Website (nicht ausfüllen)</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off">
</div>
<div class="mb-4">
<label for="comment" class="form-label fw-semibold">Ihr Kommentar *</label>
<textarea class="form-control" id="comment" name="comment" rows="5"
placeholder="Teilen Sie Ihre Gedanken mit uns! 😊" required></textarea>
<div class="mt-2">
<emoji-picker></emoji-picker>
</div>
</div>
<div class="mb-4">
<label for="captcha" class="form-label fw-semibold">Sicherheitscode eingeben *</label>
<div class="row g-3 align-items-center">
<div class="col-6">
<img src="captcha.php" alt="CAPTCHA Bild" class="img-fluid border rounded"
onclick="this.src='captcha.php?v='+Math.random()">
</div>
<div class="col-6">
<input type="text" class="form-control form-control-lg" id="captcha" name="captcha" required>
</div>
</div>
<small class="form-text text-muted mt-2 d-block">
Klicken Sie auf das Bild, um es zu aktualisieren.
</small>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-send"></i> Eintrag senden
</button>
</div>
</form>
</div>
</div>
<div class="text-center mt-5 pt-4 border-top">
<p class="text-muted">
<i class="bi bi-heart-fill text-danger"></i>
Vielen Dank für Ihren Besuch!
</p>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<script>
// Emojis einfügen
document.querySelector('emoji-picker')?.addEventListener('emoji-click', event => {
const textarea = document.getElementById('comment');
if (!textarea) return;
const emoji = event.detail.unicode;
const startPos = textarea.selectionStart || 0;
const endPos = textarea.selectionEnd || 0;
textarea.value = textarea.value.substring(0, startPos) + emoji + textarea.value.substring(endPos);
textarea.selectionStart = textarea.selectionEnd = startPos + emoji.length;
textarea.focus();
});
</script>
</body>
</html>
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Frage an ChatGPT:
Danke - das Skript funktioniert jetzt so. Sollte es auch bei allen anderen Providern so funktionieren oder kann es noch andere Probleme geben.
ChatGPT hat geschrieben:
Gern, Tommy! Kurzfassung:
Dein Skript ist jetzt „IONOS-sicher“ und funktioniert bei vielen Hostern — aber **nicht bei allen** garantiert, weil sich Mail-Umgebungen unterscheiden. Hier sind die typischen Stolpersteine und wie du sie vermeidest.
# Wo es je nach Provider haken kann
* **`mail()`-Verhalten & `-f` (Envelope-Sender):**
Manche Hoster ignorieren das `-f`-Flag oder blocken es. Auf **Windows/IIS** wird `-f` oft ignoriert; auf manchen Shared-Hostings ist es deaktiviert. Ergebnis: schlechtere Zustellbarkeit oder andere Return-Path-Adresse.
* **`mail()` deaktiviert / Drosselung:**
Einige Provider schalten `mail()` ab oder limitieren stark (Rate Limits, Spam-Schwellen). Dann geht’s sporadisch oder gar nicht.
* **SPF/DKIM/DMARC & Absender-Domain:**
Es muss **mindestens eine** der Prüfungen sauber „alignen“ (SPF *oder* DKIM) **mit deiner From-Domain**.
* Wenn dein Server **direkt** versendet (kein SMTP-Relay), muss die **Server-IP** in deinem **SPF** erlaubt sein.
* Wenn du über den **SMTP-Server des Providers** sendest, muss dessen SPF-Mechanismus in deinem Record erlaubt sein (und idealerweise **DKIM** aktiv sein).
* **DMARC** kann Mails ablehnen, wenn From-Domain nicht mit SPF/DKIM übereinstimmt.
* **IP-Reputation / Shared Hosting:**
Selbst korrekte Header nützen wenig, wenn die Absender-IP (deines Hosters) einen schlechten Ruf hat.
* **Spam-Filter-Eigenheiten:**
Manche Filter mögen bestimmte Betreff-/Textmuster nicht, HTML vs. Plaintext etc. (Du sendest Plaintext — das ist ok.)
* **Header-Details:**
Du nutzt bereits `CRLF`, MIME-Encoded Subject/From und gültiges `Reply-To` → gut. Manche MTAs sind aber pingelig mit Zeilenumbrüchen/Leerzeilen.
# Was du noch „provider-unabhängiger“ machen kannst
1. **Auf SMTP mit Auth umstellen (empfohlen):**
Statt `mail()` eine Library wie **PHPMailer** oder **Symfony Mailer** via **SMTP (Port 587, STARTTLS)** zum SMTP deines Providers nutzen.
Vorteile: kein Ärger mit `-f`, kontrollierter Return-Path, bessere Nachvollziehbarkeit, stabilere Zustellung.
2. **DNS richtig setzen:**
* **SPF**: Erlaube *genau den Weg*, den du nutzt (entweder Server-IP oder das Relay deines Providers).
* **DKIM**: Aktivieren/signieren lassen (bei vielen Hostern klickbar).
* **DMARC**: Eintrag setzen (z. B. erst `p=none` zum Beobachten, später `quarantine`/`reject`).
3. **From-Domain strikt beibehalten:**
`From` immer deine Domain (so wie jetzt). **Nutzer-Mails gehören in `Reply-To`** (so machst du’s bereits). Das vermeidet DMARC-Probleme.
4. **Bounces im Blick behalten:**
Mit `-f` (oder SMTP) gehen Unzustellbarkeiten an dieses Postfach. Regelmäßig prüfen.
5. **Logging erweitern (optional):**
Bei SMTP bekommst du genauere Fehlerursachen (Auth, TLS, Rate Limit, Policy).
# Erkennbare Symptome & Diagnose
* **Kommt gar nichts an:** Prüfe zuerst Spam/Junk, dann `error_log`. Wenn `mail()` `false` liefert, ist es oft Policy/`-f`/Rate Limit.
* **Kommt bei einzelnen Providern nicht an (z. B. Gmail/Outlook):** Meist DMARC/SPF/DKIM-Alignment oder Reputation. Hier hilft SMTP + korrekte DNS-Einträge.
# Konkrete Empfehlung
Wenn du „einmal richtig, überall stabil“ willst, stell den Versand auf **SMTP mit Auth** um. Dein Formularcode bleibt gleich; nur die Sende-Funktion wird ersetzt. Wenn du willst, bau ich dir die `sendAdminNotification()` noch als **PHPMailer-Variante** ein (kurzer Austausch, gleiche Signatur), dann bist du provider-unabhängig und bekommst bessere Fehlermeldungen.
Re: Gästebuch ohne Datenbank
Na prima, dann nehmen wir das
Pack ich jetzt ins ZIP rein
Läuft ja auch bei mir
PHPMailer wollte ich vermeiden

Pack ich jetzt ins ZIP rein
Läuft ja auch bei mir

PHPMailer wollte ich vermeiden

Gruß Volker
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Warte halt auf Leute bei denen es dann nicht geht.
Laut ChatGPT kann man das nur mit SMTP verhindern. Behalte das für zukünftigen Mail-Versand im Hinterkopf.
Laut ChatGPT kann man das nur mit SMTP verhindern. Behalte das für zukünftigen Mail-Versand im Hinterkopf.
Re: Gästebuch ohne Datenbank
Ich weiß das Tommy, aber wir reden hier nur über eine kurze mail das ein neuer Eintrag im GB da ist
Das sollte bei 90 % aller Hoster jetzt so laufen.
Hab jetzt alles im ZIP und auch die Readme umgeändert ( wieder )
Muss man halt überall seine E-Mail adresse rein schreiben - sicherheitshalber
Meine Scripte : https://www.niederastroth.de/scripte.html
Bleibt als Seitenname jetzt so

Das sollte bei 90 % aller Hoster jetzt so laufen.
Hab jetzt alles im ZIP und auch die Readme umgeändert ( wieder )
Muss man halt überall seine E-Mail adresse rein schreiben - sicherheitshalber

Meine Scripte : https://www.niederastroth.de/scripte.html
Bleibt als Seitenname jetzt so
Gruß Volker
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
wie schön - nur glaube nicht, dass ich Dich schon entlasse 
Also - nachdem Du das unveränderte Skript für den dynamische iFrame nun in alle die Dateien eingebaut hast, fehlt noch der Sprunganker. Ich habe den hier bereits eingebaut:
https://www.mobirise-tutorials.com/Tuto ... Volker.php
Dafür müssen, wie bei Deinem Formular-Script, zwei IDs gesetzt werden.
> Das iFrame muss wieder die id="myFrame" tragen. Beispiel:
und der Mobirise-Block mit diesem iFrame dann die id="Gastbuch".
Das Skript gehört dann so und unverändert vor das schließende </body> Tag jeder der drei Seiten:

Also - nachdem Du das unveränderte Skript für den dynamische iFrame nun in alle die Dateien eingebaut hast, fehlt noch der Sprunganker. Ich habe den hier bereits eingebaut:
https://www.mobirise-tutorials.com/Tuto ... Volker.php
Dafür müssen, wie bei Deinem Formular-Script, zwei IDs gesetzt werden.
> Das iFrame muss wieder die id="myFrame" tragen. Beispiel:
Code: Alles auswählen
<iframe id="myFrame" class="notranslate" translate="no" data-gt-no-translate src="https://www.mobirise-tutorials.com/Tutorials-3/SMTPMail-KI-Volker/index.php" scrolling="no"></iframe>
und der Mobirise-Block mit diesem iFrame dann die id="Gastbuch".
Das Skript gehört dann so und unverändert vor das schließende </body> Tag jeder der drei Seiten:
Code: Alles auswählen
<!-- Sprunganker: im Parent zum #Gastbuch scrollen (für iFrame mit id="myFrame") -->
<script>
(function () {
function scrollParentToGastbuch() {
try {
// nur wenn wir wirklich im iFrame laufen
if (window.parent && window.parent !== window) {
var parentDoc = window.parent.document;
// (Optional) kurz warten, damit das Resize-Skript des iFrames die Höhe setzen konnte
// und der Parent nach dem Reflow die korrekte Scrollposition kennt
setTimeout(function () {
var anchor = parentDoc.getElementById('Gastbuch');
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 50);
}
} catch (e) {
console.warn('Scroll zu #Gastbuch nicht möglich:', e);
}
}
// 1) Beim Laden der iFrame-Seite
window.addEventListener('load', scrollParentToGastbuch);
// 2) Bei Klick auf Paginierungslinks sofort scrollen
document.addEventListener('click', function (e) {
var link = e.target.closest('a.page-link');
if (!link) return;
var href = link.getAttribute('href') || '';
// greift für deine Pagination-Links wie ?page=2 usw.
if (href.indexOf('?page=') !== -1) {
// Sofort nach oben zum Block scrollen (die neue Seite lädt danach erneut und scrollt erneut onload)
scrollParentToGastbuch();
}
});
// 3) Falls du an anderen Stellen via JS Inhalte nachlädst/änderst,
// kannst du scrollParentToGastbuch() dort einfach ebenfalls aufrufen.
})();
</script>
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Mir ist gerade noch aufgefallen, dass die Reihenfolge der Beiträge innerhalb desselben Tages nicht stimmt: Neuere Einträge stehen unter älteren.
Der neueste Eintrag muss jedoch immer oben stehen.
Der neueste Eintrag muss jedoch immer oben stehen.
Re: Gästebuch ohne Datenbank
So, alles behoben:
Sprunganker in allen Dateien eingefügt.
Fehler mit der Reihenfolge behoben
Alles immer noch V1.0
und im aktuellen ZIP File
Sprunganker in allen Dateien eingefügt.
Fehler mit der Reihenfolge behoben
Alles immer noch V1.0
und im aktuellen ZIP File
Gruß Volker
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Moin,
also bei mir ist der Index immer noch falsch
"Test 3" müsste über "Test 2" stehen, da dieser Eintrag der neuere ist, wie man auch an der gespeicherten Zeit sehen kann.
Diese ZIP aus Deinem Download hatte ich heute morgen nochmals runter geladen. Das ist also das aktuelle Download:
Insofern habe ich jetzt noch nichts umgestellt. Hier geht es zu meiner Beispiel- und Tutorialseite:
https://www.mobirise-tutorials.com/Tuto ... Volker.php
> Wenn Du das mit dem Index verbessert hast, dann schreibe mir doch bitte wo und in welchen Dateien was geändert wurde, sonst muss ich wieder alles neu machen. Das ist etwas doof, ich mache ständig alles neu um dann feststellen zu müssen, das nichts geändert wurde
> Eigentlich gehört ja auch ein Button "Bearbeiten" dazu, sodass man Beiträge bearbeiten kann. Man kann diese zur Zeit nur löschen - oder soll der Admin das in der .json Datei händisch machen (ist mir auch recht)?
> Bilder, Links, HTML-Code können nicht gepostet werden oder? Kenne mich JSON gar nicht aus.
> Weiterhin fehlen die Anleitungen zu den spezifischen IDs in der Textdatei (readme) vom Gästebuch für den Sprunganker.
> Weiterhin hast Du auch die Umleitung beim direkten Aufruf der geframten Seite in der `readme` nicht beschrieben.
> Warum nennst Du diese Textdatei nicht "readme.txt" oder "lies_mich.txt" - nur danach suchen doch die Leute?!
> Umlaute und Leerzeichen in Dateinamen sind ein absolutes NO GO
> Man sollte persönliche Daten, wie die E-Mail-Adresse, möglichst nicht unverschlüsselt öffentlich ins Netz stellen. Das lädt Betrüger und Spammer regelrecht ein: Solche Adressen werden gesammelt, weiterverkauft und missbraucht, etwa um Absender zu fälschen. Deshalb halte ich ein sichtbares E-Mail-Feld im Gästebuch für vollkommen ungeeignet. Das sollte vielleicht durch ein Feld für die Website (URL) ersetzt werden oder entfallen. Um die Gefahr wissen vielleicht viele User nicht und tappen blind in die Falle. Ich habe daher die Eingabe der E-Mail in meiner Beispielseite vom Gästebuch blockiert.
> Niemand kann diese Anwendung ohne iFrame in Mobirise einbauen, schon weil Du eine neuere Bootstrap-Version 5.3.0 verwendest hast, was ich persönlich überhaupt nicht verstehe. Warum verwendest Du für deine Anwendungen eigentlich nicht die aktuelle Version 5.0.1 von Mobirise, sodass das passt?
also bei mir ist der Index immer noch falsch

"Test 3" müsste über "Test 2" stehen, da dieser Eintrag der neuere ist, wie man auch an der gespeicherten Zeit sehen kann.
Diese ZIP aus Deinem Download hatte ich heute morgen nochmals runter geladen. Das ist also das aktuelle Download:
Insofern habe ich jetzt noch nichts umgestellt. Hier geht es zu meiner Beispiel- und Tutorialseite:
https://www.mobirise-tutorials.com/Tuto ... Volker.php
> Wenn Du das mit dem Index verbessert hast, dann schreibe mir doch bitte wo und in welchen Dateien was geändert wurde, sonst muss ich wieder alles neu machen. Das ist etwas doof, ich mache ständig alles neu um dann feststellen zu müssen, das nichts geändert wurde

> Eigentlich gehört ja auch ein Button "Bearbeiten" dazu, sodass man Beiträge bearbeiten kann. Man kann diese zur Zeit nur löschen - oder soll der Admin das in der .json Datei händisch machen (ist mir auch recht)?
> Bilder, Links, HTML-Code können nicht gepostet werden oder? Kenne mich JSON gar nicht aus.
> Weiterhin fehlen die Anleitungen zu den spezifischen IDs in der Textdatei (readme) vom Gästebuch für den Sprunganker.
> Weiterhin hast Du auch die Umleitung beim direkten Aufruf der geframten Seite in der `readme` nicht beschrieben.
> Warum nennst Du diese Textdatei nicht "readme.txt" oder "lies_mich.txt" - nur danach suchen doch die Leute?!
> Umlaute und Leerzeichen in Dateinamen sind ein absolutes NO GO

> Man sollte persönliche Daten, wie die E-Mail-Adresse, möglichst nicht unverschlüsselt öffentlich ins Netz stellen. Das lädt Betrüger und Spammer regelrecht ein: Solche Adressen werden gesammelt, weiterverkauft und missbraucht, etwa um Absender zu fälschen. Deshalb halte ich ein sichtbares E-Mail-Feld im Gästebuch für vollkommen ungeeignet. Das sollte vielleicht durch ein Feld für die Website (URL) ersetzt werden oder entfallen. Um die Gefahr wissen vielleicht viele User nicht und tappen blind in die Falle. Ich habe daher die Eingabe der E-Mail in meiner Beispielseite vom Gästebuch blockiert.
> Niemand kann diese Anwendung ohne iFrame in Mobirise einbauen, schon weil Du eine neuere Bootstrap-Version 5.3.0 verwendest hast, was ich persönlich überhaupt nicht verstehe. Warum verwendest Du für deine Anwendungen eigentlich nicht die aktuelle Version 5.0.1 von Mobirise, sodass das passt?
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Ich habe die index.php jstzt von ChatGPT umschreiben lassen. So wird es korrekt sortiert. Ich habe es bei mir bereits eingebaut:
https://www.mobirise-tutorials.com/Tuto ... Volker.php
Die neuen Befehle sind oben und unten mit "NEU SORTIEREN" markiert, an zwei Stellen:
https://www.mobirise-tutorials.com/Tuto ... Volker.php
Die neuen Befehle sind oben und unten mit "NEU SORTIEREN" markiert, an zwei Stellen:
Code: Alles auswählen
<?php
session_start();
// Konfiguration für Paginierung
$ENTRIES_PER_PAGE = 5;
// Konfiguration
$entries_file = 'entries.json';
// Hilfsfunktionen
function loadEntries($file) {
if (!file_exists($file)) {
return [];
}
$content = file_get_contents($file);
return json_decode($content, true) ?: [];
}
// Daten laden
$entries = loadEntries($entries_file);
// =========================== NEU SORTIEREN ============================
// Einträge nach Zeitstempel absteigend sortieren (neueste zuerst)
if (!function_exists('entry_ts')) {
function entry_ts(array $e): int {
// Falls irgendwann zusätzlich 'timestamp' (Epoch) mitgespeichert wird:
if (!empty($e['timestamp'])) return (int)$e['timestamp'];
// Aus 'd.m.Y H:i:s' parsen (z. B. "05.09.2025 06:04:55")
if (!empty($e['date'])) {
$dt = DateTime::createFromFormat('d.m.Y H:i:s', $e['date'], new DateTimeZone('Europe/Berlin'));
if ($dt instanceof DateTime) return $dt->getTimestamp();
}
return 0; // unbekannt -> ganz nach unten
}
}
usort($entries, function ($a, $b) {
return entry_ts($b) <=> entry_ts($a); // absteigend
});
// ========================= ENDE NEU SORTIEREN =========================
// Paginierungs-Variablen
$total_entries = count($entries);
$total_pages = ceil($total_entries / $ENTRIES_PER_PAGE);
// Aktuelle Seite festlegen (standardmäßig 1)
$current_page = isset($_GET['page']) && is_numeric($_GET['page']) && $_GET['page'] > 0 ? (int)$_GET['page'] : 1;
if ($current_page > $total_pages) {
$current_page = $total_pages > 0 ? $total_pages : 1;
}
// Offset für array_slice berechnen
$offset = ($current_page - 1) * $ENTRIES_PER_PAGE;
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
if ( self == top ) { location.href = "https://www.mobirise-tutorials.com/Tutorials-3/Gastbuch-Volker.php#Gastbuch"; }
</script>
<title>Gästebuch</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.0/font/bootstrap-icons.min.css" rel="stylesheet">
<style>
body {
margin-bottom: 50px;
}
.entry-card {
border-left: 4px solid #007bff;
transition: transform 0.2s ease-in-out;
}
.entry-card:hover {
/* transform: translateY(-2px); */
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.gradient-bg {
background: linear-gradient(135deg, #506AEB 0%, #152EB0 100%);
}
.form-card {
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
border: none;
}
</style>
<!-- Resize-Skript (beibehalten) von Werner Zenk -->
<script>
window.addEventListener("load", fenster);
window.addEventListener("resize", fenster);
function fenster() {
if (parent.document.getElementsByTagName("iframe")[0]) {
parent.document.getElementsByTagName("iframe")[0].style.height =
document.getElementsByTagName("html")[0].offsetHeight +'px';
}
}
</script>
</head>
<body class="bg-light">
<div class="gradient-bg text-white py-5 mb-5">
<div class="container">
<div class="row justify-content-center text-center">
<div class="col-lg-8">
<h1 class="display-4 fw-bold mb-3">
<i class="bi bi-journal-text"></i> Gästebuch
</h1>
<p class="lead">Teilen Sie Ihre Gedanken und Erfahrungen mit uns!</p>
</div>
</div>
</div>
</div>
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="d-flex justify-content-between mb-4">
<a href="eintragsformular.php" class="btn btn-primary">
<i class="bi bi-pencil-square"></i> Eintrag verfassen
</a>
<a href="admin.php" class="btn btn-outline-secondary">
<i class="bi bi-shield-lock"></i> Admin-Bereich
</a>
</div>
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="text-dark"><i class="bi bi-chat-dots"></i> Gästebucheinträge</h3>
<span class="badge bg-primary fs-6"><?= $total_entries ?> Einträge</span>
</div>
<?php if ($total_entries === 0): ?>
<div class="text-center py-5">
<div class="mb-4">
<i class="bi bi-journal display-1 text-muted"></i>
</div>
<h4 class="text-muted mt-3">Noch keine Einträge vorhanden</h4>
<p class="text-muted">Seien Sie der Erste und <a href="eintragsformular.php">hinterlassen Sie einen Eintrag!</a></p>
</div>
<?php else: ?>
<div class="row">
<?php
// Einträge für die aktuelle Seite abrufen
// ========================= NEU SORTIEREN =========================
// array_reverse entfällt, da oben bereits absteigend sortiert wird
$display_entries = array_slice($entries, $offset, $ENTRIES_PER_PAGE);
// ====================== ENDE NEU SORTIEREN =======================
foreach ($display_entries as $entry):
?>
<div class="col-12 mb-4">
<div class="card entry-card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h5 class="card-title text-primary mb-1">
<i class="bi bi-person-circle"></i> <?= $entry['name'] ?>
</h5>
<?php if (!empty($entry['email'])): ?>
<small class="text-muted d-block">
<i class="bi bi-envelope"></i> <?= $entry['email'] ?>
</small>
<?php endif; ?>
</div>
<div class="text-end">
<small class="text-muted">
<i class="bi bi-calendar3"></i> <?= $entry['date'] ?>
</small>
</div>
</div>
<hr class="my-3">
<p class="card-text"><?= nl2br($entry['comment']) ?></p>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<nav aria-label="Gästebuch Paginierung" class="mt-4">
<ul class="pagination justify-content-center">
<li class="page-item <?= $current_page <= 1 ? 'disabled' : '' ?>">
<a class="page-link" href="?page=<?= max(1, $current_page - 1) ?>">Vorherige</a>
</li>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?= $current_page === $i ? 'active' : '' ?>">
<a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<li class="page-item <?= $current_page >= $total_pages ? 'disabled' : '' ?>">
<a class="page-link" href="?page=<?= min($total_pages, $current_page + 1) ?>">Nächste</a>
</li>
</ul>
</nav>
<?php endif; ?>
<div class="text-center mt-5 pt-4 border-top">
<p class="text-muted">
<i class="bi bi-heart-fill text-danger"></i>
Vielen Dank für Ihren Besuch!
</p>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<!-- Sprunganker: im Parent zum #Gastbuch scrollen (für iFrame mit id="myFrame") -->
<script>
(function () {
function scrollParentToGastbuch() {
try {
// nur wenn wir wirklich im iFrame laufen
if (window.parent && window.parent !== window) {
var parentDoc = window.parent.document;
// (Optional) kurz warten, damit das Resize-Skript des iFrames die Höhe setzen konnte
// und der Parent nach dem Reflow die korrekte Scrollposition kennt
setTimeout(function () {
var anchor = parentDoc.getElementById('Gastbuch');
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 50);
}
} catch (e) {
console.warn('Scroll zu #Gastbuch nicht möglich:', e);
}
}
// 1) Beim Laden der iFrame-Seite
window.addEventListener('load', scrollParentToGastbuch);
// 2) Bei Klick auf Paginierungslinks sofort scrollen
document.addEventListener('click', function (e) {
var link = e.target.closest('a.page-link');
if (!link) return;
var href = link.getAttribute('href') || '';
// greift für deine Pagination-Links wie ?page=2 usw.
if (href.indexOf('?page=') !== -1) {
// Sofort nach oben zum Block scrollen (die neue Seite lädt danach erneut und scrollt erneut onload)
scrollParentToGastbuch();
}
});
// 3) Falls du an anderen Stellen via JS Inhalte nachlädst/änderst,
// kannst du scrollParentToGastbuch() dort einfach ebenfalls aufrufen.
})();
</script>
</body>
</html>
Re: Gästebuch ohne Datenbank
Moin Tommy,
nochmals die Reihenfolge geändert: mit usort() sortiert explizit nach dem tatsächlichen Datum, unabhängig von der Reihenfolge in der JSON-Datei und sollte nun gehen. Bei mir klappts.
Bearbeiten der Beiträge: Wozu Tommy ? soll der Admin Rechtschreibfehler korrigieren oder den Text verschönern ?
Bleibt nur beim Löschen der Einträge. War von Anfang an so vorgesehen.
Bilder, Links usw.: Sollen nicht da rein. Ich will nur Text. Die Emojis hab ich auch nachträglich da rein gemacht. Wollte ich ursprünglich auch nicht.
Anleitung: Habe Dein Iframe TUT dort eingefügt. Allerdings ohne Aufruf der geframten Seiten verhindern. Letztlich sollen ja auch alle nach Deinem TUT einbauen
Ob es readme.txt oder Gästebuch V1.0 heisst ist eigentlich wuppe. Das das eine .txt Datei ist sieht glaub auch der Laie.
Email im Gästebuch: DIE muss doch niemand da eingeben ! Deswegen ja optional ! Nicht jeder hat eine Webseite, der in Gästebücher schreibt
Das war auch von Anfang an so gewollt und geplant.
Ich habe aber jetzt eine Option eingebaut die folgendes erlaubt und eingestellt werden kann:
Bootstrap: Warum soll ich in einem neuen Script ein altes Bootstrap einbauen ? Nur weil Mobirise das noch hat ? NÖ
Meine Scripte sollen ja nicht nur in Mobirise passen, sondern sind generelle PHP Scripte in aktuellen PHP und Bootstrap.
Ist ab sofort in der ZIP: https://www.niederastroth.de/g%C3%A4stebuch.7z
nochmals die Reihenfolge geändert: mit usort() sortiert explizit nach dem tatsächlichen Datum, unabhängig von der Reihenfolge in der JSON-Datei und sollte nun gehen. Bei mir klappts.
Bearbeiten der Beiträge: Wozu Tommy ? soll der Admin Rechtschreibfehler korrigieren oder den Text verschönern ?
Bleibt nur beim Löschen der Einträge. War von Anfang an so vorgesehen.
Bilder, Links usw.: Sollen nicht da rein. Ich will nur Text. Die Emojis hab ich auch nachträglich da rein gemacht. Wollte ich ursprünglich auch nicht.
Anleitung: Habe Dein Iframe TUT dort eingefügt. Allerdings ohne Aufruf der geframten Seiten verhindern. Letztlich sollen ja auch alle nach Deinem TUT einbauen

Ob es readme.txt oder Gästebuch V1.0 heisst ist eigentlich wuppe. Das das eine .txt Datei ist sieht glaub auch der Laie.
Email im Gästebuch: DIE muss doch niemand da eingeben ! Deswegen ja optional ! Nicht jeder hat eine Webseite, der in Gästebücher schreibt

Ich habe aber jetzt eine Option eingebaut die folgendes erlaubt und eingestellt werden kann:
// SICHERHEITS-OPTIONEN für E-Mail-Anzeige:
// 1 = E-Mails komplett verstecken
// 2 = E-Mails verschleiern (m***@example.com)
// 3 = E-Mails nur für Admin sichtbar
// 4 = Gravatar-Avatar anzeigen statt E-Mail
$EMAIL_DISPLAY_MODE = 2;
Bootstrap: Warum soll ich in einem neuen Script ein altes Bootstrap einbauen ? Nur weil Mobirise das noch hat ? NÖ
Meine Scripte sollen ja nicht nur in Mobirise passen, sondern sind generelle PHP Scripte in aktuellen PHP und Bootstrap.
Ist ab sofort in der ZIP: https://www.niederastroth.de/g%C3%A4stebuch.7z
Gruß Volker
Re: Gästebuch ohne Datenbank
OK Chat GPT kann das auch
aber ich mit weniger Aufwand:

aber ich mit weniger Aufwand:
// Einträge nach Datum sortieren (neueste zuerst)
usort($entries, function($a, $b) {
return strtotime($b['date']) - strtotime($a['date']);
});
Gruß Volker
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Ich werde dann meine Dateien nicht mehr ändern und auch die Sortierung von ChatGPT übernehmen, denn deine hat nicht funktioniert
Hier noch der umgeschriebene Code (erfolgreich getestet) für die Datei:
admin.php
Na ja - da muss man schon recht oft auch als Admin etwas ändern. Fast alle schreiben falsches Deutsch und oder komplett unverständlich. Vielleicht muss man auch Dinge ändern, damit man den Beitrag auch öffentlich zeigen kann. Ich kann das aber über die JSON machen, fraglich ist nur ob andere Benutzer das auch können und wollen.

Hier noch der umgeschriebene Code (erfolgreich getestet) für die Datei:
admin.php
Code: Alles auswählen
<?php
session_start();
// Konfiguration
// WICHTIG: Ersetzen Sie den Platzhalter-Hash mit Ihrem eigenen, generierten Hash
$admin_password_hash = '*****************************************************';
$entries_file = 'entries.json';
$pending_file = 'pending.json';
// Hilfsfunktionen
function loadEntries($file) {
if (!file_exists($file)) {
return [];
}
$content = file_get_contents($file);
return json_decode($content, true) ?: [];
}
function saveEntries($entries, $file) {
file_put_contents($file, json_encode($entries, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Verarbeitung der Formulareingaben
$message = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'admin_login':
// NEU: Passwort-Prüfung mit password_verify()
if (password_verify($_POST['password'], $admin_password_hash)) {
$_SESSION['admin'] = true;
$message = 'Erfolgreich als Administrator angemeldet.';
} else {
$error = 'Falsches Passwort. Zugang verweigert.';
}
break;
case 'admin_logout':
unset($_SESSION['admin']);
$message = 'Erfolgreich abgemeldet.';
break;
case 'approve_entry':
if (isset($_SESSION['admin'])) {
$pending = loadEntries($pending_file);
$approved = loadEntries($entries_file);
$entry_id = $_POST['entry_id'];
foreach ($pending as $key => $entry) {
if ($entry['id'] === $entry_id) {
$approved[] = $entry;
unset($pending[$key]);
break;
}
}
saveEntries(array_values($pending), $pending_file);
saveEntries($approved, $entries_file);
$message = 'Eintrag wurde erfolgreich freigegeben.';
} else {
$error = 'Nicht autorisiert.';
}
break;
case 'reject_entry':
if (isset($_SESSION['admin'])) {
$pending = loadEntries($pending_file);
$entry_id = $_POST['entry_id'];
foreach ($pending as $key => $entry) {
if ($entry['id'] === $entry_id) {
unset($pending[$key]);
break;
}
}
saveEntries(array_values($pending), $pending_file);
$message = 'Eintrag wurde abgelehnt und entfernt.';
} else {
$error = 'Nicht autorisiert.';
}
break;
case 'delete_entry':
if (isset($_SESSION['admin'])) {
$entries = loadEntries($entries_file);
$entry_id = $_POST['entry_id'];
foreach ($entries as $key => $entry) {
if ($entry['id'] === $entry_id) {
unset($entries[$key]);
break;
}
}
saveEntries(array_values($entries), $entries_file);
$message = 'Eintrag wurde permanent gelöscht.';
} else {
$error = 'Nicht autorisiert.';
}
break;
case 'approve_all':
if (isset($_SESSION['admin'])) {
$pending = loadEntries($pending_file);
$approved = loadEntries($entries_file);
$count = count($pending);
// Bestätigungs-E-Mails für alle genehmigten Einträge senden (nur wenn verfügbar)
if ($email_enabled && class_exists('EmailConfig') && EmailConfig::SEND_AUTHOR_CONFIRMATIONS) {
try {
$mailer = new GuestbookMailer(EmailConfig::ADMIN_EMAIL, EmailConfig::SITE_NAME);
foreach ($pending as $entry) {
if (!empty($entry['email'])) {
$mailer->notifyEntryApproved($entry);
}
}
} catch (Exception $e) {
error_log("Gästebuch E-Mail Fehler: " . $e->getMessage());
}
}
$approved = array_merge($approved, $pending);
saveEntries($approved, $entries_file);
saveEntries([], $pending_file);
$message = "$count Eintr?e wurden alle auf einmal freigegeben.";
} else {
$error = 'Nicht autorisiert.';
}
break;
case 'reject_all':
if (isset($_SESSION['admin'])) {
$count = count(loadEntries($pending_file));
saveEntries([], $pending_file);
$message = "$count wartende Eintr?e wurden alle abgelehnt.";
} else {
$error = 'Nicht autorisiert.';
}
break;
}
}
}
// Daten laden
$entries = loadEntries($entries_file);
$pending = loadEntries($pending_file);
$isAdmin = isset($_SESSION['admin']);
// =========================== NEU SORTIEREN ============================
// Einheitliche Sortierung (neueste zuerst) für wartende und freigegebene Einträge
if (!function_exists('entry_ts')) {
function entry_ts(array $e): int {
if (!empty($e['timestamp'])) return (int)$e['timestamp'];
if (!empty($e['date'])) {
$dt = DateTime::createFromFormat('d.m.Y H:i:s', $e['date'], new DateTimeZone('Europe/Berlin'));
if ($dt instanceof DateTime) return $dt->getTimestamp();
}
return 0;
}
}
usort($pending, function ($a, $b) { return entry_ts($b) <=> entry_ts($a); });
usort($entries, function ($a, $b) { return entry_ts($b) <=> entry_ts($a); });
// ========================= ENDE NEU SORTIEREN =========================
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
if ( self == top ) { location.href = "https://www.mobirise-tutorials.com/Tutorials-3/Gastbuch-Volker.php#Gastbuch"; }
</script>
<title>Gästebuch - Administration</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.0/font/bootstrap-icons.min.css" rel="stylesheet">
<style>
body {
margin-bottom: 50px;
}
.admin-header {
background: linear-gradient(135deg, #dc3545 0%, #6f42c1 100%);
}
.pending-card {
border-left: 4px solid #ffc107;
background-color: #fff9e6;
}
.approved-card {
border-left: 4px solid #28a745;
}
.stats-card {
background: linear-gradient(135deg, #17a2b8 0%, #007bff 100%);
color: white;
}
.login-card {
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
border: none;
}
</style>
<!-- Resize-Skript (beibehalten) von Werner Zenk -->
<script>
window.addEventListener("load", fenster);
window.addEventListener("resize", fenster);
function fenster() {
if (parent.document.getElementsByTagName("iframe")[0]) {
parent.document.getElementsByTagName("iframe")[0].style.height =
document.getElementsByTagName("html")[0].offsetHeight +'px';
}
}
</script>
</head>
<body class="bg-light">
<div class="admin-header text-white py-4 mb-4">
<div class="container">
<div class="row align-items-center">
<div class="col">
<h1 class="h3 mb-0">
<i class="bi bi-shield-check"></i> Gästebuch Administration
</h1>
</div>
<div class="col-auto">
<a href="index.php" class="btn btn-light">
<i class="bi bi-arrow-left"></i> Zurück zum Gästebuch
</a>
</div>
</div>
</div>
</div>
<div class="container">
<?php if ($message): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle"></i> <?= $message ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-circle"></i> <?= $error ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (!$isAdmin): ?>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card login-card">
<div class="card-header bg-danger text-white text-center">
<h4 class="mb-0"><i class="bi bi-lock"></i> Administrator Login</h4>
</div>
<div class="card-body p-5">
<form method="POST">
<input type="hidden" name="action" value="admin_login">
<div class="mb-4">
<label for="password" class="form-label fw-semibold">Passwort</label>
<input type="password" name="password" id="password"
class="form-control form-control-lg"
placeholder="Admin-Passwort eingeben" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-danger btn-lg">
<i class="bi bi-key"></i> Anmelden
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?php else: ?>
<div class="row mb-4">
<div class="col-md-4 mb-3">
<div class="card stats-card">
<div class="card-body text-center">
<i class="bi bi-hourglass-split display-4 mb-2"></i>
<h3 class="mb-0"><?= count($pending) ?></h3>
<p class="mb-0">Wartende Einträge</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card bg-success text-white">
<div class="card-body text-center">
<i class="bi bi-check-circle display-4 mb-2"></i>
<h3 class="mb-0"><?= count($entries) ?></h3>
<p class="mb-0">Freigegebene Einträge</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card bg-info text-white">
<div class="card-body text-center">
<i class="bi bi-graph-up display-4 mb-2"></i>
<h3 class="mb-0"><?= count($pending) + count($entries) ?></h3>
<p class="mb-0">Einträge gesamt</p>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col">
<div class="card">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0"><i class="bi bi-gear"></i> Admin-Funktionen</h5>
</div>
<div class="card-body">
<div class="btn-group me-2" role="group">
<?php if (!empty($pending)): ?>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="approve_all">
<button type="submit" class="btn btn-success"
onclick="return confirm('Alle wartenden Einträge freigeben?')">
<i class="bi bi-check-all"></i> Alle freigeben
</button>
</form>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="reject_all">
<button type="submit" class="btn btn-warning"
onclick="return confirm('Alle wartenden Einträge ablehnen?')">
<i class="bi bi-x-circle"></i> Alle ablehnen
</button>
</form>
<?php endif; ?>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="admin_logout">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-box-arrow-right"></i> Abmelden
</button>
</form>
<div class="col-auto">
<a href="backup.php" class="btn btn-info">
<i class="bi bi-floppy"></i> Backup erstellen
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<?php if (!empty($pending)): ?>
<div class="card mb-4">
<div class="card-header bg-warning text-dark">
<h5 class="mb-0">
<i class="bi bi-hourglass-split"></i>
Wartende Einträge zur Freischaltung (<?= count($pending) ?>)
</h5>
</div>
<div class="card-body">
<?php foreach ($pending as $entry): ?>
<div class="card mb-3 pending-card">
<div class="card-body">
<div class="row">
<div class="col-md-8">
<h6 class="card-title text-primary">
<i class="bi bi-person"></i> <?= htmlspecialchars($entry['name']) ?>
</h6>
<?php if (!empty($entry['email'])): ?>
<p class="card-text mb-1">
<small class="text-muted">
<i class="bi bi-envelope"></i> <?= htmlspecialchars($entry['email']) ?>
</small>
</p>
<?php endif; ?>
<p class="card-text"><?= nl2br(htmlspecialchars($entry['comment'])) ?></p>
</div>
<div class="col-md-4 text-end">
<small class="text-muted d-block mb-3">
<i class="bi bi-calendar"></i> <?= $entry['date'] ?>
</small>
<div class="btn-group-vertical d-grid gap-2" role="group">
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="approve_entry">
<input type="hidden" name="entry_id" value="<?= $entry['id'] ?>">
<button type="submit" class="btn btn-success">
<i class="bi bi-check-lg"></i> Freigeben
</button>
</form>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="reject_entry">
<input type="hidden" name="entry_id" value="<?= $entry['id'] ?>">
<button type="submit" class="btn btn-danger"
onclick="return confirm('Eintrag wirklich ablehnen?')">
<i class="bi bi-x-lg"></i> Ablehnen
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php else: ?>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> Keine wartenden Einträge vorhanden.
</div>
<?php endif; ?>
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">
<i class="bi bi-check-circle"></i>
Freigegebene Einträge (<?= count($entries) ?>)
</h5>
</div>
<div class="card-body">
<?php if (empty($entries)): ?>
<p class="text-muted text-center py-3">
<i class="bi bi-journal-x display-4"></i><br>
Noch keine freigegebenen Einträge vorhanden.
</p>
<?php else: ?>
<?php foreach ($entries as $entry): // NEU SORTIEREN: array_reverse entfällt ?>
<div class="card mb-3 approved-card">
<div class="card-body">
<div class="row">
<div class="col-md-10">
<h6 class="card-title text-success">
<i class="bi bi-person-check"></i> <?= htmlspecialchars($entry['name']) ?>
</h6>
<?php if (!empty($entry['email'])): ?>
<p class="card-text mb-1">
<small class="text-muted">
<i class="bi bi-envelope"></i> <?= htmlspecialchars($entry['email']) ?>
</small>
</p>
<?php endif; ?>
<p class="card-text"><?= nl2br(htmlspecialchars($entry['comment'])) ?></p>
<small class="text-muted">
<i class="bi bi-calendar"></i> <?= $entry['date'] ?>
</small>
</div>
<div class="col-md-2 text-end">
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="delete_entry">
<input type="hidden" name="entry_id" value="<?= $entry['id'] ?>">
<button type="submit" class="btn btn-outline-danger btn-sm"
onclick="return confirm('Eintrag permanent löschen?')">
<i class="bi bi-trash"></i> Löschen
</button>
</form>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<!-- Sprunganker: im Parent zum #Gastbuch scrollen (für iFrame mit id="myFrame") -->
<script>
(function () {
function scrollParentToGastbuch() {
try {
// nur wenn wir wirklich im iFrame laufen
if (window.parent && window.parent !== window) {
var parentDoc = window.parent.document;
// (Optional) kurz warten, damit das Resize-Skript des iFrames die Höhe setzen konnte
// und der Parent nach dem Reflow die korrekte Scrollposition kennt
setTimeout(function () {
var anchor = parentDoc.getElementById('Gastbuch');
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 50);
}
} catch (e) {
console.warn('Scroll zu #Gastbuch nicht möglich:', e);
}
}
// 1) Beim Laden der iFrame-Seite
window.addEventListener('load', scrollParentToGastbuch);
// 2) Bei Klick auf Paginierungslinks sofort scrollen
document.addEventListener('click', function (e) {
var link = e.target.closest('a.page-link');
if (!link) return;
var href = link.getAttribute('href') || '';
// greift für deine Pagination-Links wie ?page=2 usw.
if (href.indexOf('?page=') !== -1) {
// Sofort nach oben zum Block scrollen (die neue Seite lädt danach erneut und scrollt erneut onload)
scrollParentToGastbuch();
}
});
// 3) Falls du an anderen Stellen via JS Inhalte nachlädst/änderst,
// kannst du scrollParentToGastbuch() dort einfach ebenfalls aufrufen.
})();
</script>
</body>
</html>
Na ja - da muss man schon recht oft auch als Admin etwas ändern. Fast alle schreiben falsches Deutsch und oder komplett unverständlich. Vielleicht muss man auch Dinge ändern, damit man den Beitrag auch öffentlich zeigen kann. Ich kann das aber über die JSON machen, fraglich ist nur ob andere Benutzer das auch können und wollen.
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
Habe Deine neuste ZIP eben getestet. Die Sortierung ist nun korrekt in der index.php.
In der admin.php noch nicht.
In der admin.php noch nicht.
Re: Gästebuch ohne Datenbank
Ich weiß Tommy,
bin halt auch ein fauler Hund
Muss das in der admin sein ? Hauptsache im Frontend wird es richtig angezeigt
bin halt auch ein fauler Hund

Muss das in der admin sein ? Hauptsache im Frontend wird es richtig angezeigt

Gruß Volker
- Tommy Herrmann
- Site Admin
- Beiträge: 7752
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Gästebuch ohne Datenbank
OK - auch das mit der E-Mail getestet - so ist das sehr gut 
Nee - muss in der Admin nicht sein. Kannst aber auch einfach meinen Code oben kopieren. Der geht in jedem Fall.

Nee - muss in der Admin nicht sein. Kannst aber auch einfach meinen Code oben kopieren. Der geht in jedem Fall.
Wer ist online?
Mitglieder in diesem Forum: 0 Mitglieder und 6 Gäste