257 lines
15 KiB
PHP
257 lines
15 KiB
PHP
<?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'; ?>
|