Files
intranet/web/intranet_repo/projects/adminpanel/admin.php
T

1305 lines
42 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php /* Name: admin.php
Beschreibung: Administration Panel */
// Initialisierung
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
/**
* Gibt alle echten Rollen-Spalten aus user_permissions zurück.
* Dadurch werden auch künftig neu ergänzte Rollen automatisch angezeigt.
*/
function getPermissionRoles(PDO $pdo): array {
$roles = [];
$sql = "SELECT column_name FROM information_schema.columns WHERE table_name = 'user_permissions' AND table_schema = 'public'";
$result = $pdo->query($sql);
while ($column = $result->fetch(PDO::FETCH_ASSOC)) {
$field = $column['column_name'];
if (!in_array($field, ['p_id', 'user_id'], true)) {
$roles[] = $field;
}
}
return $roles;
}
function quoteIdentifier(string $identifier): string {
return '"' . str_replace('"', '""', $identifier) . '"';
}
function getProfileImageUrl(array $group, string $g_profilePicturePath): string {
$profileImg = glob($g_profilePicturePath."{$group['user_id']}-*.jpg");
if (!empty($profileImg)) {
usort($profileImg, function($a, $b) {
return filemtime($b) - filemtime($a);
});
$latestProfileImg = $profileImg[0];
return str_replace($_SERVER['DOCUMENT_ROOT'], '', $latestProfileImg);
}
return '/intranet_repo/general_files/profile_pictures/default.jpg';
}
function getDisplayName(array $group): string {
$fullName = trim(($group['first_name'] ?? '') . ' ' . ($group['last_name'] ?? ''));
return $fullName !== '' ? $fullName : ($group['mail'] ?? 'Unbekannt');
}
$permissionRoles = getPermissionRoles($pdo);
/* Besondere Admin-Rechte checken */
$stmt = $pdo->prepare("SELECT up.* FROM user_permissions up WHERE up.user_id = ? LIMIT 1");
$stmt->execute([$_SESSION["user_id"]]);
$isAdmin = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$isAdmin || (int)($isAdmin["Admin"] ?? 0) !== 1) {
header("Location: {$g_httpHomePath}");
exit();
}
// Neuen Nutzer inklusive Rollen anlegen
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["addRecord"])) {
$newMail = trim($_POST["newMail"] ?? '');
$newFirstName = trim($_POST["newFirstName"] ?? '');
$newLastName = trim($_POST["newLastName"] ?? '');
$selectedRoles = $_POST["newRoles"] ?? [];
if (!is_array($selectedRoles)) {
$selectedRoles = [];
}
if (empty($newMail) || empty($newFirstName) || empty($newLastName)) {
header("Location: admin.php?message=7");
exit();
}
$checkSql = "SELECT COUNT(*) FROM user_general_information WHERE mail=?";
$checkStmt = $pdo->prepare($checkSql);
$checkStmt->execute([$newMail]);
$count = $checkStmt->fetchColumn();
if ($count > 0) {
header("Location: admin.php?message=4");
exit();
}
$pdo->beginTransaction();
try {
$insertSql = "INSERT INTO user_general_information (mail, first_name, last_name) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($insertSql);
$stmt->execute([$newMail, $newFirstName, $newLastName]);
$newId = $pdo->lastInsertId();
// Fallback, falls insert_id wegen DB-Konfiguration nicht gesetzt ist
if (!$newId) {
$checkSql = "SELECT user_id FROM user_general_information WHERE mail=?";
$checkStmt = $pdo->prepare($checkSql);
$checkStmt->execute([$newMail]);
$row = $checkStmt->fetch(PDO::FETCH_ASSOC);
$newId = $row['user_id'];
}
$roleColumns = array_map('quoteIdentifier', $permissionRoles);
$columns = array_merge(['user_id'], $roleColumns);
$placeholders = array_fill(0, count($columns), '?');
$roleValues = [];
foreach ($permissionRoles as $role) {
$roleValues[] = in_array($role, $selectedRoles, true) ? 1 : 0;
}
$params = array_merge([(int)$newId], $roleValues);
$insertPermissionsSql = "INSERT INTO user_permissions (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
$stmt = $pdo->prepare($insertPermissionsSql);
$stmt->execute($params);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
// Einladungsmail an den neuen Nutzer senden
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
$displayName = trim("{$newFirstName} {$newLastName}");
$greeting = !empty($displayName) ? "Hallo {$newFirstName}" : "Hallo";
$signupUrl = "https://" . $_SERVER['HTTP_HOST'] . "/intranet_repo/projects/authorization/signup.php";
$userBetreff = "Einladung zum Intranet NAK Reinickendorf";
$userNachricht = "{$greeting},\n\n";
$userNachricht .= "Sie wurden zum Intranet der NAK Reinickendorf eingeladen.\n\n";
$userNachricht .= "Bitte registrieren Sie sich mit der E-Mail-Adresse {$newMail} über den folgenden Link, um Ihren Zugang zu aktivieren.";
send_intranet_mail($newMail, $userBetreff, $userNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt registrieren", $signupUrl);
// Info-Mail an Admins
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
if (empty($adminName)) $adminName = 'Ein Administrator';
$nutzerAnzeige = !empty($displayName) ? $displayName : $newMail;
$adminBetreff = "Neuer Nutzer angelegt: {$nutzerAnzeige}";
$adminNachricht = "{$adminName} hat einen neuen Nutzer angelegt:\n\n";
$adminNachricht .= "Name: " . (!empty($displayName) ? $displayName : '(kein Name)') . "\n";
$adminNachricht .= "E-Mail: {$newMail}\n";
$adminNachricht .= "Rollen: " . (!empty($selectedRoles) ? implode(', ', $selectedRoles) : 'Keine');
send_admin_team_mail($pdo, $adminBetreff, $adminNachricht);
header("Location: admin.php?message=3");
exit();
}
// Einladungsmail erneut senden
if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["resendInvite"])) {
$resendUserId = (int)($_GET["resendInvite"] ?? 0);
if ($resendUserId > 0) {
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
$stmt = $pdo->prepare("SELECT mail, first_name, last_name FROM user_general_information WHERE user_id = ?");
$stmt->execute([$resendUserId]);
$invUser = $stmt->fetch(PDO::FETCH_ASSOC);
if ($invUser) {
$displayName = trim(($invUser['first_name'] ?? '') . ' ' . ($invUser['last_name'] ?? ''));
$greeting = !empty($invUser['first_name']) ? "Hallo {$invUser['first_name']}" : "Hallo";
$signupUrl = "https://" . $_SERVER['HTTP_HOST'] . "/intranet_repo/projects/authorization/signup.php";
$userBetreff = "Erinnerung: Einladung zum Intranet NAK Reinickendorf";
$userNachricht = "{$greeting},\n\n";
$userNachricht .= "Sie wurden zum Intranet der NAK Reinickendorf eingeladen und haben sich noch nicht registriert.\n\n";
$userNachricht .= "Bitte registrieren Sie sich mit der E-Mail-Adresse {$invUser['mail']} über den folgenden Link, um Ihren Zugang zu aktivieren.";
send_intranet_mail($invUser['mail'], $userBetreff, $userNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt registrieren", $signupUrl);
}
}
header("Location: admin.php?message=6");
exit();
}
// Rollen speichern
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["editRecord"])) {
$editedID = (int)($_POST["editedID"] ?? 0);
$selectedRoles = $_POST["roles"] ?? [];
if (!is_array($selectedRoles)) {
$selectedRoles = [];
}
// Update name, email and phone in user_general_information
$editFirstName = trim($_POST["edit_first_name"] ?? '');
$editLastName = trim($_POST["edit_last_name"] ?? '');
$editMail = trim($_POST["edit_mail"] ?? '');
$editPhone = trim($_POST["edit_phone"] ?? '');
$editPhoneVal = ($editPhone !== '') ? $editPhone : null;
if ($editMail !== '') {
$stmtInfo = $pdo->prepare("UPDATE user_general_information SET first_name=?, last_name=?, mail=?, phone=? WHERE user_id=?");
$stmtInfo->execute([$editFirstName, $editLastName, $editMail, $editPhoneVal, $editedID]);
}
// Update roles in user_permissions
$setParts = [];
$params = [];
foreach ($permissionRoles as $role) {
$setParts[] = quoteIdentifier($role) . "=?";
$params[] = in_array($role, $selectedRoles, true) ? 1 : 0;
}
$params[] = $editedID;
$updateSql = "UPDATE user_permissions SET " . implode(', ', $setParts) . " WHERE user_id=?";
$stmt = $pdo->prepare($updateSql);
$stmt->execute($params);
// Vermerk in erledigte Tasks
$stmt = $pdo->prepare("SELECT mail, first_name, last_name FROM user_general_information WHERE user_id = ?");
$stmt->execute([$editedID]);
$editedUser = $stmt->fetch(PDO::FETCH_ASSOC);
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
if (empty($adminName)) $adminName = 'Ein Administrator';
$editedUserName = trim(($editedUser['first_name'] ?? '') . ' ' . ($editedUser['last_name'] ?? ''));
if (empty($editedUserName)) $editedUserName = $editedUser['mail'] ?? 'Unbekannt';
$assignedRoles = !empty($selectedRoles) ? implode(', ', $selectedRoles) : 'Keine';
$taskSubject = "Profil aktualisiert: {$editedUserName}";
$taskMessage = "{$adminName} hat das Profil von {$editedUserName} aktualisiert.\nNeue Rollen: {$assignedRoles}";
$stmt = $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'] ?? '';
$stmt->execute([$adminName, $senderMail, $taskSubject, $taskMessage]);
header("Location: admin.php?message=5");
exit();
}
if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["delete"])) {
$deleteUserId = (int)$_GET["delete"];
$deleteSql = "DELETE FROM user_general_information WHERE user_id=?";
$stmt = $pdo->prepare($deleteSql);
$stmt->execute([$deleteUserId]);
header("Location: admin.php?message=2");
exit();
}
if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["resetPassword"])) {
$resetUserId = (int)$_GET["resetPassword"];
// Login-Daten löschen (Name und Telefon bleiben erhalten)
$deleteSql = "DELETE FROM user_login WHERE user_id=?";
$stmt = $pdo->prepare($deleteSql);
$stmt->execute([$resetUserId]);
// User-Daten für Mail laden
$stmt = $pdo->prepare("SELECT mail, first_name, last_name FROM user_general_information WHERE user_id = ?");
$stmt->execute([$resetUserId]);
$resetUser = $stmt->fetch(PDO::FETCH_ASSOC);
if ($resetUser) {
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
$signupUrl = "https://" . $_SERVER['HTTP_HOST'] . "/intranet_repo/projects/authorization/signup.php";
$greeting = !empty($resetUser['first_name']) ? "Hallo {$resetUser['first_name']}" : "Hallo";
$userBetreff = "Passwort zurückgesetzt";
$userNachricht = "{$greeting},\n\n";
$userNachricht .= "Ihr Passwort für das Intranet der NAK Reinickendorf wurde von einem Administrator zurückgesetzt.\n\n";
$userNachricht .= "Bitte registrieren Sie sich erneut über den folgenden Link, um ein neues Passwort festzulegen.";
send_intranet_mail($resetUser['mail'], $userBetreff, $userNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt neu registrieren", $signupUrl);
// Vermerk in erledigte Tasks schreiben
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
if (empty($adminName)) $adminName = 'Ein Administrator';
$resetUserName = trim(($resetUser['first_name'] ?? '') . ' ' . ($resetUser['last_name'] ?? ''));
if (empty($resetUserName)) $resetUserName = $resetUser['mail'];
$taskSubject = "Passwort zurückgesetzt: {$resetUserName}";
$taskMessage = "{$adminName} hat das Passwort von {$resetUserName} ({$resetUser['mail']}) zurückgesetzt. Eine Registrierungsmail wurde versendet.";
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
$stmt->execute([$adminName, $resetUser['mail'], $taskSubject, $taskMessage]);
}
header("Location: admin.php?message=1");
exit();
}
if (isset($_GET['partner_id'])) {
$_SESSION["profile_id"] = (int)$_GET['partner_id'];
header("Location: admin_profiles.php");
exit();
}
// Nutzer inklusive aller Rechte laden: Registrierte Nutzer zuerst, dann Name, dann Mail.
$sql_groups = "SELECT up.*, ugi.mail, ugi.first_name, ugi.last_name, ugi.phone,
CASE WHEN ul.user_id IS NOT NULL THEN 1 ELSE 0 END AS \"IstRegistriert\"
FROM user_permissions up
LEFT JOIN user_general_information ugi ON up.user_id = ugi.user_id
LEFT JOIN user_login ul ON up.user_id = ul.user_id
ORDER BY \"IstRegistriert\" DESC, COALESCE(ugi.last_name, ''), COALESCE(ugi.first_name, ''), ugi.mail";
$result_groups = $pdo->query($sql_groups);
$groups = $result_groups->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<style>
/* Controls moved to table header */
.admin-list-controls {
display: flex;
align-items: center;
gap: 10px;
}
.admin-list-filter-control {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 5px 9px;
min-height: 30px;
background: rgba(120, 140, 160, 0.08);
border: 1px solid rgba(120, 140, 160, 0.2);
}
.admin-list-filter-control svg {
width: 15px;
height: 15px;
fill: none;
stroke: hsl(210, 16%, 46%);
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
opacity: 0.8;
}
.admin-list-filter-wrap {
position: relative;
display: inline-flex;
align-items: center;
}
.admin-list-filter-wrap::after {
content: '▾';
position: absolute;
right: 2px;
font-size: 0.7em;
pointer-events: none;
opacity: 0.6;
color: hsl(210, 16%, 46%);
}
.admin-list-filter-control select {
appearance: none;
-webkit-appearance: none;
min-width: 120px;
max-width: 22vw;
padding: 2px 20px 2px 2px;
color: hsl(210, 20%, 25%);
background: transparent;
border: none;
outline: none;
font-size: 0.86em;
font-weight: 650;
cursor: pointer;
}
.admin-list-filter-control select option {
color: hsl(210, 20%, 15%);
background: #FFFFFF;
}
.admin-list-add-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 14px;
color: #FFFFFF;
background: linear-gradient(135deg, var(--gray-800), var(--gray-900));
border: 1px solid var(--gray-900);
border-radius: 0;
font-size: 0.86em;
font-weight: 850;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
}
.admin-list-add-btn:hover {
background: linear-gradient(135deg, var(--gray-900), #1a1a2e);
color: #FFFFFF;
text-decoration: none;
}
.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);
border-radius: 0;
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:after { display: none; }
.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;
box-shadow: none;
}
.admin-user-table thead th::after { display: none; }
.admin-user-table tbody tr.admin-user-row {
position: relative;
cursor: pointer;
background: rgba(255, 255, 255, 0.92);
border-radius: 0;
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,
.admin-user-table tbody tr.admin-user-row:focus {
transform: translateY(-1px);
background: #FFFFFF;
box-shadow: 0 12px 26px rgba(31, 38, 46, 0.13);
outline: none !important;
filter: none !important;
}
.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 {
position: static !important;
width: 82px;
min-width: 82px;
max-width: 82px;
border-radius: 0;
}
.admin-user-table tbody tr.admin-user-row td:last-child {
border-radius: 0;
}
.admin-profile-button,
.admin-name-button {
padding: 0;
margin: 0;
border: 0;
background: transparent;
font: inherit;
color: inherit;
cursor: pointer;
text-align: left;
}
.admin-profile-button {
display: block;
width: 58px;
height: 58px;
border-radius: 12px;
overflow: hidden;
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,
.admin-user-table img {
display: block;
width: 58px !important;
height: 58px !important;
padding: 0 !important;
object-fit: cover;
border: none !important;
border-radius: 12px !important;
transition: transform 0.2s ease;
}
.admin-profile-button:hover img,
.admin-user-table tbody tr.admin-user-row:hover .admin-profile-button img,
.admin-user-table tbody tr.admin-user-row:focus .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%);
}
.admin-name-button:hover .admin-user-name {
color: var(--theme-color, var(--nak-blue));
text-decoration: none;
}
.admin-registration-badge {
display: inline-flex;
align-items: center;
padding: 2px 7px;
border-radius: 0;
font-size: 0.72em;
font-weight: 800;
color: hsl(145, 50%, 32%);
background: hsl(145, 50%, 93%);
}
.admin-registration-badge.is-muted {
color: hsl(210, 12%, 48%);
background: hsl(210, 12%, 92%);
}
.admin-user-mail {
color: hsl(210, 14%, 40%);
font-weight: 600;
}
.admin-role-pills {
display: flex;
flex-flow: row wrap;
gap: 6px;
max-width: 460px;
}
.admin-role-pill {
display: inline-flex;
align-items: center;
padding: 4px 8px;
border-radius: 0;
color: hsl(210, 57%, 34%);
background: hsl(210, 57%, 94%);
border: 1px solid hsl(210, 57%, 88%);
font-size: 0.78em;
font-weight: 750;
}
.admin-role-more {
color: hsl(210, 12%, 42%);
background: hsl(210, 12%, 94%);
border-color: hsl(210, 12%, 88%);
}
.admin-empty-filter-row td {
padding: 24px !important;
text-align: center !important;
color: hsl(210, 20%, 45%);
background: #FFFFFF !important;
border-radius: 0;
}
.admin-modal-card {
position: relative;
width: min(860px, calc(100vw - 24px));
max-height: min(860px, calc(100vh - 24px));
overflow: auto;
padding: 0;
background: #FFFFFF;
border-radius: 0;
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, var(--gray-800), var(--gray-900));
}
.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-name-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 6px;
}
.admin-role-checklist {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 10px;
margin: 0 0 20px 0;
}
.admin-role-checklist label {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 12px;
border: 1px solid hsl(210, 18%, 88%);
border-radius: 0;
background: hsl(210, 12%, 98%);
color: hsl(210, 20%, 20%);
font-weight: 750;
cursor: pointer;
transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
hyphens: auto;
-webkit-hyphens: auto;
overflow-wrap: break-word;
word-break: break-word;
}
.admin-role-checklist label:hover {
transform: translateY(-1px);
background: hsl(210, 57%, 97%);
border-color: hsl(210, 57%, 78%);
}
.admin-role-checklist input[type="checkbox"] {
width: 20px;
height: 20px;
min-width: 20px;
min-height: 20px;
flex-shrink: 0;
margin: 0;
accent-color: var(--nak-blue);
}
.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-left,
.admin-modal-actions-right {
display: flex;
flex-flow: row wrap;
gap: 8px;
}
.admin-modal-actions button,
.admin-modal-actions a {
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
min-height: 36px;
padding: 8px 13px;
border: 1px solid hsl(210, 18%, 84%);
border-radius: 0;
background: #FFFFFF;
color: hsl(210, 20%, 22%);
font-size: 0.86em;
font-weight: 850;
text-decoration: none;
cursor: pointer;
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
white-space: nowrap;
}
.admin-modal-actions button:hover,
.admin-modal-actions a:hover {
background: hsl(210, 57%, 96%);
text-decoration: none;
}
.admin-save-button {
color: #FFFFFF !important;
background: linear-gradient(135deg, var(--nak-blue), var(--nak-blue-hover)) !important;
border-color: var(--nak-blue-hover) !important;
}
.admin-danger-button {
color: var(--nak-red) !important;
border-color: hsl(352, 72%, 88%) !important;
background: hsl(352, 72%, 97%) !important;
}
.admin-warning-button {
color: hsl(35, 78%, 35%) !important;
border-color: hsl(42, 90%, 82%) !important;
background: hsl(45, 90%, 96%) !important;
}
.admin-modal-card .exit {
position: absolute;
top: 14px;
right: 14px;
width: 34px;
height: 34px;
border-radius: 0;
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;
}
.admin-add-mail-field {
width: 100%;
padding: 13px 14px;
border: 1px solid hsl(210, 18%, 84%);
border-radius: 0;
background: hsl(210, 12%, 98%);
color: hsl(210, 20%, 18%);
font-size: 1em;
font-weight: 650;
outline: none;
box-shadow: inset 0 1px 2px rgba(31,38,46,0.04);
transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
box-sizing: border-box;
}
.admin-add-mail-field:focus {
border-color: hsl(210, 57%, 55%);
background: #FFFFFF;
box-shadow: 0 0 0 4px hsla(210, 57%, 55%, 0.14);
}
.admin-field-help {
margin: 7px 0 18px 0;
color: hsl(210, 14%, 45%);
font-size: 0.88em;
font-weight: 600;
}
.admin-add-summary {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 18px;
}
.admin-add-summary-card {
padding: 12px 14px;
border: 1px solid hsl(210, 18%, 88%);
border-radius: 0;
background: linear-gradient(180deg, #FFFFFF, hsl(210, 12%, 98%));
}
.admin-add-summary-label {
margin-bottom: 3px;
color: hsl(210, 14%, 46%);
font-size: 0.74em;
font-weight: 850;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.admin-add-summary-value {
color: hsl(210, 20%, 18%);
font-size: 1.02em;
font-weight: 850;
}
@media (max-width: 780px) {
.admin-list-header {
align-items: flex-start;
flex-direction: column;
padding: 14px;
gap: 8px;
}
.admin-list-controls {
width: 100%;
justify-content: space-between;
}
#fullsize.content {
top: 0;
min-height: calc(100vh - var(--header-height) - var(--header-title-height) - 2px);
}
.admin-users-shell {
padding: 10px;
}
.admin-list-header {
align-items: flex-start;
flex-direction: column;
padding: 14px;
}
table.admin-user-table {
display: block !important;
width: auto;
margin: 8px;
padding: 0;
border-spacing: 0;
}
.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);
border-radius: 0;
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;
border-radius: 0 !important;
}
.admin-user-table tbody tr.admin-user-row td:first-child {
grid-row: 1 / span 2;
width: 72px;
min-width: 72px;
max-width: 72px;
}
.admin-user-table tbody tr.admin-user-row td:nth-child(2) {
align-self: end;
min-width: 0;
}
.admin-user-table tbody tr.admin-user-row td:nth-child(3) {
align-self: start;
min-width: 0;
padding-top: 5px !important;
}
.admin-profile-button,
.admin-profile-button img,
.admin-user-table img {
width: 64px !important;
height: 64px !important;
border-radius: 12px !important;
}
.admin-user-name,
.admin-user-mail {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-registration-badge {
margin-top: 2px;
}
.admin-empty-filter-row {
display: block !important;
}
.admin-empty-filter-row[style*="display:none"] {
display: none !important;
}
.admin-empty-filter-row td {
display: block;
}
.admin-modal-card {
width: calc(100vw - 12px);
max-height: calc(100dvh - 12px);
border-radius: 0;
}
.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 {
margin-right: 40px;
font-size: 1.15em;
}
.admin-modal-subtitle {
font-size: 0.9em;
}
.admin-modal-body {
padding: 18px;
}
.admin-role-checklist {
grid-template-columns: 1fr;
gap: 8px;
}
.admin-modal-actions {
flex-direction: column-reverse;
}
.admin-modal-actions-left,
.admin-modal-actions-right {
width: 100%;
}
.admin-modal-actions button,
.admin-modal-actions a {
flex: 1 1 100%;
width: 100%;
max-width: 100%;
box-sizing: border-box;
white-space: normal;
}
}
@media (max-width: 480px) {
.admin-users-shell {
padding: 8px;
}
.admin-table-card {
overflow: hidden;
border-radius: 0;
}
.admin-list-title {
font-size: 1em;
}
.admin-list-hint {
font-size: 0.8em;
}
.admin-user-table tbody tr.admin-user-row {
grid-template-columns: 62px minmax(0, 1fr);
gap: 0 10px;
padding: 10px;
}
.admin-profile-button,
.admin-profile-button img,
.admin-user-table img {
width: 56px !important;
height: 56px !important;
border-radius: 10px !important;
}
.admin-user-name {
font-size: 0.96em;
}
.admin-user-mail {
font-size: 0.88em;
}
.admin-modal-body [style*="grid-template-columns: 1fr 1fr"] {
grid-template-columns: 1fr !important;
}
.admin-name-grid {
grid-template-columns: 1fr;
}
.admin-modal-actions-left,
.admin-modal-actions-right {
flex-direction: column;
}
.admin-modal-actions button,
.admin-modal-actions a {
width: 100%;
max-width: 100%;
box-sizing: border-box;
align-self: stretch;
white-space: normal;
}
.admin-modal-actions-left,
.admin-modal-actions-right {
flex-direction: column;
align-items: stretch;
width: 100%;
}
}
</style>
</head>
<body class="theme-admin">
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
<?php
include $g_phpSnippetsPath.'overlay_message.php';
?>
<div class="header_title ht_admin text_white unselectable" id="header_title">
<span class="header_title_text">Adminpanel</span>
</div>
<div class="content" id="fullsize">
<?php $adminActiveTab = 'users'; require __DIR__.'/_tabs.php'; ?>
<?php if (isset($_SESSION["user_id"])): ?>
<div class="admin-users-shell">
<div class="admin-table-card">
<div class="admin-list-header">
<div class="admin-list-hint"><span id="visibleUserCount"><?= count($groups) ?></span> Nutzer angezeigt</div>
<div class="admin-list-controls">
<div class="admin-list-filter-control" title="Nach Rolle filtern">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 5h18"></path>
<path d="M6 12h12"></path>
<path d="M10 19h4"></path>
</svg>
<div class="admin-list-filter-wrap">
<select id="roleFilter" onchange="filterUsersByRole()" aria-label="Nach Rolle filtern">
<option value="">Alle Rollen</option>
<?php foreach ($permissionRoles as $role): ?>
<option value="<?= htmlspecialchars($role) ?>"><?= htmlspecialchars(genderRole($role)) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<a href="#" class="admin-list-add-btn" onclick="openAddUserModal(); return false;" title="Neuen Nutzer anlegen">
+ Neu
</a>
</div>
</div>
<table class="eventbox_table admin-user-table" id="admintable">
<thead class="unselectable">
<tr>
<th>Profil</th>
<th>Name</th>
<th>E-Mail-Adresse</th>
</tr>
</thead>
<tbody>
<?php foreach ($groups as $group): ?>
<?php
$activeRoles = [];
foreach ($permissionRoles as $role) {
if ((int)($group[$role] ?? 0) === 1) {
$activeRoles[] = $role;
}
}
$nameOutput = getDisplayName($group);
$isRegistered = ((int)($group['IstRegistriert'] ?? 0) === 1);
$mailOutput = $group['mail'] ?? '';
$profileUrl = getProfileImageUrl($group, $g_profilePicturePath);
?>
<tr class="admin-user-row" id="row-<?= (int)$group['user_id'] ?>" tabindex="0" role="button" aria-label="<?= htmlspecialchars($nameOutput) ?> bearbeiten" data-roles="<?= htmlspecialchars('|'.implode('|', $activeRoles).'|') ?>" onclick="openRoleModal(<?= (int)$group['user_id'] ?>)" onkeydown="openRoleModalByKeyboard(event, <?= (int)$group['user_id'] ?>)">
<td>
<button class="admin-profile-button" type="button" tabindex="-1" aria-hidden="true">
<img src="<?= htmlspecialchars($profileUrl) ?>" alt="Profilbild von <?= htmlspecialchars($nameOutput) ?>">
</button>
</td>
<td>
<button class="admin-name-button" type="button" tabindex="-1" aria-hidden="true">
<span class="admin-user-name"><?= htmlspecialchars($nameOutput) ?></span>
</button>
<div style="margin-top:6px;">
<span class="admin-registration-badge <?= $isRegistered ? '' : 'is-muted' ?>">
<?= $isRegistered ? 'registriert' : 'nicht registriert' ?>
</span>
</div>
</td>
<td><span class="admin-user-mail"><?= htmlspecialchars($mailOutput) ?></span></td>
</tr>
<?php endforeach; ?>
<tr id="emptyFilterRow" class="admin-empty-filter-row" style="display:none;">
<td colspan="3">Keine Nutzer mit dieser Rolle gefunden.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="standard_overlay" id="addNewUser" onclick="closeAddUserModalOnBackdrop(event)">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeAddUserModal()">×</button>
<div class="admin-modal-hero">
<img class="admin-modal-profile-img" src="/intranet_repo/general_files/profile_pictures/default.jpg" alt="Neuer Nutzer">
<div>
<div class="admin-modal-title">Neuen Nutzer anlegen</div>
<div class="admin-modal-subtitle">
<span class="admin-modal-chip">neues Profil</span>
</div>
</div>
</div>
<div class="admin-modal-body">
<form method="post" action="">
<div class="admin-section-title">Name</div>
<div class="admin-name-grid">
<input class="admin-add-mail-field" type="text" name="newFirstName" placeholder="Vorname" required>
<input class="admin-add-mail-field" type="text" name="newLastName" placeholder="Nachname" required>
</div>
<div class="admin-section-title">E-Mail-Adresse</div>
<input class="admin-add-mail-field" type="email" name="newMail" placeholder="name@example.de" autocomplete="email" required>
<div class="admin-field-help">Der Nutzer erhält eine Einladungsmail zur Registrierung.</div>
<div class="admin-section-title">Rollen vergeben</div>
<div class="admin-role-checklist">
<?php foreach ($permissionRoles as $role): ?>
<label>
<input type="checkbox" name="newRoles[]" value="<?= htmlspecialchars($role) ?>">
<span><?= htmlspecialchars(genderRole($role)) ?></span>
</label>
<?php endforeach; ?>
</div>
<div class="admin-modal-actions">
<div class="admin-modal-actions-left"></div>
<div class="admin-modal-actions-right">
<button type="button" onclick="closeAddUserModal()">Abbrechen</button>
<button class="admin-save-button" type="submit" name="addRecord" value="1">Nutzer anlegen</button>
</div>
</div>
</form>
</div>
</div>
</div>
<?php foreach ($groups as $group): ?>
<?php
$nameOutput = getDisplayName($group);
$isRegistered = ((int)($group['IstRegistriert'] ?? 0) === 1);
$profileUrl = getProfileImageUrl($group, $g_profilePicturePath);
$mailOutput = $group['mail'] ?? '';
?>
<div class="standard_overlay" id="roleModal-<?= (int)$group['user_id'] ?>" onclick="closeRoleModalOnBackdrop(event, <?= (int)$group['user_id'] ?>)">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeRoleModal(<?= (int)$group['user_id'] ?>)">×</button>
<div class="admin-modal-hero">
<img class="admin-modal-profile-img" src="<?= htmlspecialchars($profileUrl) ?>" alt="Profilbild von <?= htmlspecialchars($nameOutput) ?>">
<div>
<div class="admin-modal-title"><?= htmlspecialchars($nameOutput) ?></div>
<div class="admin-modal-subtitle">
<span><?= htmlspecialchars($mailOutput) ?></span>
<span class="admin-modal-chip"><?= $isRegistered ? 'Registriertes Profil' : 'Noch nicht registriert' ?></span>
<span class="admin-modal-chip">ID <?= (int)$group['user_id'] ?></span>
</div>
<div class="admin-modal-subtitle" style="margin-bottom:0;">Rollen und Kontoaktionen verwalten</div>
</div>
</div>
<div class="admin-modal-body">
<form method="post" action="">
<input type="hidden" name="editedID" value="<?= (int)$group['user_id'] ?>">
<div class="admin-section-title">Nutzerdaten</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm);margin-bottom:var(--space-md);">
<div style="display:flex;flex-direction:column;gap:4px;">
<label style="font-size:var(--font-size-xs);font-weight:600;color:var(--gray-600);">Vorname</label>
<input type="text" name="edit_first_name" value="<?= htmlspecialchars($group['first_name'] ?? '') ?>" style="padding:8px 10px;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;">
</div>
<div style="display:flex;flex-direction:column;gap:4px;">
<label style="font-size:var(--font-size-xs);font-weight:600;color:var(--gray-600);">Nachname</label>
<input type="text" name="edit_last_name" value="<?= htmlspecialchars($group['last_name'] ?? '') ?>" style="padding:8px 10px;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;">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm);margin-bottom:var(--space-lg);">
<div style="display:flex;flex-direction:column;gap:4px;">
<label style="font-size:var(--font-size-xs);font-weight:600;color:var(--gray-600);">E-Mail-Adresse</label>
<input type="email" name="edit_mail" value="<?= htmlspecialchars($mailOutput) ?>" required style="padding:8px 10px;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;">
</div>
<div style="display:flex;flex-direction:column;gap:4px;">
<label style="font-size:var(--font-size-xs);font-weight:600;color:var(--gray-600);">Telefonnummer</label>
<div style="display:flex;align-items:stretch;">
<div class="input-prefix" style="padding:8px 10px;font-size:var(--font-size-sm);border-radius:0;">+49</div>
<input type="tel" name="edit_phone" value="<?= htmlspecialchars($group['phone'] ?? '') ?>" class="phone-input" style="padding:8px 10px;font-size:var(--font-size-sm);font-family:var(--font-primary);border:1px solid var(--gray-300);border-left:none;background:var(--gray-50);width:100%;box-sizing:border-box;flex:1;">
</div>
</div>
</div>
<div class="admin-section-title">Rollen</div>
<div class="admin-role-checklist">
<?php foreach ($permissionRoles as $role): ?>
<?php $checked = ((int)($group[$role] ?? 0) === 1); ?>
<label>
<input type="checkbox" name="roles[]" value="<?= htmlspecialchars($role) ?>" <?= $checked ? 'checked' : '' ?>>
<span><?= htmlspecialchars(genderRole($role)) ?></span>
</label>
<?php endforeach; ?>
</div>
<div class="admin-modal-actions">
<div class="admin-modal-actions-left">
<?php if (!$isRegistered): ?>
<a class="admin-save-button" style="background:linear-gradient(135deg, hsl(145,55%,42%), hsl(145,55%,34%)) !important;border-color:hsl(145,55%,34%) !important;" href="?resendInvite=<?= (int)$group['user_id'] ?>" onclick="return confirm('Einladungsmail erneut senden?')">Einladung senden</a>
<?php endif; ?>
<a class="admin-warning-button" href="?resetPassword=<?= (int)$group['user_id'] ?>" onclick="return confirm('Sind Sie sicher, dass Sie das Passwort dieses Nutzers zurücksetzen möchten?')">Passwort zurücksetzen</a>
<a class="admin-danger-button" href="?delete=<?= (int)$group['user_id'] ?>" onclick="return confirm('Sind Sie sicher, dass Sie diesen Nutzer löschen möchten?')">Nutzer löschen</a>
</div>
<div class="admin-modal-actions-right">
<button type="button" onclick="closeRoleModal(<?= (int)$group['user_id'] ?>)">Abbrechen</button>
<button class="admin-save-button" type="submit" name="editRecord" value="1">Änderungen speichern</button>
</div>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
<?php else:
header("Location: $g_httpLoginPath");
exit();
endif; ?>
</div><!-- EOD: content fullsize -->
<script>
function openAddUserModal() {
const modal = document.getElementById('addNewUser');
if (modal) {
modal.style.display = 'flex';
const mailField = modal.querySelector('input[name="newMail"]');
if (mailField) {
setTimeout(() => mailField.focus(), 80);
}
}
}
function closeAddUserModal() {
const modal = document.getElementById('addNewUser');
if (modal) {
modal.style.display = 'none';
}
}
function closeAddUserModalOnBackdrop(event) {
if (event.target.id === 'addNewUser') {
closeAddUserModal();
}
}
function openRoleModal(userId) {
const modal = document.getElementById('roleModal-' + userId);
if (modal) {
modal.style.display = 'flex';
}
}
function closeRoleModal(userId) {
const modal = document.getElementById('roleModal-' + userId);
if (modal) {
modal.style.display = 'none';
}
}
function closeRoleModalOnBackdrop(event, userId) {
if (event.target.id === 'roleModal-' + userId) {
closeRoleModal(userId);
}
}
function openRoleModalByKeyboard(event, userId) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
openRoleModal(userId);
}
}
function filterUsersByRole() {
const selectedRole = document.getElementById('roleFilter').value;
const rows = document.querySelectorAll('#admintable tbody tr[data-roles]');
const emptyRow = document.getElementById('emptyFilterRow');
const visibleUserCount = document.getElementById('visibleUserCount');
let visibleRows = 0;
rows.forEach(row => {
const roles = row.dataset.roles || '';
const showRow = selectedRole === '' || roles.includes('|' + selectedRole + '|');
row.style.display = showRow ? '' : 'none';
if (showRow) {
visibleRows++;
}
});
if (emptyRow) {
emptyRow.style.display = visibleRows === 0 ? '' : 'none';
}
if (visibleUserCount) {
visibleUserCount.textContent = visibleRows;
}
}
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
document.querySelectorAll('.standard_overlay[id^="roleModal-"], #addNewUser').forEach(modal => {
modal.style.display = 'none';
});
}
});
</script>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>