feat: roles, permissions, grille full-width, SSO display name
- Admin/roles : tableau des roles avec edition par role (/admin/role/<nom>) - Permissions par role : cases a cocher groupees (Articles, Acces & lecture) - Nouvelles capacites : propose/validate/publish articles (own/all), view_previews - Nom technique auto-genere depuis le label (JS + fallback serveur) - Blocage suppression du dernier administrateur - user_capabilities table ajoutee en DB - Navbar : dropdown unique (nom + Mon identite + Administration + Deconnexion) - SSO callback : preserve le nom personnalise, ne l ecrase plus a la connexion - Grille articles : CSS Grid auto-fill full-width, hauteur uniforme par ligne - CSP : add_files.js et post_confirm.js externalises
This commit is contained in:
+83
-147
@@ -144,6 +144,12 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<!-- ─────────────────────────── UTILISATEURS ─────────────────────────── -->
|
||||
<?php elseif ($tab === 'users' && isAdmin()): ?>
|
||||
|
||||
<?php if (($_GET['error'] ?? '') === 'last_admin'): ?>
|
||||
<div class="alert alert-danger py-2 mb-3">
|
||||
Impossible de retirer le rôle Administrateur : il doit rester au moins un administrateur.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Ajouter / attribuer un rôle -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Attribuer un rôle</div>
|
||||
@@ -198,13 +204,13 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php foreach ($u['roles'] as $roleName): ?>
|
||||
<span class="badge bg-primary me-1"><?= htmlspecialchars($roleName) ?></span>
|
||||
<?php foreach ($u['roles'] as $role): ?>
|
||||
<span class="badge bg-primary me-1"><?= htmlspecialchars($role['label']) ?></span>
|
||||
<form method="post" action="/?action=admin_revoke_role"
|
||||
class="d-inline"
|
||||
data-confirm="Retirer le rôle <?= htmlspecialchars($roleName) ?> à <?= htmlspecialchars($u['email']) ?> ?">
|
||||
data-confirm="Retirer le rôle «<?= htmlspecialchars($role['label']) ?>» à <?= htmlspecialchars($u['email']) ?> ?">
|
||||
<input type="hidden" name="email" value="<?= htmlspecialchars($u['email']) ?>">
|
||||
<input type="hidden" name="role" value="<?= htmlspecialchars($roleName) ?>">
|
||||
<input type="hidden" name="role" value="<?= htmlspecialchars($role['name']) ?>">
|
||||
<button type="submit" class="btn btn-link btn-sm p-0 text-danger" title="Retirer">×</button>
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
@@ -215,8 +221,8 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<td class="text-end">
|
||||
<!-- Ajout rapide d'un rôle existant -->
|
||||
<?php
|
||||
$currentRoles = $u['roles'];
|
||||
$missing = array_filter($adminData['roles'], fn ($r) => !in_array($r['name'], $currentRoles, true));
|
||||
$currentRoleNames = array_column($u['roles'], 'name');
|
||||
$missing = array_filter($adminData['roles'], fn ($r) => !in_array($r['name'], $currentRoleNames, true));
|
||||
?>
|
||||
<?php if ($missing): ?>
|
||||
<form method="post" action="/?action=admin_grant_role" class="d-inline-flex gap-1">
|
||||
@@ -241,151 +247,81 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<!-- ─────────────────────────── RÔLES ─────────────────────────── -->
|
||||
<?php elseif ($tab === 'roles' && isAdmin()): ?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-4">
|
||||
|
||||
<!-- Liste des rôles existants -->
|
||||
<div class="col-lg-8">
|
||||
<h5 class="mb-3">Rôles existants</h5>
|
||||
<?php if (empty($adminData['roles'])): ?>
|
||||
<p class="text-muted">Aucun rôle défini.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-sm table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:160px">Nom technique</th>
|
||||
<th>Label affiché</th>
|
||||
<th class="text-center" style="width:90px">Utilisateurs</th>
|
||||
<th style="width:100px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($adminData['roles'] as $r): ?>
|
||||
<tr>
|
||||
<td><code class="text-body"><?= htmlspecialchars($r['name']) ?></code></td>
|
||||
<td>
|
||||
<form method="post" action="/?action=admin_update_role"
|
||||
class="d-flex gap-2 align-items-center">
|
||||
<input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
|
||||
<input type="text" name="label"
|
||||
value="<?= htmlspecialchars($r['label']) ?>"
|
||||
class="form-control form-control-sm" required>
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm text-nowrap">
|
||||
Sauver
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-secondary"><?= (int)$r['user_count'] ?></span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<form method="post" action="/?action=admin_delete_role"
|
||||
data-confirm="Supprimer le rôle «<?= htmlspecialchars($r['name']) ?>» ?<?= (int)$r['user_count'] > 0 ? ' ' . (int)$r['user_count'] . ' utilisateur(s) perdront ce rôle.' : '' ?>">
|
||||
<input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||||
Supprimer
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Créer un rôle -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header">Nouveau rôle</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/?action=admin_create_role">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold">Nom technique</label>
|
||||
<input type="text" name="name" class="form-control form-control-sm"
|
||||
placeholder="ex : moderator"
|
||||
pattern="[a-z0-9_-]+"
|
||||
title="Lettres minuscules, chiffres, tirets et underscores uniquement"
|
||||
required>
|
||||
<div class="form-text">Utilisé dans le code — ne change pas.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold">Label affiché</label>
|
||||
<input type="text" name="label" class="form-control form-control-sm"
|
||||
placeholder="ex : Modérateur" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100">Créer</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permissions par rôle -->
|
||||
<?php if (!empty($adminData['roles'])): ?>
|
||||
<div class="col-12 mt-2">
|
||||
<h5 class="mb-3">Permissions par rôle</h5>
|
||||
<p class="text-muted small mb-3">Le rôle <code>admin</code> a toutes les permissions implicitement.</p>
|
||||
<div class="row g-3">
|
||||
<?php foreach ($adminData['roles'] as $r):
|
||||
if ($r['name'] === 'admin') {
|
||||
continue;
|
||||
} ?>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header py-2 d-flex align-items-center justify-content-between">
|
||||
<span class="fw-semibold"><?= htmlspecialchars($r['label']) ?></span>
|
||||
<code class="text-muted small"><?= htmlspecialchars($r['name']) ?></code>
|
||||
</div>
|
||||
<div class="card-body py-3">
|
||||
<form method="post" action="/?action=admin_update_role_caps">
|
||||
<input type="hidden" name="role_id" value="<?= (int)$r['id'] ?>">
|
||||
<?php foreach (CAPABILITY_GROUPS as $group): ?>
|
||||
<?php if (isset($group['single'])): ?>
|
||||
<?php $cap = $group['single']; ?>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="caps[]" value="<?= htmlspecialchars($cap) ?>"
|
||||
id="cap_<?= (int)$r['id'] ?>_<?= htmlspecialchars($cap) ?>"
|
||||
<?= in_array($cap, $r['capabilities'], true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label small fw-semibold"
|
||||
for="cap_<?= (int)$r['id'] ?>_<?= htmlspecialchars($cap) ?>">
|
||||
<?= htmlspecialchars($group['label']) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="mb-3">
|
||||
<div class="small fw-semibold mb-1"><?= htmlspecialchars($group['label']) ?></div>
|
||||
<div class="d-flex gap-3 ps-1">
|
||||
<?php foreach (['own' => 'Propres articles', 'all' => 'Tous'] as $scope => $scopeLabel):
|
||||
$cap = $group[$scope]; ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="caps[]" value="<?= htmlspecialchars($cap) ?>"
|
||||
id="cap_<?= (int)$r['id'] ?>_<?= htmlspecialchars($cap) ?>"
|
||||
<?= in_array($cap, $r['capabilities'], true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label small"
|
||||
for="cap_<?= (int)$r['id'] ?>_<?= htmlspecialchars($cap) ?>">
|
||||
<?= $scopeLabel ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm mt-1 w-100">
|
||||
Enregistrer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tableau des rôles existants -->
|
||||
<div class="col-lg-8">
|
||||
<?php if (empty($adminData['roles'])): ?>
|
||||
<p class="text-muted">Aucun rôle défini.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rôle</th>
|
||||
<th class="text-center" style="width:100px">Utilisateurs</th>
|
||||
<th style="width:110px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($adminData['roles'] as $r): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= htmlspecialchars($r['label']) ?>
|
||||
<?php if ($r['name'] !== 'admin'): ?>
|
||||
<div class="text-muted small">
|
||||
<?php
|
||||
$capLabels = array_map(
|
||||
fn ($c) => KNOWN_CAPABILITIES[$c] ?? $c,
|
||||
$r['capabilities']
|
||||
);
|
||||
echo htmlspecialchars(implode(', ', $capLabels) ?: '–');
|
||||
?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="text-muted small">Toutes les permissions</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-secondary"><?= (int)$r['user_count'] ?></span>
|
||||
</td>
|
||||
<td class="text-end d-flex gap-1 justify-content-end">
|
||||
<a href="/admin/role/<?= rawurlencode($r['name']) ?>"
|
||||
class="btn btn-outline-secondary btn-sm">Éditer</a>
|
||||
<?php if ((int)$r['user_count'] === 0 && $r['name'] !== 'admin'): ?>
|
||||
<form method="post" action="/?action=admin_delete_role"
|
||||
data-confirm="Supprimer le rôle «<?= htmlspecialchars($r['name']) ?>» ?">
|
||||
<input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm">×</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Créer un rôle -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header">Nouveau rôle</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/?action=admin_create_role">
|
||||
<div class="mb-3">
|
||||
<label for="role-label" class="form-label small fw-semibold">Nom du rôle</label>
|
||||
<input type="text" id="role-label" name="label" class="form-control form-control-sm"
|
||||
placeholder="ex : Modérateur" required autocomplete="off">
|
||||
</div>
|
||||
<input type="hidden" id="role-name" name="name">
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100">Créer</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
|
||||
Reference in New Issue
Block a user