Files
deskhub/DeskHub-Webseite/staff/roles.php
T
2026-11-05 21:21:52 +00:00

257 lines
15 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
require_once __DIR__ . '/../includes/bootstrap.php';
require_once __DIR__ . '/_ui.php';
$staff = require_admin_or_permission('manage_roles');
$pdo = get_db();
// Alle Rechte, Gruppen, Beschreibungen und Standardwerte: includes/permissions.php
$catalog = permission_catalog();
$permLabels = permission_labels();
$agentDefaults = default_permissions('agent');
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$action = $_POST['action'] ?? '';
if ($action === 'create' || $action === 'update') {
$id = (int) ($_POST['id'] ?? 0);
$name = trim($_POST['name'] ?? '');
$isActive = ($_POST['status'] ?? 'active') === 'active' ? 1 : 0;
$notes = trim($_POST['notes'] ?? '');
if ($name === '') $errors[] = 'Bitte einen Namen angeben.';
$dup = $pdo->prepare('SELECT COUNT(*) FROM roles WHERE LOWER(name) = LOWER(?) AND id <> ?');
$dup->execute([$name, $id]);
if ($dup->fetchColumn() > 0) $errors[] = 'Eine Rolle mit diesem Namen existiert bereits.';
if (!$errors) {
$perms = [];
// Wer kein echter Administrator ist, kann keine Rechte vergeben, die er selbst nicht hat (sonst könnte sich ein Co-Admin zum Administrator hochstufen)
$oldPerms = [];
if ($action === 'update') {
$op = $pdo->prepare('SELECT permissions FROM roles WHERE id = ?'); $op->execute([$id]);
$oldPerms = json_decode((string) $op->fetchColumn(), true) ?: [];
}
foreach (array_keys($permLabels) as $p) {
$want = !empty($_POST['perm_' . $p]);
if ($staff['role'] !== 'admin' && $want && !has_permission($staff, $p)) $want = !empty($oldPerms[$p]);
$perms[$p] = $want;
}
$json = json_encode($perms);
if ($action === 'create') {
$pdo->prepare('INSERT INTO roles (name, permissions, is_active, notes, created_at, updated_at) VALUES (?,?,?,?,?,?)')
->execute([$name, $json, $isActive, $notes ?: null, db_now(), db_now()]);
audit_log($staff['id'], "Rolle erstellt: {$name}");
flash('success', 'Rolle erstellt.');
} else {
$pdo->prepare('UPDATE roles SET name=?, permissions=?, is_active=?, notes=?, updated_at=? WHERE id=?')
->execute([$name, $json, $isActive, $notes ?: null, db_now(), $id]);
audit_log($staff['id'], "Rolle aktualisiert: {$name}");
flash('success', 'Änderungen gespeichert.');
}
redirect('/staff/roles.php');
}
}
if ($action === 'seed_defaults') {
if ($staff['role'] !== 'admin') {
flash('error', 'Vordefinierte Rollen anlegen dürfen nur Administratoren.');
} else {
$made = permission_seed_default_roles($pdo);
if ($made) { audit_log($staff['id'], 'Vordefinierte Rollen angelegt: ' . implode(', ', $made)); flash('success', count($made) . ' Rolle(n) angelegt: ' . implode(', ', $made) . '.'); }
else flash('info', 'Alle vordefinierten Rollen sind schon vorhanden.');
}
redirect('/staff/roles.php');
}
if ($action === 'delete') {
$id = (int) $_POST['id'];
// Mitarbeiter und Abteilungszugriffe auf die Hauptrolle zurücksetzen
$pdo->prepare('UPDATE staff SET role_id = NULL WHERE role_id = ?')->execute([$id]);
$pdo->prepare('UPDATE staff_department SET role_id = NULL WHERE role_id = ?')->execute([$id]);
$pdo->prepare('DELETE FROM roles WHERE id = ?')->execute([$id]);
audit_log($staff['id'], "Rolle gelöscht: #{$id}");
flash('success', 'Rolle gelöscht.');
redirect('/staff/roles.php');
}
}
$isNew = isset($_GET['new']);
$editId = (int) ($_GET['edit'] ?? 0);
$showForm = $isNew || $editId || ($errors && in_array($_POST['action'] ?? '', ['create', 'update'], true));
$presetNames = [];
foreach (permission_presets() as [$__pn, , , $__seed]) if ($__seed) $presetNames[] = mb_strtolower($__pn);
$roles = $pdo->query(
'SELECT r.*, (SELECT COUNT(*) FROM staff s WHERE s.role_id = r.id) + (SELECT COUNT(*) FROM staff_department sd WHERE sd.role_id = r.id) AS staff_count
FROM roles r ORDER BY r.name'
)->fetchAll();
$editRole = null;
$editPerms = $agentDefaults; // neue Rolle: wie ein Standard-Mitarbeiter; fehlende Rechte älterer Rollen gelten mit dem Standardwert
if ($editId) {
$es = $pdo->prepare('SELECT * FROM roles WHERE id = ?'); $es->execute([$editId]);
$editRole = $es->fetch() ?: null;
if ($editRole) $editPerms = permission_effective(json_decode($editRole['permissions'] ?? '{}', true) ?? []);
}
if ($_POST && $errors) foreach (array_keys($permLabels) as $p) $editPerms[$p] = !empty($_POST['perm_' . $p]);
$pageTitle = 'Rollen'; $activePage = 'roles';
require __DIR__ . '/_header.php';
?>
<?php foreach ($errors as $err): ?><div class="flash error"><?= e($err) ?></div><?php endforeach; ?>
<?php if ($showForm): ?>
<?php
$f = fn(string $k, $default = '') => $_POST[$k] ?? $default;
$v = fn(string $k, string $col, $default = '') => $f($k, $editRole[$col] ?? $default);
$curActive = $_POST ? (($_POST['status'] ?? 'active') === 'active') : (bool) ($editRole['is_active'] ?? 1);
?>
<h1>
<?= $editRole ? 'Rolle aktualisieren' : 'Neue Rolle' ?>
<?php if ($editRole): ?><span class="muted" style="font-size:1rem;font-weight:400">— <?= e($editRole['name']) ?></span><?php endif; ?>
</h1>
<form method="post" class="card" style="padding:0">
<?= csrf_field() ?>
<input type="hidden" name="action" value="<?= $editRole ? 'update' : 'create' ?>">
<?php if ($editRole): ?><input type="hidden" name="id" value="<?= (int) $editRole['id'] ?>"><?php endif; ?>
<?php ui_tab_bar(['role' => 'Rolle', 'perms' => 'Berechtigungen']); ?>
<div class="tab-pane" data-pane="role" style="padding:16px 20px">
<h3 style="margin-top:0">Rolleninformationen</h3>
<label for="name">Name *</label>
<input type="text" id="name" name="name" value="<?= e($v('name', 'name')) ?>" placeholder="z. B. Nur-Antwort" required>
<?php ui_status_radios($curActive); ?>
<p class="muted" style="font-size:.78rem;margin-top:-6px">Eine deaktivierte Rolle wird ignoriert; betroffene Mitarbeiter haben dann die Standard-Mitarbeiterrechte.</p>
<h3>Administratornotizen <span class="muted" style="font-weight:400;font-size:.85rem">– Interne Notizen, sichtbar für alle Administratoren</span></h3>
<textarea name="notes" rows="6"><?= e($v('notes', 'notes')) ?></textarea>
</div>
<div class="tab-pane" data-pane="perms" style="padding:16px 20px;display:none">
<p class="muted" style="margin-top:0">Administratoren haben immer alle Rechte. Die Rechte einer Rolle gelten für Mitarbeiter mit dieser Rolle – je Abteilung kann ein Mitarbeiter eine andere Rolle haben (Mitarbeiter → Zugang). Ein Recht für einen Adminportal-Bereich macht die Person zum „Co-Admin“ für genau diesen Bereich. <strong>Immer nur echten Administratoren vorbehalten:</strong> Einstellungen, Backup, System-Update, Plugins, API-Schlüssel, Webhooks, osTicket-Import, Sicherheitsprüfung, Passkeys ein-/ausschalten und die Datenschutz-Löschfunktionen.</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:12px 0 4px">
<strong style="font-size:.86rem">Vorlage laden:</strong>
<?php foreach (permission_presets() as $pk => [$pname, $pdesc, $pperms]): ?>
<button type="button" class="btn secondary" style="padding:4px 12px;font-size:.82rem" data-preset="<?= e($pk) ?>" title="<?= e($pdesc) ?>"><?= e($pname) ?></button>
<?php endforeach; ?>
<span class="muted" style="margin-left:auto;font-size:.82rem"><span id="perm-count"></span> Rechte</span>
</div>
<?php foreach ($catalog as $gk => $group): ?>
<fieldset style="border:1px solid var(--border);border-radius:8px;margin:14px 0;padding:6px 14px 10px" data-group="<?= e($gk) ?>">
<legend style="font-weight:700;font-size:.9rem;padding:0 6px"><?= e($group['title']) ?></legend>
<div style="text-align:right;font-size:.78rem;margin-bottom:2px"><a href="#" data-all="<?= e($gk) ?>">Alle</a> · <a href="#" data-none="<?= e($gk) ?>">Keine</a></div>
<?php foreach ($group['items'] as $key => $item): ?>
<label style="display:flex;align-items:flex-start;gap:8px;padding:5px 0;font-weight:400;cursor:pointer">
<input type="checkbox" name="perm_<?= e($key) ?>" style="width:auto;margin-top:3px" <?= ($editPerms[$key] ?? false) ? 'checked' : '' ?>>
<span><span style="font-weight:600"><?= e($item['label']) ?></span><?php if ($item['area']): ?> <span class="muted" style="font-size:.7rem;border:1px solid var(--border);border-radius:10px;padding:0 6px">Adminportal</span><?php endif; ?><br><span class="muted" style="font-size:.78rem"><?= e($item['hint']) ?></span></span>
</label>
<?php endforeach; ?>
</fieldset>
<?php endforeach; ?>
</div>
<script>
(function () {
var presets = <?= json_encode(array_map(fn($x) => $x[2], permission_presets()), JSON_UNESCAPED_UNICODE) ?>;
var groups = <?= json_encode(array_map(fn($g) => array_keys($g['items']), $catalog), JSON_UNESCAPED_UNICODE) ?>;
var box = function (k) { return document.querySelector('input[name="perm_' + k + '"]'); };
function count() {
var n = 0, t = 0;
Object.keys(groups).forEach(function (g) { groups[g].forEach(function (k) { t++; if (box(k) && box(k).checked) n++; }); });
document.getElementById('perm-count').textContent = n + ' / ' + t;
}
document.querySelectorAll('[data-preset]').forEach(function (b) { b.addEventListener('click', function () {
var p = presets[b.getAttribute('data-preset')];
Object.keys(p).forEach(function (k) { if (box(k)) box(k).checked = !!p[k]; });
count();
}); });
document.querySelectorAll('[data-all],[data-none]').forEach(function (a) { a.addEventListener('click', function (e) {
e.preventDefault();
var g = a.getAttribute('data-all') || a.getAttribute('data-none'), on = a.hasAttribute('data-all');
groups[g].forEach(function (k) { if (box(k)) box(k).checked = on; });
count();
}); });
document.querySelectorAll('input[name^="perm_"]').forEach(function (c) { c.addEventListener('change', count); });
count();
})();
</script>
<div class="btn-row" style="padding:0 20px 18px">
<button type="submit" class="btn"><?= $editRole ? 'Änderungen speichern' : 'Rolle erstellen' ?></button>
<a href="/staff/roles.php" class="btn secondary">Abbrechen</a>
</div>
</form>
<?php ui_tab_assets('role'); ?>
<?php else: ?>
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
<h1 style="margin:0">Rollen</h1>
<a href="/staff/roles.php?new=1" class="btn">Neue Rolle hinzufügen</a>
</div>
<p class="muted">Berechtigungssets für Mitarbeiter. Ohne Rolle gelten die Standard-Mitarbeiterrechte.</p>
<p class="muted">Rollen mit dem Kennzeichen „vordefiniert“ (Co-Administrator, Support-Manager, IT / Technik, Teamleitung, Support 1st und 2nd Level, Redaktion, Auswertung, Teamverwaltung, Aushilfe, Nur lesen) wurden mit dem Update angelegt und lassen sich wie jede andere Rolle ändern oder löschen.</p>
<?php $missingRoles = permission_missing_default_roles($pdo); ?>
<?php if ($missingRoles && $staff['role'] === 'admin'): ?>
<div class="card" style="display:flex;gap:14px;align-items:center;flex-wrap:wrap">
<div style="flex:1;min-width:260px"><strong><?= count($missingRoles) ?> vordefinierte Rolle(n) fehlen</strong><div class="muted" style="font-size:.84rem"><?= e(implode(', ', $missingRoles)) ?></div></div>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="seed_defaults"><button class="btn secondary" type="submit">Fehlende Rollen anlegen</button></form>
</div>
<?php endif; ?>
<div class="card">
<?php $defaults = $agentDefaults; $roleRights = []; foreach ($roles as $r) $roleRights[(int) $r['id']] = permission_effective(json_decode($r['permissions'] ?? '{}', true) ?? []); ?>
<details>
<summary style="cursor:pointer;font-weight:600">Rechte-Übersicht: Wer darf was?</summary>
<p class="muted" style="font-size:.82rem;margin:8px 0">Standard = Mitarbeiter ohne eigene Rolle. Administratoren dürfen immer alles. Eine deaktivierte Rolle zählt wie Standard.</p>
<div style="overflow-x:auto">
<table style="font-size:.82rem;min-width:520px">
<thead><tr><th>Recht</th><th style="text-align:center">Standard</th><?php foreach ($roles as $r): ?><th style="text-align:center"><?= e($r['name']) ?><?= ($r['is_active'] ?? 1) ? '' : ' (aus)' ?></th><?php endforeach; ?></tr></thead>
<tbody>
<?php foreach ($catalog as $group): ?>
<tr><td colspan="<?= 2 + count($roles) ?>" style="background:var(--bg-soft);font-weight:700"><?= e($group['title']) ?></td></tr>
<?php foreach ($group['items'] as $key => $item): ?>
<tr><td><?= e($item['label']) ?></td>
<td style="text-align:center;color:<?= !empty($defaults[$key]) ? 'var(--success)' : 'var(--muted)' ?>"><?= !empty($defaults[$key]) ? '✓' : '–' ?></td>
<?php foreach ($roles as $r): $on = ($r['is_active'] ?? 1) ? !empty($roleRights[(int) $r['id']][$key]) : !empty($defaults[$key]); ?>
<td style="text-align:center;color:<?= $on ? 'var(--success)' : 'var(--muted)' ?>"><?= $on ? '✓' : '–' ?></td>
<?php endforeach; ?></tr>
<?php endforeach; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>
</details>
</div>
<div class="card">
<table>
<thead><tr><th>Rollenname</th><th>Status</th><th style="text-align:center">Rechte</th><th style="text-align:center">Zugewiesen</th><th>Erstellt</th><th>Zuletzt aktualisiert</th><th></th></tr></thead>
<tbody>
<?php foreach ($roles as $r): ?>
<tr>
<td><a href="/staff/roles.php?edit=<?= (int) $r['id'] ?>"><?= e($r['name']) ?></a><?php if (in_array(mb_strtolower((string) $r['name']), $presetNames, true)): ?> <span class="badge" style="background:#e6f4f3;color:#0f6f6a;font-weight:600;font-size:.68rem">vordefiniert</span><?php endif; ?></td>
<td><strong><?= ($r['is_active'] ?? 1) ? 'Aktiv' : 'Deaktiviert' ?></strong></td>
<td style="text-align:center"><?= count(array_filter($roleRights[(int) $r['id']])) ?> / <?= count($permLabels) ?></td>
<td style="text-align:center"><?= (int) $r['staff_count'] ?></td>
<td class="muted" style="font-size:.82rem"><?= e(ui_date($r['created_at'] ?? null)) ?></td>
<td class="muted" style="font-size:.82rem"><?= e(ui_date($r['updated_at'] ?? null)) ?></td>
<td style="white-space:nowrap">
<a href="/staff/roles.php?edit=<?= (int) $r['id'] ?>">Bearbeiten</a> ·
<form method="post" style="display:inline" onsubmit="return confirm('Rolle löschen? Zugeordnete Mitarbeiter erhalten wieder ihre Hauptrolle bzw. die Standardrechte.')">
<?= csrf_field() ?>
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= (int) $r['id'] ?>">
<button type="submit" style="background:none;border:none;color:#dc2626;cursor:pointer;padding:0;font-size:.9rem">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$roles): ?><tr><td colspan="7" class="muted">Noch keine benutzerdefinierten Rollen angelegt.</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php require __DIR__ . '/_footer.php'; ?>