1d2e3d9a24
- 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
331 lines
15 KiB
PHP
331 lines
15 KiB
PHP
<?php
|
||
ob_start();
|
||
|
||
$now = time();
|
||
|
||
function adminStatusBadge(array $a, int $now): string
|
||
{
|
||
if (!$a['published']) {
|
||
return '<span class="badge bg-secondary">Brouillon</span>';
|
||
}
|
||
if (strtotime((string)($a['published_at'] ?? '')) > $now) {
|
||
return '<span class="badge bg-warning text-dark">Avant-première</span>';
|
||
}
|
||
return '<span class="badge bg-success">Publié</span>';
|
||
}
|
||
?>
|
||
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h1 class="h3 mb-0">Administration</h1>
|
||
<a href="/new" class="btn btn-primary btn-sm">+ Nouvel article</a>
|
||
</div>
|
||
|
||
<!-- Onglets -->
|
||
<ul class="nav nav-tabs mb-4">
|
||
<?php if (isAdmin()): ?>
|
||
<li class="nav-item">
|
||
<a class="nav-link <?= $tab === 'dashboard' ? 'active' : '' ?>"
|
||
href="/admin/dashboard">Tableau de bord</a>
|
||
</li>
|
||
<?php endif; ?>
|
||
<li class="nav-item">
|
||
<a class="nav-link <?= $tab === 'articles' ? 'active' : '' ?>"
|
||
href="/admin/articles"><?= isAdmin() ? 'Articles' : 'Mes articles' ?></a>
|
||
</li>
|
||
<?php if (isAdmin()): ?>
|
||
<li class="nav-item">
|
||
<a class="nav-link <?= $tab === 'users' ? 'active' : '' ?>"
|
||
href="/admin/users">Utilisateurs</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link <?= $tab === 'roles' ? 'active' : '' ?>"
|
||
href="/admin/roles">Rôles</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="/categories">Catégories</a>
|
||
</li>
|
||
<?php endif; ?>
|
||
</ul>
|
||
|
||
<!-- ─────────────────────────── DASHBOARD ─────────────────────────── -->
|
||
<?php if ($tab === 'dashboard' && isAdmin()): ?>
|
||
|
||
<div class="row g-3 mb-4">
|
||
<?php
|
||
$stats = [
|
||
['label' => 'Publiés', 'value' => $adminData['published'], 'color' => 'success'],
|
||
['label' => 'Avant-premières', 'value' => $adminData['previews'], 'color' => 'warning'],
|
||
['label' => 'Brouillons', 'value' => $adminData['drafts'], 'color' => 'secondary'],
|
||
['label' => 'Total', 'value' => $adminData['total'], 'color' => 'primary'],
|
||
];
|
||
foreach ($stats as $s): ?>
|
||
<div class="col-6 col-md-3">
|
||
<div class="card text-center">
|
||
<div class="card-body">
|
||
<div class="display-6 fw-bold text-<?= $s['color'] ?>"><?= $s['value'] ?></div>
|
||
<div class="text-muted small"><?= $s['label'] ?></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<h5>Activité récente</h5>
|
||
<table class="table table-sm table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Titre</th>
|
||
<th>Auteur</th>
|
||
<th>Statut</th>
|
||
<th>Modifié le</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($adminData['recent'] as $a): ?>
|
||
<tr>
|
||
<td>
|
||
<a href="/post/<?= htmlspecialchars($a['slug'] ?? '') ?>">
|
||
<?= htmlspecialchars($a['title']) ?>
|
||
</a>
|
||
</td>
|
||
<td class="text-muted small"><?= htmlspecialchars($a['author'] ?? '–') ?></td>
|
||
<td><?= adminStatusBadge($a, $now) ?></td>
|
||
<td class="text-muted small">
|
||
<?= htmlspecialchars(date('d/m/Y H:i', strtotime((string)($a['updated_at'] ?? $a['created_at'] ?? '')))) ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- ─────────────────────────── ARTICLES ─────────────────────────── -->
|
||
<?php elseif ($tab === 'articles'): ?>
|
||
|
||
<?php if (empty($adminData['articles'])): ?>
|
||
<p class="text-muted">Aucun article.</p>
|
||
<?php else: ?>
|
||
<table class="table table-sm table-hover align-middle">
|
||
<thead>
|
||
<tr>
|
||
<th>Titre</th>
|
||
<?php if (isAdmin()): ?><th>Auteur</th><?php endif; ?>
|
||
<th>Catégorie</th>
|
||
<th>Statut</th>
|
||
<th>Date</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($adminData['articles'] as $a): ?>
|
||
<tr>
|
||
<td>
|
||
<a href="/post/<?= htmlspecialchars($a['slug'] ?? '') ?>">
|
||
<?= htmlspecialchars($a['title']) ?>
|
||
</a>
|
||
</td>
|
||
<?php if (isAdmin()): ?>
|
||
<td class="text-muted small"><?= htmlspecialchars($a['author'] ?? '–') ?></td>
|
||
<?php endif; ?>
|
||
<td class="text-muted small"><?= htmlspecialchars($a['category'] ?? '–') ?></td>
|
||
<td><?= adminStatusBadge($a, $now) ?></td>
|
||
<td class="text-muted small text-nowrap">
|
||
<?= htmlspecialchars(date('d/m/Y', strtotime((string)($a['published_at'] ?? $a['created_at'] ?? '')))) ?>
|
||
</td>
|
||
<td class="text-end text-nowrap">
|
||
<a href="/edit/<?= htmlspecialchars($a['uuid']) ?>"
|
||
class="btn btn-outline-secondary btn-sm">Modifier</a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
|
||
<!-- ─────────────────────────── 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>
|
||
<div class="card-body">
|
||
<form method="post" action="/?action=admin_grant_role" class="row g-2 align-items-end">
|
||
<div class="col-md-5">
|
||
<label class="form-label small">Email</label>
|
||
<input type="email" name="email" class="form-control form-control-sm"
|
||
placeholder="utilisateur@exemple.fr" required>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small">Rôle</label>
|
||
<select name="role" class="form-select form-select-sm" required>
|
||
<?php foreach ($adminData['roles'] as $r): ?>
|
||
<option value="<?= htmlspecialchars($r['name']) ?>">
|
||
<?= htmlspecialchars($r['label']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<button type="submit" class="btn btn-primary btn-sm w-100">Attribuer</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Liste des utilisateurs -->
|
||
<?php if (empty($adminData['users'])): ?>
|
||
<p class="text-muted">Aucun utilisateur.</p>
|
||
<?php else: ?>
|
||
<table class="table table-sm table-hover align-middle">
|
||
<thead>
|
||
<tr>
|
||
<th>Email</th>
|
||
<th>Statut</th>
|
||
<th>Rôles</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($adminData['users'] as $u): ?>
|
||
<tr>
|
||
<td class="small"><?= htmlspecialchars($u['email']) ?></td>
|
||
<td>
|
||
<?php if ($u['is_active'] === null): ?>
|
||
<span class="badge bg-light text-muted">Pré-inscrit</span>
|
||
<?php elseif ($u['is_active']): ?>
|
||
<span class="badge bg-success">Actif</span>
|
||
<?php else: ?>
|
||
<span class="badge bg-danger">Inactif</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?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($role['label']) ?>» à <?= htmlspecialchars($u['email']) ?> ?">
|
||
<input type="hidden" name="email" value="<?= htmlspecialchars($u['email']) ?>">
|
||
<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; ?>
|
||
<?php if (empty($u['roles'])): ?>
|
||
<span class="text-muted small">–</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td class="text-end">
|
||
<!-- Ajout rapide d'un rôle existant -->
|
||
<?php
|
||
$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">
|
||
<input type="hidden" name="email" value="<?= htmlspecialchars($u['email']) ?>">
|
||
<select name="role" class="form-select form-select-sm" style="width:auto">
|
||
<?php foreach ($missing as $r): ?>
|
||
<option value="<?= htmlspecialchars($r['name']) ?>">
|
||
<?= htmlspecialchars($r['label']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<button type="submit" class="btn btn-outline-primary btn-sm">+</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
|
||
<!-- ─────────────────────────── RÔLES ─────────────────────────── -->
|
||
<?php elseif ($tab === 'roles' && isAdmin()): ?>
|
||
|
||
<div class="row g-4">
|
||
|
||
<!-- 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
|
||
$content = ob_get_clean();
|
||
$title = 'Administration — varlog';
|
||
include __DIR__ . '/layout.php';
|