web - refactor core modules, harden authorization, simplify endpoints
This commit is contained in:
@@ -1,457 +1,117 @@
|
||||
<?php /* Name: admin_brandschutz.php
|
||||
Beschreibung: Brandschutz-Schulungsverwaltung im Adminpanel
|
||||
- Übersicht aller Nutzer mit Brandschutz-Rolle
|
||||
- Fälligkeitsstatus und Details
|
||||
- Manuelle Anpassung des Schulungsdatums */
|
||||
<?php
|
||||
/* Name: admin_brandschutz.php
|
||||
Beschreibung: Brandschutz-Schulungsverwaltung im Adminpanel */
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/initialise.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/projects/brandschutz/util/brandschutz_helpers.php';
|
||||
|
||||
// Admin-Check
|
||||
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
$isAdmin = $stmt->fetchColumn();
|
||||
if ((int) ($groups['Admin'] ?? 0) !== 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ((int)$isAdmin !== 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
$csrfToken = brandschutz_csrf_token();
|
||||
|
||||
// Hilfsfunktionen
|
||||
function getProfileImageUrl_bs(array $row, string $g_profilePicturePath): string {
|
||||
$profileImg = glob($g_profilePicturePath."{$row['user_id']}-*.jpg");
|
||||
if (!empty($profileImg)) {
|
||||
usort($profileImg, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
return str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImg[0]);
|
||||
}
|
||||
return '/intranet_repo/general_files/profile_pictures/default.jpg';
|
||||
}
|
||||
function getProfileImageUrl_bs(array $row, string $g_profilePicturePath): string
|
||||
{
|
||||
$profileImages = glob($g_profilePicturePath . $row['user_id'] . '-*.jpg') ?: [];
|
||||
if ($profileImages !== []) {
|
||||
usort($profileImages, static function (string $a, string $b): int {
|
||||
return filemtime($b) <=> filemtime($a);
|
||||
});
|
||||
return str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImages[0]);
|
||||
}
|
||||
|
||||
function getDisplayName_bs(array $row): string {
|
||||
$fullName = trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? ''));
|
||||
return $fullName !== '' ? $fullName : ($row['mail'] ?? 'Unbekannt');
|
||||
}
|
||||
return '/intranet_repo/general_files/profile_pictures/default.jpg';
|
||||
}
|
||||
|
||||
// Schulungsdatum manuell anpassen (POST)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bs_update_date'])) {
|
||||
$editUserId = (int)($_POST['bs_user_id'] ?? 0);
|
||||
$newDate = $_POST['bs_new_date'] ?? '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
brandschutz_verify_csrf($_POST['csrf_token'] ?? null);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
http_response_code(400);
|
||||
exit($exception->getMessage());
|
||||
}
|
||||
|
||||
if ($editUserId > 0 && preg_match('/^\d{4}-\d{2}-\d{2}$/', $newDate)) {
|
||||
// Prüfen ob das neue Datum innerhalb der Frist liegt (365 Tage ab Datum > heute)
|
||||
$newDueDate = date('Y-m-d', strtotime($newDate . ' +365 days'));
|
||||
$isWithinDeadline = (strtotime($newDueDate) >= strtotime(date('Y-m-d')));
|
||||
if (($_POST['action'] ?? '') !== 'update_training_date') {
|
||||
http_response_code(400);
|
||||
exit('Ungültige Aktion.');
|
||||
}
|
||||
|
||||
if ($isWithinDeadline) {
|
||||
// Datum in der Frist: Zähler zurücksetzen
|
||||
$stmtUpsert = $pdo->prepare("INSERT INTO brandschutz_training (user_id, last_training_date, reminder_count, admin_reminder_count) VALUES (?, ?, 0, 0) ON CONFLICT (user_id) DO UPDATE SET last_training_date = EXCLUDED.last_training_date, reminder_count = 0, admin_reminder_count = 0");
|
||||
} else {
|
||||
// Datum außerhalb der Frist: Nur Datum aktualisieren, Zähler beibehalten
|
||||
$stmtUpsert = $pdo->prepare("INSERT INTO brandschutz_training (user_id, last_training_date, reminder_count, admin_reminder_count) VALUES (?, ?, 0, 0) ON CONFLICT (user_id) DO UPDATE SET last_training_date = EXCLUDED.last_training_date");
|
||||
}
|
||||
$stmtUpsert->execute([$editUserId, $newDate]);
|
||||
$editUserId = filter_var($_POST['bs_user_id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
|
||||
$newDate = trim((string) ($_POST['bs_new_date'] ?? ''));
|
||||
if ($editUserId === false || !brandschutz_is_valid_date($newDate)) {
|
||||
http_response_code(400);
|
||||
exit('Ungültige Eingaben.');
|
||||
}
|
||||
|
||||
// Systemnachricht in erledigte Admin-Nachrichten
|
||||
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
if (empty($adminName)) $adminName = 'Ein Administrator';
|
||||
$stmtUser = $pdo->prepare(
|
||||
'SELECT ugi.first_name, ugi.last_name, ugi.mail '
|
||||
. 'FROM user_general_information ugi '
|
||||
. 'INNER JOIN user_permissions up ON up.user_id = ugi.user_id '
|
||||
. 'WHERE ugi.user_id = ? AND up."Brandschutz" = 1'
|
||||
);
|
||||
$stmtUser->execute([(int) $editUserId]);
|
||||
$editedUser = $stmtUser->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$editedUser) {
|
||||
http_response_code(404);
|
||||
exit('Brandschutz-Nutzer nicht gefunden.');
|
||||
}
|
||||
|
||||
$stmtUser = $pdo->prepare("SELECT first_name, last_name, mail FROM user_general_information WHERE user_id = ?");
|
||||
$stmtUser->execute([$editUserId]);
|
||||
$editedUser = $stmtUser->fetch(PDO::FETCH_ASSOC);
|
||||
$newStatus = brandschutz_training_status($newDate);
|
||||
$resetCounters = (int) $newStatus['days_until_due'] >= 0;
|
||||
$adminName = brandschutz_display_name(is_array($user) ? $user : [], 'Ein Administrator');
|
||||
$editedName = brandschutz_display_name($editedUser);
|
||||
|
||||
$editedName = trim(($editedUser['first_name'] ?? '') . ' ' . ($editedUser['last_name'] ?? ''));
|
||||
if (empty($editedName)) $editedName = $editedUser['mail'] ?? 'Unbekannt';
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
brandschutz_store_training_date($pdo, (int) $editUserId, $newDate, $resetCounters);
|
||||
brandschutz_log_admin_message(
|
||||
$pdo,
|
||||
$adminName,
|
||||
(string) ($editedUser['mail'] ?? ''),
|
||||
'Brandschutz-Schulungsdatum angepasst: ' . $editedName,
|
||||
$adminName . ' hat das Schulungsdatum von ' . $editedName . ' manuell auf den '
|
||||
. (new DateTimeImmutable($newDate))->format('d.m.Y') . ' gesetzt.'
|
||||
);
|
||||
$pdo->commit();
|
||||
} catch (Throwable $exception) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$formattedDate = date('d.m.Y', strtotime($newDate));
|
||||
$sysSubject = "Brandschutz-Schulungsdatum angepasst: {$editedName}";
|
||||
$sysMessage = "{$adminName} hat das Schulungsdatum von {$editedName} manuell auf den {$formattedDate} gesetzt.";
|
||||
header('Location: admin_brandschutz.php?message=5', true, 303);
|
||||
exit();
|
||||
}
|
||||
|
||||
$stmtMsg = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$senderMail = $editedUser['mail'] ?? '';
|
||||
$stmtMsg->execute([$adminName, $senderMail, $sysSubject, $sysMessage]);
|
||||
$sql = 'SELECT ugi.user_id, ugi.first_name, ugi.last_name, ugi.mail, '
|
||||
. 'bt.last_training_date, bt.reminder_count, bt.admin_reminder_count, '
|
||||
. 'CASE WHEN ul.user_id IS NOT NULL THEN 1 ELSE 0 END AS IstRegistriert '
|
||||
. 'FROM user_permissions up '
|
||||
. 'INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id '
|
||||
. 'LEFT JOIN brandschutz_training bt ON ugi.user_id = bt.user_id '
|
||||
. 'LEFT JOIN user_login ul ON ugi.user_id = ul.user_id '
|
||||
. 'WHERE up."Brandschutz" = 1 '
|
||||
. 'ORDER BY bt.last_training_date ASC, ugi.last_name ASC, ugi.first_name ASC';
|
||||
$bsUsers = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
header("Location: admin_brandschutz.php?message=5");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Brandschutz-Nutzer laden
|
||||
$sql = "SELECT ugi.user_id, ugi.first_name, ugi.last_name, ugi.mail,
|
||||
bt.last_training_date, bt.reminder_count, bt.admin_reminder_count,
|
||||
CASE WHEN ul.user_id IS NOT NULL THEN 1 ELSE 0 END AS IstRegistriert
|
||||
FROM user_permissions up
|
||||
INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id
|
||||
LEFT JOIN brandschutz_training bt ON ugi.user_id = bt.user_id
|
||||
LEFT JOIN user_login ul ON ugi.user_id = ul.user_id
|
||||
WHERE up.\"Brandschutz\" = 1
|
||||
ORDER BY bt.last_training_date ASC, ugi.last_name ASC, ugi.first_name ASC";
|
||||
$bsUsers = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($bsUsers as &$bsUser) {
|
||||
$bsUser['_display_name'] = brandschutz_display_name($bsUser);
|
||||
$bsUser['_profile_url'] = getProfileImageUrl_bs($bsUser, $g_profilePicturePath);
|
||||
$bsUser['_training_status'] = brandschutz_training_status($bsUser['last_training_date'] ?? null);
|
||||
}
|
||||
unset($bsUser);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
<style>
|
||||
.admin-users-shell {
|
||||
padding: 18px;
|
||||
background: linear-gradient(135deg, var(--gray-50) 0%, var(--theme-light, var(--nak-blue-light)) 100%);
|
||||
min-height: 100%;
|
||||
}
|
||||
.admin-table-card {
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(120, 140, 160, 0.22);
|
||||
background: rgba(255,255,255,0.86);
|
||||
box-shadow: 0 20px 45px rgba(31, 38, 46, 0.10), 0 1px 0 rgba(255,255,255,0.9) inset;
|
||||
}
|
||||
.admin-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 16px 18px 12px 18px;
|
||||
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.72));
|
||||
}
|
||||
.admin-list-title {
|
||||
font-size: 1.05em;
|
||||
font-weight: 800;
|
||||
color: hsl(210, 20%, 15%);
|
||||
}
|
||||
.admin-list-hint {
|
||||
font-size: 0.86em;
|
||||
color: hsl(210, 15%, 46%);
|
||||
}
|
||||
table.admin-user-table {
|
||||
display: table !important;
|
||||
width: calc(100% - 24px);
|
||||
margin: 8px 12px 14px 12px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 8px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
overflow: visible;
|
||||
}
|
||||
.admin-user-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 10px 14px;
|
||||
color: hsl(210, 16%, 46%);
|
||||
font-size: 0.78em;
|
||||
font-weight: 800;
|
||||
text-align: left;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 6px 18px rgba(31, 38, 46, 0.06);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row:hover {
|
||||
transform: translateY(-1px);
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 12px 26px rgba(31, 38, 46, 0.13);
|
||||
}
|
||||
.admin-user-table td {
|
||||
padding: 12px 14px !important;
|
||||
vertical-align: middle;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row td:first-child {
|
||||
width: 82px;
|
||||
min-width: 82px;
|
||||
max-width: 82px;
|
||||
}
|
||||
.admin-profile-button {
|
||||
display: block;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 20px rgba(31, 38, 46, 0.18), 0 0 0 3px rgba(255,255,255,0.95);
|
||||
}
|
||||
.admin-profile-button img {
|
||||
display: block;
|
||||
width: 58px !important;
|
||||
height: 58px !important;
|
||||
object-fit: cover;
|
||||
border-radius: 12px !important;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row:hover .admin-profile-button img {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
.admin-user-name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 800;
|
||||
color: hsl(210, 20%, 15%);
|
||||
}
|
||||
.bs-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
font-size: 0.78em;
|
||||
font-weight: 800;
|
||||
}
|
||||
.bs-status-ok {
|
||||
color: hsl(145, 50%, 28%);
|
||||
background: hsl(145, 50%, 93%);
|
||||
border: 1px solid hsl(145, 50%, 82%);
|
||||
}
|
||||
.bs-status-warning {
|
||||
color: hsl(35, 78%, 30%);
|
||||
background: hsl(42, 90%, 93%);
|
||||
border: 1px solid hsl(42, 90%, 78%);
|
||||
}
|
||||
.bs-status-overdue {
|
||||
color: hsl(0, 65%, 38%);
|
||||
background: hsl(0, 65%, 95%);
|
||||
border: 1px solid hsl(0, 65%, 82%);
|
||||
}
|
||||
.bs-status-critical {
|
||||
color: #FFFFFF;
|
||||
background: hsl(0, 65%, 45%);
|
||||
border: 1px solid hsl(0, 65%, 38%);
|
||||
}
|
||||
.bs-due-date {
|
||||
font-size: 0.9em;
|
||||
font-weight: 650;
|
||||
color: hsl(210, 14%, 35%);
|
||||
}
|
||||
<link rel="stylesheet" href="/intranet_repo/projects/_central/layouts/admin_brandschutz.css">
|
||||
<script src="/intranet_repo/projects/_central/scripts/admin_brandschutz.js" defer></script>
|
||||
|
||||
/* Modal */
|
||||
.admin-modal-card {
|
||||
position: relative;
|
||||
width: min(720px, calc(100vw - 24px));
|
||||
max-height: min(760px, calc(100vh - 24px));
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 24px 70px rgba(0,0,0,0.36);
|
||||
}
|
||||
.admin-modal-hero {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 112px 1fr;
|
||||
gap: 18px;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
color: #FFFFFF;
|
||||
background:
|
||||
radial-gradient(circle at 10% 0%, rgba(255,255,255,0.24), transparent 32%),
|
||||
linear-gradient(135deg, hsl(25, 90%, 45%), hsl(25, 95%, 35%));
|
||||
}
|
||||
.admin-modal-profile-img {
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
object-fit: cover;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 28px rgba(0,0,0,0.32), 0 0 0 4px rgba(255,255,255,0.18);
|
||||
}
|
||||
.admin-modal-title {
|
||||
margin: 0 42px 6px 0;
|
||||
font-size: 1.45em;
|
||||
font-weight: 850;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.admin-modal-subtitle {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
color: rgba(255,255,255,0.86);
|
||||
font-weight: 600;
|
||||
}
|
||||
.admin-modal-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 9px;
|
||||
border-radius: 9999px;
|
||||
background: rgba(255,255,255,0.14);
|
||||
border: 1px solid rgba(255,255,255,0.18);
|
||||
font-size: 0.82em;
|
||||
}
|
||||
.admin-modal-body {
|
||||
padding: 22px 24px 24px 24px;
|
||||
}
|
||||
.admin-section-title {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 0.9em;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: hsl(210, 18%, 36%);
|
||||
}
|
||||
.admin-modal-card .exit {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 1px solid rgba(255,255,255,0.25);
|
||||
background: rgba(255,255,255,0.16);
|
||||
color: #FFFFFF;
|
||||
font-size: 1.35em;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bs-detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.bs-detail-card {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid hsl(210, 18%, 88%);
|
||||
background: linear-gradient(180deg, #FFFFFF, hsl(210, 12%, 98%));
|
||||
}
|
||||
.bs-detail-label {
|
||||
margin-bottom: 3px;
|
||||
color: hsl(210, 14%, 46%);
|
||||
font-size: 0.74em;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.bs-detail-value {
|
||||
color: hsl(210, 20%, 18%);
|
||||
font-size: 1.02em;
|
||||
font-weight: 850;
|
||||
}
|
||||
.bs-detail-value.overdue {
|
||||
color: hsl(0, 65%, 42%);
|
||||
}
|
||||
.bs-detail-value.ok {
|
||||
color: hsl(145, 50%, 32%);
|
||||
}
|
||||
.admin-modal-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
border-top: 1px solid hsl(210, 18%, 90%);
|
||||
padding-top: 16px;
|
||||
}
|
||||
.admin-modal-actions button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 36px;
|
||||
padding: 8px 13px;
|
||||
border: 1px solid hsl(210, 18%, 84%);
|
||||
background: #FFFFFF;
|
||||
color: hsl(210, 20%, 22%);
|
||||
font-size: 0.86em;
|
||||
font-weight: 850;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
|
||||
}
|
||||
.admin-modal-actions button:hover {
|
||||
background: hsl(210, 57%, 96%);
|
||||
}
|
||||
.admin-save-button {
|
||||
color: #FFFFFF !important;
|
||||
background: linear-gradient(135deg, var(--nak-orange), hsl(25, 95%, 40%)) !important;
|
||||
border-color: hsl(25, 95%, 40%) !important;
|
||||
}
|
||||
.bs-date-input {
|
||||
padding: 10px 12px;
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-primary);
|
||||
border: 1px solid var(--gray-300);
|
||||
background: var(--gray-50);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-weight: 650;
|
||||
}
|
||||
.bs-date-input:focus {
|
||||
border-color: hsl(25, 80%, 55%);
|
||||
background: #FFFFFF;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 4px hsla(25, 80%, 55%, 0.14);
|
||||
}
|
||||
|
||||
@media (max-width: 780px) {
|
||||
.admin-users-shell { padding: 10px; }
|
||||
.admin-list-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 14px;
|
||||
gap: 8px;
|
||||
}
|
||||
table.admin-user-table {
|
||||
display: block !important;
|
||||
width: auto;
|
||||
margin: 8px;
|
||||
}
|
||||
.admin-user-table thead { display: none; }
|
||||
.admin-user-table tbody {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
gap: 0 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(120, 140, 160, 0.16);
|
||||
box-shadow: 0 8px 22px rgba(31, 38, 46, 0.08);
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row td {
|
||||
display: block;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row td:first-child {
|
||||
grid-row: 1 / span 3;
|
||||
width: 72px;
|
||||
min-width: 72px;
|
||||
max-width: 72px;
|
||||
}
|
||||
.admin-profile-button,
|
||||
.admin-profile-button img {
|
||||
width: 64px !important;
|
||||
height: 64px !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
.admin-modal-card {
|
||||
width: calc(100vw - 12px);
|
||||
max-height: calc(100dvh - 12px);
|
||||
}
|
||||
.admin-modal-hero {
|
||||
grid-template-columns: 78px minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
padding: 18px;
|
||||
}
|
||||
.admin-modal-profile-img {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.admin-modal-title {
|
||||
font-size: 1.15em;
|
||||
margin-right: 40px;
|
||||
}
|
||||
.admin-modal-body { padding: 18px; }
|
||||
.bs-detail-grid { grid-template-columns: 1fr; }
|
||||
.admin-modal-actions { flex-direction: column-reverse; }
|
||||
.admin-modal-actions button { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-admin">
|
||||
|
||||
@@ -487,45 +147,9 @@
|
||||
<tbody>
|
||||
<?php foreach ($bsUsers as $bsUser): ?>
|
||||
<?php
|
||||
$nameOutput = getDisplayName_bs($bsUser);
|
||||
$profileUrl = getProfileImageUrl_bs($bsUser, $g_profilePicturePath);
|
||||
|
||||
$lastTraining = $bsUser['last_training_date'];
|
||||
$reminderCount = (int)($bsUser['reminder_count'] ?? 0);
|
||||
$adminReminderCount = (int)($bsUser['admin_reminder_count'] ?? 0);
|
||||
|
||||
// Berechne Fälligkeit
|
||||
if (empty($lastTraining)) {
|
||||
$dueDate = null;
|
||||
$daysOverdue = null;
|
||||
$statusText = 'Nie geschult';
|
||||
$statusClass = 'bs-status-overdue';
|
||||
$dueDateDisplay = 'Sofort fällig';
|
||||
} else {
|
||||
$dueDate = date('Y-m-d', strtotime($lastTraining . ' +365 days'));
|
||||
$today = date('Y-m-d');
|
||||
$diff = (strtotime($dueDate) - strtotime($today)) / 86400;
|
||||
|
||||
if ($diff > 30) {
|
||||
$statusText = 'Aktuell';
|
||||
$statusClass = 'bs-status-ok';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
} elseif ($diff > 0) {
|
||||
$statusText = 'Bald fällig';
|
||||
$statusClass = 'bs-status-warning';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
} elseif ($diff > -120) {
|
||||
$daysOverdue = abs((int)$diff);
|
||||
$statusText = "{$daysOverdue} Tage überfällig";
|
||||
$statusClass = 'bs-status-overdue';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
} else {
|
||||
$daysOverdue = abs((int)$diff);
|
||||
$statusText = "{$daysOverdue} Tage überfällig";
|
||||
$statusClass = 'bs-status-critical';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
}
|
||||
}
|
||||
$nameOutput = (string) $bsUser['_display_name'];
|
||||
$profileUrl = (string) $bsUser['_profile_url'];
|
||||
$trainingStatus = $bsUser['_training_status'];
|
||||
?>
|
||||
<tr class="admin-user-row" tabindex="0" role="button" aria-label="<?= htmlspecialchars($nameOutput) ?> Details" onclick="openBsModal(<?= (int)$bsUser['user_id'] ?>)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();openBsModal(<?= (int)$bsUser['user_id'] ?>)}">
|
||||
<td>
|
||||
@@ -537,10 +161,10 @@
|
||||
<span class="admin-user-name"><?= htmlspecialchars($nameOutput) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="bs-due-date"><?= $dueDateDisplay ?></span>
|
||||
<span class="bs-due-date"><?= htmlspecialchars((string) $trainingStatus['due_date_display']) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="bs-status-badge <?= $statusClass ?>"><?= $statusText ?></span>
|
||||
<span class="bs-status-badge <?= htmlspecialchars((string) $trainingStatus['status_class']) ?>"><?= htmlspecialchars((string) $trainingStatus['status_text']) ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@@ -555,34 +179,12 @@
|
||||
<!-- Modals für jeden Brandschutz-Nutzer -->
|
||||
<?php foreach ($bsUsers as $bsUser): ?>
|
||||
<?php
|
||||
$nameOutput = getDisplayName_bs($bsUser);
|
||||
$profileUrl = getProfileImageUrl_bs($bsUser, $g_profilePicturePath);
|
||||
$nameOutput = (string) $bsUser['_display_name'];
|
||||
$profileUrl = (string) $bsUser['_profile_url'];
|
||||
$lastTraining = $bsUser['last_training_date'];
|
||||
$reminderCount = (int)($bsUser['reminder_count'] ?? 0);
|
||||
$adminReminderCount = (int)($bsUser['admin_reminder_count'] ?? 0);
|
||||
|
||||
if (empty($lastTraining)) {
|
||||
$dueDate = null;
|
||||
$dueDateDisplay = 'Sofort fällig';
|
||||
$lastTrainingDisplay = 'Nie';
|
||||
$overdueText = 'Keine Schulung hinterlegt – sofort fällig';
|
||||
$overdueClass = 'overdue';
|
||||
} else {
|
||||
$dueDate = date('Y-m-d', strtotime($lastTraining . ' +365 days'));
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
$lastTrainingDisplay = date('d.m.Y', strtotime($lastTraining));
|
||||
$today = date('Y-m-d');
|
||||
$diff = (strtotime($dueDate) - strtotime($today)) / 86400;
|
||||
|
||||
if ($diff > 0) {
|
||||
$overdueText = 'Nicht überfällig (' . (int)$diff . ' Tage verbleibend)';
|
||||
$overdueClass = 'ok';
|
||||
} else {
|
||||
$daysOverdue = abs((int)$diff);
|
||||
$overdueText = "{$daysOverdue} Tage überfällig";
|
||||
$overdueClass = 'overdue';
|
||||
}
|
||||
}
|
||||
$reminderCount = (int) ($bsUser['reminder_count'] ?? 0);
|
||||
$adminReminderCount = (int) ($bsUser['admin_reminder_count'] ?? 0);
|
||||
$trainingStatus = $bsUser['_training_status'];
|
||||
?>
|
||||
<div class="standard_overlay" id="bsModal-<?= (int)$bsUser['user_id'] ?>" onclick="if(event.target.id==='bsModal-<?= (int)$bsUser['user_id'] ?>')closeBsModal(<?= (int)$bsUser['user_id'] ?>)">
|
||||
<div class="admin-modal-card">
|
||||
@@ -605,15 +207,15 @@
|
||||
<div class="bs-detail-grid">
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Letzte Schulung</div>
|
||||
<div class="bs-detail-value"><?= $lastTrainingDisplay ?></div>
|
||||
<div class="bs-detail-value"><?= htmlspecialchars((string) $trainingStatus['last_training_display']) ?></div>
|
||||
</div>
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Nächste Fälligkeit</div>
|
||||
<div class="bs-detail-value"><?= $dueDateDisplay ?></div>
|
||||
<div class="bs-detail-value"><?= htmlspecialchars((string) $trainingStatus['due_date_display']) ?></div>
|
||||
</div>
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Überfälligkeit</div>
|
||||
<div class="bs-detail-value <?= $overdueClass ?>"><?= $overdueText ?></div>
|
||||
<div class="bs-detail-value <?= htmlspecialchars((string) $trainingStatus['overdue_class']) ?>"><?= htmlspecialchars((string) $trainingStatus['overdue_text']) ?></div>
|
||||
</div>
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Erinnerungsmails gesendet</div>
|
||||
@@ -629,6 +231,8 @@
|
||||
|
||||
<div class="admin-section-title">Schulungsdatum anpassen</div>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="action" value="update_training_date">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="bs_user_id" value="<?= (int)$bsUser['user_id'] ?>">
|
||||
<div style="display:flex;gap:10px;align-items:flex-end;margin-bottom:8px;">
|
||||
<div style="flex:1;">
|
||||
@@ -643,7 +247,7 @@
|
||||
<button type="button" onclick="closeBsModal(<?= (int)$bsUser['user_id'] ?>)">Schließen</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" name="bs_update_date" value="1" class="admin-save-button">Datum speichern</button>
|
||||
<button type="submit" class="admin-save-button">Datum speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -653,23 +257,7 @@
|
||||
<?php endforeach; ?>
|
||||
</div><!-- content -->
|
||||
|
||||
<script>
|
||||
function openBsModal(userId) {
|
||||
var modal = document.getElementById('bsModal-' + userId);
|
||||
if (modal) modal.style.display = 'flex';
|
||||
}
|
||||
function closeBsModal(userId) {
|
||||
var modal = document.getElementById('bsModal-' + userId);
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.standard_overlay[id^="bsModal-"]').forEach(function(m) {
|
||||
m.style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
- Info-Nachrichten */
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/authorization/util/authorization_helpers.php';
|
||||
$authorizationCsrfToken = authorization_csrf_token();
|
||||
|
||||
// Admin-Check
|
||||
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
|
||||
@@ -18,6 +20,11 @@
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !authorization_csrf_is_valid($_POST['csrf_token'] ?? null)) {
|
||||
header('Location: admin_inbox.php?inbox_status=error');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Typ-Konfiguration für Inbox
|
||||
$typeConfig = [
|
||||
'registration' => ['label' => 'Registrierung', 'class' => 'inbox-type-registration'],
|
||||
@@ -398,12 +405,14 @@
|
||||
<button type="button" class="inbox-btn inbox-btn-approve" style="background:linear-gradient(135deg,var(--nak-orange),hsl(25,95%,40%)) !important;border-color:hsl(25,95%,40%) !important;" onclick="openBsEscalationModal(<?= (int)$msg['id'] ?>, <?= (int)$data['user_id'] ?>, '<?= htmlspecialchars($data['user_name'] ?? '', ENT_QUOTES) ?>', <?= (int)($data['days_overdue'] ?? 0) ?>)">Persönliche Erinnerung bestätigen</button>
|
||||
<?php else: ?>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="csrf_token" value="<?= authorization_html($authorizationCsrfToken) ?>">
|
||||
<input type="hidden" name="mark_done" value="<?= (int)$msg['id'] ?>">
|
||||
<button type="submit" class="inbox-btn inbox-btn-done">Als erledigt markieren</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="csrf_token" value="<?= authorization_html($authorizationCsrfToken) ?>">
|
||||
<input type="hidden" name="mark_done" value="<?= (int)$msg['id'] ?>">
|
||||
<button type="submit" class="inbox-btn inbox-btn-done">Als erledigt markieren</button>
|
||||
</form>
|
||||
@@ -453,6 +462,7 @@
|
||||
|
||||
<div style="padding:22px 24px 24px 24px;">
|
||||
<form method="post" action="approve-registration.php" id="regModalForm">
|
||||
<input type="hidden" name="csrf_token" value="<?= authorization_html($authorizationCsrfToken) ?>">
|
||||
<input type="hidden" name="pending_id" id="regModalPendingId">
|
||||
<input type="hidden" name="message_id" id="regModalMessageId">
|
||||
<input type="hidden" name="action" id="regModalAction">
|
||||
@@ -542,6 +552,7 @@ document.addEventListener('keydown', function(e) {
|
||||
</div>
|
||||
|
||||
<form method="post" action="" id="bsEscForm">
|
||||
<input type="hidden" name="csrf_token" value="<?= authorization_html($authorizationCsrfToken) ?>">
|
||||
<input type="hidden" name="bs_escalation_msg_id" id="bsEscMsgId">
|
||||
<input type="hidden" name="bs_escalation_user_id" id="bsEscUserId">
|
||||
<input type="hidden" name="bs_escalation_confirm" value="1">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/authorization/util/authorization_helpers.php';
|
||||
|
||||
// Admin-Check
|
||||
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
|
||||
@@ -22,6 +23,11 @@
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!authorization_csrf_is_valid($_POST['csrf_token'] ?? null)) {
|
||||
header("Location: admin_inbox.php?inbox_status=error");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Admin-Name ermitteln
|
||||
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
if (empty($adminName)) $adminName = 'Ein Administrator';
|
||||
|
||||
Reference in New Issue
Block a user