add notification messages

This commit is contained in:
Bruno Eckhardt committed 2026-07-24 07:32:47 +02:00
1 parent c540d06939
commit 1ad59a9881
5 files changed
+394 -57

No files matched your search

@@ -0,0 +1,55 @@
<?php
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => false,
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();
header('Content-Type: application/json');
error_reporting(E_ALL);
ini_set('display_errors', '0');
if (empty($_SESSION['user_logged_in'])) {
http_response_code(401);
echo json_encode(['error' => 'Nicht eingeloggt']);
exit;
}
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
include_once(__DIR__ . '/../../../controller/NotifyController.php');
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
$action = $daten['action'] ?? '';
try {
switch ($action) {
case 'mark_seen': {
$nachrichtId = $daten['nachricht_id'] ?? null;
if (!is_numeric($nachrichtId)) {
throw new InvalidArgumentException('Ungültige Nachrichten-ID');
}
$notificationCtl = new Notification();
$notificationCtl->markAsSeen((int)$nachrichtId);
echo json_encode(['success' => true]);
break;
}
default:
throw new InvalidArgumentException("Unbekannte Aktion: $action");
}
} catch (InvalidArgumentException $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
} catch (Throwable $e) {
http_response_code(500);
error_log($e->getMessage());
echo json_encode(['error' => 'Serverfehler']);
}
+132
View File
@@ -0,0 +1,132 @@
<?php
include_once(__DIR__ . '/DatabaseController.php');
include_once(__DIR__ . '/DataController.php');
class Notification
{
protected $table;
protected $seenTable;
public function __construct()
{
$this->table = "Benachrichtigung";
$this->seenTable = "Benachrichtigung_Gesehen";
}
/**
* Ermittelt die Benutzer-ID des aktuell eingeloggten Nutzers.
*/
private function getCurrentUserId(): ?int
{
$username = $_SESSION['username'] ?? null;
if (!$username) {
return null;
}
$rows = DatabaseShow(
"Benutzer",
"ID",
"NAME = ?",
$username
);
return $rows[0]['ID'] ?? null;
}
/**
* Alle Nachrichten (z.B. für eine Verwaltungsansicht).
*/
public function getAllMessages(): array
{
return DatabaseShow(
$this->table,
"ID, TITEL, NACHRICHT"
);
}
/**
* Nur die Nachrichten, die der aktuelle Nutzer noch NICHT gesehen hat.
* Genau das braucht das Modal, um zu entscheiden ob/was es anzeigt.
*/
public function getUnseenMessages(): array
{
$userId = $this->getCurrentUserId();
if ($userId === null) {
return [];
}
$alleNachrichten = DatabaseShow(
$this->table,
"ID, TITEL, NACHRICHT"
);
$gesehenRows = DatabaseShow(
$this->seenTable,
"NACHRICHTEN_ID",
"BENUTZER_ID = ?",
$userId
);
$gesehenIds = array_column($gesehenRows, 'NACHRICHTEN_ID');
return array_values(array_filter(
$alleNachrichten,
fn($nachricht) => !in_array($nachricht['ID'], $gesehenIds)
));
}
/**
* Legt eine neue Nachricht im Pool an (z.B. für eine Admin-Funktion).
*/
public function createMessage(string $titel, string $nachricht): bool
{
try {
DatabaseInsert(
$this->table,
"TITEL, NACHRICHT",
$titel,
$nachricht
);
return true;
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
/**
* Markiert eine bestimmte Nachricht als vom aktuellen Nutzer gesehen/bestätigt.
*/
public function markAsSeen(int $nachrichtId): bool
{
$userId = $this->getCurrentUserId();
if ($userId === null) {
throw new Exception('Kein eingeloggter Benutzer gefunden.');
}
// Prüfen, ob schon als gesehen markiert, um Duplikate zu vermeiden
$bereitsGesehen = DatabaseShow(
$this->seenTable,
"ID",
"BENUTZER_ID = ? AND NACHRICHTEN_ID = ?",
$userId,
$nachrichtId
);
if (!empty($bereitsGesehen)) {
return true; // war schon markiert, kein Fehler
}
try {
DatabaseInsert(
$this->seenTable,
"BENUTZER_ID, NACHRICHTEN_ID",
$userId,
$nachrichtId
);
return true;
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
}
@@ -0,0 +1,25 @@
const NOTIFICATION_HANDLER_URL = '/api/js/handler/NotificationAjaxHandler.php';
async function notificationAjaxRequest(payload) {
try {
const response = await fetch(NOTIFICATION_HANDLER_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await parseJsonResponse(response);
if (data.error) {
throw new Error(data.error);
}
return data;
} catch (error) {
console.error('Notification-Ajax-Fehler:', error.message);
throw error;
}
}
function markNotificationAsSeen(nachrichtId) {
return notificationAjaxRequest({ action: 'mark_seen', nachricht_id: nachrichtId });
}
+98 -57
View File
@@ -1,4 +1,6 @@
<?php
include_once(__DIR__ . '/../controller/NotifyController.php');
$username = $_SESSION['username'] ?? 'Gast';
$is_superuser = $_SESSION['is_superuser'] ?? false;
$is_admin = $_SESSION['is_admin'] ?? false;
@@ -10,8 +12,24 @@ $db_res = DatabaseShow(
$username
);
// --- Ungesehene Benachrichtigung ermitteln ---
$notificationCtl = new Notification();
$offeneNachrichten = $notificationCtl->getUnseenMessages();
$aktuelleNachricht = $offeneNachrichten[0] ?? null; // immer nur die erste anzeigen
?>
<?php if ($aktuelleNachricht): ?>
<div class="notification-overlay open" id="notificationModal" data-nachricht-id="<?= htmlspecialchars($aktuelleNachricht['ID']) ?>">
<div class="notification-box">
<div class="notification-icon">📢</div>
<h3><?= htmlspecialchars($aktuelleNachricht['TITEL']) ?></h3>
<p><?= nl2br(htmlspecialchars($aktuelleNachricht['NACHRICHT'])) ?></p>
<button type="button" id="notificationConfirm">Verstanden</button>
</div>
</div>
<?php endif; ?>
<div class="home-dashboard">
<div class="links-container">
@@ -20,18 +38,18 @@ $db_res = DatabaseShow(
<div class="group-links" style="padding: 10px; color: inherit;">
<p style="margin: 5px 0;"><strong>Name:</strong> <code><?= htmlspecialchars($username) ?></code></p>
<?php if ($username != 'gast'): ?>
<p style="margin: 5px 0;"><strong>Nachname:</strong>
<code><?= htmlspecialchars($db_res[0]['NACHNAME']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>E-Mail:</strong>
<code><?= htmlspecialchars($db_res[0]['EMAIL']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Benutzerrolle:</strong>
<code><?= htmlspecialchars($db_res[0]['ROLLE']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Home IP:</strong>
<code><?= htmlspecialchars($db_res[0]['HOME_IP']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Nachname:</strong>
<code><?= htmlspecialchars($db_res[0]['NACHNAME']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>E-Mail:</strong>
<code><?= htmlspecialchars($db_res[0]['EMAIL']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Benutzerrolle:</strong>
<code><?= htmlspecialchars($db_res[0]['ROLLE']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Home IP:</strong>
<code><?= htmlspecialchars($db_res[0]['HOME_IP']) ?></code>
</p>
<?php endif ?>
</div>
</div>
@@ -41,18 +59,18 @@ $db_res = DatabaseShow(
<div class="group-links"
style="padding: 10px; display: flex; flex-direction: row; gap: 10px; flex-wrap: wrap;">
<?php if ((new getSpecificData())->getIsAdmin()): ?>
<span
style="background: #dc3545; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Admin</span>
<span
style="background: #dc3545; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Admin</span>
<?php endif; ?>
<?php if ((new getSpecificData())->getIsSuperuser()): ?>
<span
style="background: #ffc107; color: #000; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Superuser</span>
<span
style="background: #ffc107; color: #000; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Superuser</span>
<?php endif; ?>
<?php if (!(new getSpecificData())->getIsSuperuser() && !(new getSpecificData())->getIsAdmin()): ?>
<span
style="background: #6c757d; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Standard-Nutzer</span>
<span
style="background: #6c757d; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Standard-Nutzer</span>
<?php endif; ?>
</div>
</div>
@@ -67,21 +85,21 @@ $db_res = DatabaseShow(
<code>Aktiv</code>
</p>
<?php if ($is_admin): ?>
<p style="margin: 5px 0;"><strong>Client IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getClientIP()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Server IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getServerIP()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Geo IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getGeoIP((new getSpecificData())->getClientIP())) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Database IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getDatabase()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Wetter:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getWeather()['aktuell']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Client IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getClientIP()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Server IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getServerIP()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Geo IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getGeoIP((new getSpecificData())->getClientIP())) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Database IP:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getDatabase()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Wetter:</strong>
<code><?= htmlspecialchars((new getSpecificData())->getWeather()['aktuell']) ?></code>
</p>
<?php endif ?>
</div>
</div>
@@ -89,32 +107,55 @@ $db_res = DatabaseShow(
</div>
</div>
<script src="/js/ajaxApi/NotificationAjaxMethods.js"></script>
<script src="/js/AjaxMethods.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const modal = document.getElementById('notificationModal');
const confirmBtn = document.getElementById('notificationConfirm');
if (!modal || !confirmBtn) return; // keine offene Nachricht vorhanden
confirmBtn.addEventListener('click', async () => {
const nachrichtId = modal.dataset.nachrichtId;
try {
await markNotificationAsSeen(nachrichtId);
modal.classList.remove('open');
setTimeout(() => modal.remove(), 200); // nach dem Fade-out entfernen
} catch (err) {
alert('Fehler beim Bestätigen: ' + err.message);
}
});
});
</script>
<style>
/* Zusätzliche Inline-Styles für die Feinabstimmung in der home_info */
.home-dashboard {
animation: fadeIn 0.5s ease-in-out;
}
code {
background: rgba(0, 0, 0, 0.1);
padding: 2px 5px;
border-radius: 4px;
font-family: monospace;
}
body.dark-mode code {
background: rgba(255, 255, 255, 0.1);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
/* Zusätzliche Inline-Styles für die Feinabstimmung in der home_info */
.home-dashboard {
animation: fadeIn 0.5s ease-in-out;
}
to {
opacity: 1;
transform: translateY(0);
code {
background: rgba(0, 0, 0, 0.1);
padding: 2px 5px;
border-radius: 4px;
font-family: monospace;
}
body.dark-mode code {
background: rgba(255, 255, 255, 0.1);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}
</style>
+84
View File
@@ -1460,4 +1460,88 @@ body.dark-mode .autocomplete-suggestions div.active {
.register-form input {
margin-bottom: 0;
/* gap übernimmt jetzt den Abstand, kein doppeltes Spacing mehr */
}
/* ===== Benachrichtigungs-Modal ===== */
.notification-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.notification-overlay.open {
opacity: 1;
pointer-events: auto;
}
.notification-box {
background-color: white;
border-radius: 10px;
border-top: 4px solid #007bff;
width: 420px;
max-width: 90%;
padding: 28px 32px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
text-align: center;
transform: translateY(-10px);
transition: transform 0.2s ease;
}
.notification-overlay.open .notification-box {
transform: translateY(0);
}
body.dark-mode .notification-box {
background-color: #1a1a1a;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
}
.notification-box .notification-icon {
font-size: 2.2rem;
margin-bottom: 12px;
}
.notification-box h3 {
margin-top: 0;
margin-bottom: 12px;
font-size: 1.3rem;
}
body.dark-mode .notification-box h3 {
color: #f0f0f0;
}
.notification-box p {
margin-bottom: 24px;
line-height: 1.5;
color: #555;
white-space: pre-line;
}
body.dark-mode .notification-box p {
color: #ccc;
}
.notification-box button {
padding: 10px 28px;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background-color 0.2s ease;
}
.notification-box button:hover {
background-color: #0056b3;
}