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:
+90
-53
@@ -1,87 +1,124 @@
|
||||
<?php
|
||||
ob_start();
|
||||
$existingFiles = $articles->getFiles($addFilesArticle['uuid']);
|
||||
$articleUuid = $addFilesArticle['uuid'];
|
||||
$articleTitle = $addFilesArticle['title'];
|
||||
|
||||
// Extraire 1-3 mots significatifs du titre pour l'auto-recherche
|
||||
$_sfStop = ['ou','et','un','une','le','la','les','de','du','des','en','au','aux','ce','cet',
|
||||
'cette','ces','que','qui','par','sur','dans','son','sa','ses','mon','ton','nos',
|
||||
'vos','leur','leurs','voir','comment','quoi','dont','votre','notre','selon','car',
|
||||
'mais','donc','puis','plus','très','avec','pour','pas','est','sont','était',
|
||||
'être','avoir','faire','tout','tous','toute','toutes'];
|
||||
$_sfWords = preg_split('/[^a-zA-ZÀ-ÿ0-9]+/u', $articleTitle) ?: [];
|
||||
$_sfKw = [];
|
||||
foreach ($_sfWords as $_w) {
|
||||
if (mb_strlen($_w) >= 3 && !in_array(mb_strtolower($_w), $_sfStop, true)) {
|
||||
$_sfKw[] = $_w;
|
||||
if (count($_sfKw) >= 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$autoSearchQuery = !empty($_sfKw) ? implode(' ', $_sfKw) : $articleTitle;
|
||||
unset($_sfStop, $_sfWords, $_sfKw, $_w);
|
||||
?>
|
||||
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<a href="/edit/<?= rawurlencode($addFilesArticle['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a>
|
||||
<a href="/edit/<?= rawurlencode($articleUuid) ?>" class="btn btn-secondary btn-sm">← Retour</a>
|
||||
<h1 class="h4 mb-0">Ajouter des fichiers</h1>
|
||||
</div>
|
||||
|
||||
<p class="text-muted small mb-4">
|
||||
Article : <strong><?= htmlspecialchars($addFilesArticle['title']) ?></strong>
|
||||
Article : <strong><?= htmlspecialchars($articleTitle) ?></strong>
|
||||
</p>
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
<!-- Formulaire d'upload -->
|
||||
<div class="col-lg-5">
|
||||
<div class="card">
|
||||
<!-- Upload -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title h6 mb-3">Uploader</h5>
|
||||
<form method="POST"
|
||||
action="/files/<?= rawurlencode($addFilesArticle['uuid']) ?>/add"
|
||||
action="/files/<?= rawurlencode($articleUuid) ?>/add"
|
||||
enctype="multipart/form-data">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="files" class="form-label fw-semibold">Fichiers à uploader</label>
|
||||
<input type="file" class="form-control" id="files" name="files[]"
|
||||
multiple required>
|
||||
<input type="file" class="form-control" id="files" name="files[]" multiple required>
|
||||
<div class="form-text">
|
||||
Images → nommées <code>sha256-taille.ext</code><br>
|
||||
Images → <code>sha256-taille.ext</code><br>
|
||||
Vidéos, PDF, autres → nom sanitisé
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">Uploader</button>
|
||||
<a href="/edit/<?= rawurlencode($addFilesArticle['uuid']) ?>"
|
||||
class="btn btn-outline-secondary">Annuler</a>
|
||||
<button type="submit" class="btn btn-primary btn-sm">Uploader</button>
|
||||
<a href="/edit/<?= rawurlencode($articleUuid) ?>" class="btn btn-outline-secondary btn-sm">Annuler</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fichiers déjà présents -->
|
||||
<?php if ($existingFiles): ?>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title h6 mb-3">Fichiers existants</h5>
|
||||
<div class="list-group list-group-flush">
|
||||
<?php foreach ($existingFiles as $f):
|
||||
$fileUrl = '/file?uuid=' . rawurlencode($articleUuid) . '&name=' . rawurlencode($f['name']);
|
||||
?>
|
||||
<div class="list-group-item d-flex align-items-center gap-2 px-0 py-1">
|
||||
<?php if ($f['is_image']): ?>
|
||||
<img src="<?= htmlspecialchars($fileUrl) ?>" alt=""
|
||||
style="width:40px;height:40px;object-fit:cover;border-radius:4px;flex-shrink:0">
|
||||
<?php else: ?>
|
||||
<span style="width:40px;text-align:center;font-size:1.3rem;flex-shrink:0">
|
||||
<?= match(true) {
|
||||
str_starts_with($f['mime'], 'video/') => '🎬',
|
||||
str_starts_with($f['mime'], 'audio/') => '🎵',
|
||||
$f['mime'] === 'application/pdf' => '📑',
|
||||
default => '📄',
|
||||
} ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<div class="overflow-hidden" style="min-width:0">
|
||||
<code class="d-block small text-truncate"><?= htmlspecialchars($f['name']) ?></code>
|
||||
<small class="text-muted"><?= number_format($f['size'] / 1024, 1) ?> Ko</small>
|
||||
</div>
|
||||
<?php if (($addFilesArticle['cover'] ?? '') === $f['name']): ?>
|
||||
<span class="badge bg-primary ms-auto flex-shrink-0">cover</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Recherche dans les autres articles -->
|
||||
<div class="col-lg-8">
|
||||
<div id="sf-panel" data-uuid="<?= htmlspecialchars($articleUuid) ?>" class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title h6 mb-3">Fichiers d'autres articles</h5>
|
||||
<div class="d-flex gap-2 mb-3">
|
||||
<input type="text" id="sf-input" class="form-control form-control-sm"
|
||||
value="<?= htmlspecialchars($autoSearchQuery) ?>"
|
||||
placeholder="Titre, mot-clé…" autocomplete="off">
|
||||
<button type="button" id="sf-btn" class="btn btn-sm btn-outline-secondary text-nowrap">
|
||||
Chercher
|
||||
</button>
|
||||
</div>
|
||||
<div id="sf-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fichiers déjà présents -->
|
||||
<?php if ($existingFiles): ?>
|
||||
<div class="col-lg-7">
|
||||
<h5 class="mb-3">Fichiers existants</h5>
|
||||
<div class="list-group">
|
||||
<?php foreach ($existingFiles as $f): ?>
|
||||
<?php $fileUrl = '/file?uuid=' . rawurlencode($addFilesArticle['uuid']) . '&name=' . rawurlencode($f['name']); ?>
|
||||
<div class="list-group-item d-flex align-items-center gap-3 py-2">
|
||||
<?php if ($f['is_image']): ?>
|
||||
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener" class="flex-shrink-0">
|
||||
<img src="<?= htmlspecialchars($fileUrl) ?>" alt=""
|
||||
style="width:48px;height:48px;object-fit:cover;border-radius:4px">
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span style="width:48px;text-align:center;font-size:1.5rem;flex-shrink:0">
|
||||
<?= match(true) {
|
||||
str_starts_with($f['mime'], 'video/') => '🎬',
|
||||
str_starts_with($f['mime'], 'audio/') => '🎵',
|
||||
$f['mime'] === 'application/pdf' => '📑',
|
||||
default => '📄',
|
||||
} ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<code class="d-block small text-truncate"><?= htmlspecialchars($f['name']) ?></code>
|
||||
<small class="text-muted"><?= number_format($f['size'] / 1024, 1) ?> Ko</small>
|
||||
</div>
|
||||
<?php if ($addFilesArticle['cover'] ?? '' === $f['name']): ?>
|
||||
<span class="badge bg-primary flex-shrink-0">cover</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/add_files.js"></script>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
$title = 'Ajouter des fichiers — ' . htmlspecialchars($addFilesArticle['title']);
|
||||
$title = 'Ajouter des fichiers — ' . htmlspecialchars($articleTitle);
|
||||
include __DIR__ . '/layout.php';
|
||||
|
||||
+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
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
ob_start();
|
||||
$isAdminRole = ($editRole['name'] === 'admin');
|
||||
?>
|
||||
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<a href="/admin/roles" class="btn btn-secondary btn-sm">← Retour</a>
|
||||
<h1 class="h4 mb-0">Rôle : <?= htmlspecialchars($editRole['label']) ?></h1>
|
||||
<code class="text-muted"><?= htmlspecialchars($editRole['name']) ?></code>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/admin/role/<?= rawurlencode($editRole['name']) ?>">
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
<!-- Permissions -->
|
||||
<div class="col-lg-8">
|
||||
|
||||
<?php if ($isAdminRole): ?>
|
||||
<div class="alert alert-warning">
|
||||
Le rôle <code>admin</code> a toutes les permissions implicitement — les cases à cocher sont ignorées.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<?php foreach (CAPABILITY_GROUPS as $groupLabel => $groupCaps): ?>
|
||||
<div class="mb-4">
|
||||
<h6 class="fw-semibold text-muted text-uppercase small mb-3"><?= htmlspecialchars($groupLabel) ?></h6>
|
||||
<?php foreach ($groupCaps as $cap): ?>
|
||||
<?php if (!array_key_exists($cap, KNOWN_CAPABILITIES)) continue; ?>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="caps[]" value="<?= htmlspecialchars($cap) ?>"
|
||||
id="cap_<?= htmlspecialchars($cap) ?>"
|
||||
<?= in_array($cap, $editRoleCaps, true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="cap_<?= htmlspecialchars($cap) ?>">
|
||||
<?= htmlspecialchars(KNOWN_CAPABILITIES[$cap]) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Label + Actions -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="role_label" class="form-label fw-semibold small">Label affiché</label>
|
||||
<input type="text" id="role_label" name="label" class="form-control form-control-sm"
|
||||
value="<?= htmlspecialchars($editRole['label']) ?>" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Enregistrer</button>
|
||||
<a href="/admin/roles" class="btn btn-outline-secondary w-100 mt-2">Annuler</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
$title = 'Rôle — ' . $editRole['label'];
|
||||
include __DIR__ . '/layout.php';
|
||||
@@ -100,16 +100,17 @@
|
||||
|
||||
<ul class="navbar-nav">
|
||||
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/admin">Admin</a></li>
|
||||
<?php endif; ?>
|
||||
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/profile">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<?= htmlspecialchars(function_exists('currentUserName') ? currentUserName() : (currentUserEmail() ?? '')) ?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-muted" href="/logout.php" title="Déconnexion">Déconnexion</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" href="/profile">Mon identité</a></li>
|
||||
<li><a class="dropdown-item" href="/admin"><?= (function_exists('isAdmin') && isAdmin()) ? 'Administration' : 'Mes articles' ?></a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-muted" href="/logout.php">Déconnexion</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/login">Connexion</a></li>
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
ob_start();
|
||||
$CONTEXT = 3;
|
||||
|
||||
$base = rtrim(APP_URL, '/');
|
||||
$effectiveTitle = ($seoTitle !== '') ? $seoTitle : $title;
|
||||
$effectiveDesc = ($seoDescription !== '') ? $seoDescription : $autoSeoDesc;
|
||||
$effectiveSlug = $postSlug;
|
||||
|
||||
$coverFilename = ($newCover ?? '') !== '' ? $newCover : ($article['cover'] ?? '');
|
||||
$suggestedOgImage = $coverFilename !== ''
|
||||
? $base . '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($coverFilename)
|
||||
: '';
|
||||
?>
|
||||
|
||||
<div class="d-flex align-items-center gap-3 mb-4 flex-wrap">
|
||||
<a href="/edit/<?= htmlspecialchars($uuid) ?>" class="btn btn-outline-secondary">← Retour à l'édition</a>
|
||||
<h1 class="h4 mb-0">Confirmer les modifications</h1>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/edit/<?= htmlspecialchars($uuid) ?>">
|
||||
<input type="hidden" name="_confirm" value="1">
|
||||
<input type="hidden" name="title" value="<?= htmlspecialchars($title) ?>">
|
||||
<input type="hidden" name="content" value="<?= htmlspecialchars($content) ?>">
|
||||
<?php if ($published): ?>
|
||||
<input type="hidden" name="published" value="1">
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="published_at" value="<?= htmlspecialchars($published_at) ?>">
|
||||
<!-- seo_title et seo_description sont des champs visibles dans la colonne droite -->
|
||||
<!-- og_image est calculé depuis la couverture côté serveur -->
|
||||
<input type="hidden" name="category" value="<?= htmlspecialchars($category) ?>">
|
||||
<?php if (($newCover ?? '') !== ''): ?>
|
||||
<input type="hidden" name="cover_file" value="<?= htmlspecialchars($newCover) ?>">
|
||||
<?php endif; ?>
|
||||
<?php foreach ($fmetaNames as $fi => $fname): ?>
|
||||
<input type="hidden" name="fmeta_name[]" value="<?= htmlspecialchars($fname) ?>">
|
||||
<input type="hidden" name="fmeta_author[]" value="<?= htmlspecialchars($fmetaAuthors[$fi] ?? '') ?>">
|
||||
<input type="hidden" name="fmeta_source[]" value="<?= htmlspecialchars($fmetaSources[$fi] ?? '') ?>">
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-8">
|
||||
|
||||
<?php if (!empty($changes)): ?>
|
||||
<div class="alert alert-info py-2 mb-3">
|
||||
<?= htmlspecialchars(ucfirst(implode(' · ', $changes))) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-secondary py-2 mb-3">Aucune modification détectée.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ─── Diff ─────────────────────────────────────────────────────────────── -->
|
||||
<div class="mb-4">
|
||||
<h2 class="h6 fw-semibold mb-2">Diff du contenu</h2>
|
||||
<?php if ($diffLines === []): ?>
|
||||
<div class="text-muted small">Contenu identique.</div>
|
||||
<?php else:
|
||||
$total = count($diffLines);
|
||||
$show = [];
|
||||
for ($i = 0; $i < $total; $i++) {
|
||||
if ($diffLines[$i][0] !== '=') {
|
||||
for ($c = max(0, $i - $CONTEXT); $c <= min($total - 1, $i + $CONTEXT); $c++) {
|
||||
$show[$c] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$hasChanges = false;
|
||||
foreach ($diffLines as [$op]) { if ($op !== '=') { $hasChanges = true; break; } }
|
||||
?>
|
||||
<div class="d-flex gap-3 mb-1 small">
|
||||
<span class="diff-del px-2 py-1 rounded">− Supprimé</span>
|
||||
<span class="diff-ins px-2 py-1 rounded">+ Ajouté</span>
|
||||
</div>
|
||||
<div class="diff-view font-monospace small">
|
||||
<?php $inEllipsis = false; for ($i = 0; $i < $total; $i++): ?>
|
||||
<?php [$op, $line] = $diffLines[$i]; ?>
|
||||
<?php if (!isset($show[$i])): ?>
|
||||
<?php if (!$inEllipsis): $inEllipsis = true; ?>
|
||||
<div class="diff-ellipsis text-muted px-2">⋯</div>
|
||||
<?php endif; continue; ?>
|
||||
<?php else: $inEllipsis = false; endif; ?>
|
||||
<?php if ($op === '!'): ?>
|
||||
<div class="diff-warning text-warning px-2"><?= htmlspecialchars($line) ?></div>
|
||||
<?php elseif ($op === '-'): ?>
|
||||
<div class="diff-del px-2">− <?= htmlspecialchars($line) ?></div>
|
||||
<?php elseif ($op === '+'): ?>
|
||||
<div class="diff-ins px-2">+ <?= htmlspecialchars($line) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="diff-eq px-2 text-muted"> <?= htmlspecialchars($line) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- ─── Slug ─────────────────────────────────────────────────────────────── -->
|
||||
<?php
|
||||
$slugDefault = ($titleChanged && $autoSlug !== $postSlug) ? $autoSlug : $postSlug;
|
||||
$slugOriginal = $postSlug;
|
||||
?>
|
||||
<div class="mb-3">
|
||||
<label for="confirm-slug" class="form-label fw-semibold">
|
||||
Slug (URL permanente)
|
||||
<small class="text-muted fw-normal">— /post/<span id="slug-display"><?= htmlspecialchars($slugDefault) ?></span></small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm font-monospace" id="confirm-slug" name="slug"
|
||||
value="<?= htmlspecialchars($slugDefault) ?>"
|
||||
pattern="[a-z0-9][a-z0-9\-]*"
|
||||
oninput="document.getElementById('slug-display').textContent=this.value">
|
||||
<?php if ($titleChanged && $autoSlug !== $slugOriginal): ?>
|
||||
<div class="mt-2 d-flex align-items-center gap-2 flex-wrap">
|
||||
<small class="text-muted">Slug recalculé depuis le nouveau titre. Slug initial :</small>
|
||||
<code class="small"><?= htmlspecialchars($slugOriginal) ?></code>
|
||||
<button type="button" id="slug-btn-keep" class="btn btn-sm btn-outline-secondary py-0"
|
||||
data-slug-keep="<?= htmlspecialchars($slugOriginal) ?>">
|
||||
Conserver
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- ─── Commentaire de révision ──────────────────────────────────────────── -->
|
||||
<div class="mb-4">
|
||||
<label for="revision_comment" class="form-label fw-semibold">
|
||||
Commentaire de révision <small class="text-muted fw-normal">(optionnel)</small>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="revision_comment" name="revision_comment"
|
||||
value="<?= htmlspecialchars($autoRevisionComment) ?>"
|
||||
placeholder="ex. Correction typos, ajout section X…">
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-3 flex-wrap">
|
||||
<button type="submit" class="btn btn-success">Confirmer et enregistrer</button>
|
||||
<a href="/edit/<?= htmlspecialchars($uuid) ?>" class="btn btn-outline-secondary">← Retour</a>
|
||||
</div>
|
||||
|
||||
</div><!-- /col-lg-8 -->
|
||||
|
||||
<!-- ─── SEO ───────────────────────────────────────────────────────────────── -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card border-secondary mb-3">
|
||||
<div class="card-header bg-transparent py-2">
|
||||
<span class="fw-semibold small">SEO — titre, description, image</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="seo_title" class="form-label small">
|
||||
Titre SEO <small class="text-muted">(og:title, <title>)</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm" id="seo_title" name="seo_title"
|
||||
maxlength="70"
|
||||
value="<?= htmlspecialchars($seoTitle) ?>"
|
||||
placeholder="<?= htmlspecialchars($title) ?>">
|
||||
<div class="d-flex justify-content-between mt-1">
|
||||
<small class="text-muted">Idéal : 30–60 car.</small>
|
||||
<small id="seo_title_counter" class="text-muted">0 / 60</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="seo_description" class="form-label small">
|
||||
Description SEO <small class="text-muted">(meta description)</small>
|
||||
</label>
|
||||
<textarea class="form-control form-control-sm" id="seo_description" name="seo_description"
|
||||
rows="3" maxlength="200"
|
||||
placeholder="<?= htmlspecialchars(mb_strimwidth($autoSeoDesc, 0, 80, '…')) ?>"><?= htmlspecialchars($seoDescription) ?></textarea>
|
||||
<div class="d-flex justify-content-between mt-1">
|
||||
<small class="text-muted">Idéal : 120–155 car.</small>
|
||||
<small id="seo_desc_counter" class="text-muted">0 / 155</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-secondary">
|
||||
<div class="card-header bg-transparent py-2">
|
||||
<span class="fw-semibold small">Aperçu dans les moteurs de recherche</span>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<?php
|
||||
$pubTs = strtotime((string)($published_at ?? $article['published_at'] ?? ''));
|
||||
$modTs = time(); // sera mis à jour à la sauvegarde
|
||||
$pubFmt = $pubTs ? date('d/m/Y H:i', $pubTs) : '—';
|
||||
$modFmt = date('d/m/Y H:i', $modTs);
|
||||
$catVal = trim($category ?? '');
|
||||
?>
|
||||
<div class="seo-preview mb-3">
|
||||
<div class="seo-preview-url small text-truncate mb-1" id="preview-url">
|
||||
<?= htmlspecialchars(rtrim($base, '/') . '/post/' . $effectiveSlug) ?>
|
||||
</div>
|
||||
<div class="seo-preview-title mb-1" id="preview-title">
|
||||
<?= htmlspecialchars($effectiveTitle) ?>
|
||||
</div>
|
||||
<div class="seo-preview-desc small" id="preview-desc">
|
||||
<?= htmlspecialchars($effectiveDesc) ?>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-sm table-borderless mb-0 small">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap" style="width:1%">Auteur</th>
|
||||
<td>Cédrix</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Publication</th>
|
||||
<td><?= htmlspecialchars($pubFmt) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Modification</th>
|
||||
<td><?= htmlspecialchars($modFmt) ?> <small class="text-muted">(après enreg.)</small></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Langue</th>
|
||||
<td>fr-FR</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Catégorie</th>
|
||||
<td><?= $catVal !== '' ? htmlspecialchars($catVal) : '<span class="text-muted">—</span>' ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">og:image</th>
|
||||
<td class="text-truncate" style="max-width:0">
|
||||
<?= $suggestedOgImage !== '' ? '<span title="' . htmlspecialchars($suggestedOgImage) . '">' . htmlspecialchars($coverFilename) . '</span>' : '<span class="text-muted">— (pas de couverture)</span>' ?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /row -->
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.diff-view { border: 1px solid var(--bs-border-color, #dee2e6); border-radius: 6px; overflow-x: auto; }
|
||||
.diff-view > div { padding: 1px 8px; white-space: pre; line-height: 1.5; }
|
||||
.diff-del { background: #ffeef0; color: #b91c1c; }
|
||||
.diff-ins { background: #e6ffec; color: #15803d; }
|
||||
.diff-eq { }
|
||||
.diff-ellipsis { background: #f8f9fa; padding: 2px 8px; user-select: none; }
|
||||
|
||||
.seo-preview { border: 1px solid #dee2e6; border-radius: 6px; padding: 10px 12px; background: #fff; }
|
||||
.seo-preview-url { color: #006621; font-size: .78rem; }
|
||||
.seo-preview-title { color: #1a0dab; font-size: 1.05rem; font-weight: 500; line-height: 1.3; word-break: break-word; }
|
||||
.seo-preview-desc { color: #545454; line-height: 1.5; }
|
||||
</style>
|
||||
<div id="pc-data" hidden
|
||||
data-default-title="<?= htmlspecialchars($effectiveTitle) ?>"
|
||||
data-default-desc="<?= htmlspecialchars($effectiveDesc) ?>"
|
||||
data-base-url="<?= htmlspecialchars(rtrim($base, '/') . '/post/') ?>"></div>
|
||||
<script src="/assets/js/post_confirm.js"></script>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
$title = 'Confirmer — ' . $title;
|
||||
include __DIR__ . '/layout.php';
|
||||
+5
-57
@@ -43,6 +43,9 @@ $dateValue = isset($published_at)
|
||||
value="<?= htmlspecialchars($title ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<?php if ($action === 'edit'): ?>
|
||||
<input type="hidden" id="slug" name="slug" value="<?= htmlspecialchars($postSlug ?? '') ?>">
|
||||
<?php else: ?>
|
||||
<div class="mb-3">
|
||||
<label for="slug" class="form-label">
|
||||
Slug <small class="text-muted">(URL : /post/<span id="slug-preview"><?= htmlspecialchars($postSlug ?? '') ?></span>)</small>
|
||||
@@ -52,6 +55,7 @@ $dateValue = isset($published_at)
|
||||
pattern="[a-z0-9][a-z0-9\-]*"
|
||||
placeholder="généré automatiquement depuis le titre">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($action === 'create'): ?>
|
||||
<div class="mb-3">
|
||||
@@ -130,63 +134,6 @@ $dateValue = isset($published_at)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="revision_comment" class="form-label">Commentaire de révision <small class="text-muted">(optionnel)</small></label>
|
||||
<input type="text" class="form-control" id="revision_comment" name="revision_comment"
|
||||
placeholder="ex. Correction typos, ajout section X…">
|
||||
</div>
|
||||
|
||||
<div class="card mb-3 border-secondary">
|
||||
<div class="card-header bg-transparent py-2">
|
||||
<button class="btn btn-sm btn-link text-secondary text-decoration-none p-0 fw-semibold"
|
||||
type="button" data-bs-toggle="collapse" data-bs-target="#seoPanel" aria-expanded="false">
|
||||
▸ SEO — titre, description, image
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse" id="seoPanel">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="seo_title" class="form-label">
|
||||
Titre SEO
|
||||
<small class="text-muted">(balise <title> et og:title)</small>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="seo_title" name="seo_title"
|
||||
maxlength="70"
|
||||
value="<?= htmlspecialchars($seoTitle ?? '') ?>"
|
||||
placeholder="Généré automatiquement depuis le titre">
|
||||
<div class="d-flex justify-content-between mt-1">
|
||||
<small class="text-muted">Idéal : 30–60 caractères</small>
|
||||
<small id="seo_title_counter" class="text-muted">0 / 60</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="seo_description" class="form-label">
|
||||
Description SEO
|
||||
<small class="text-muted">(meta description et og:description)</small>
|
||||
</label>
|
||||
<textarea class="form-control" id="seo_description" name="seo_description"
|
||||
rows="3" maxlength="200"
|
||||
placeholder="Générée automatiquement depuis le début du contenu"><?= htmlspecialchars($seoDescription ?? '') ?></textarea>
|
||||
<div class="d-flex justify-content-between mt-1">
|
||||
<small class="text-muted">Idéal : 120–155 caractères</small>
|
||||
<small id="seo_desc_counter" class="text-muted">0 / 155</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label for="og_image" class="form-label">
|
||||
Image Open Graph
|
||||
<small class="text-muted">(URL absolue, optionnel)</small>
|
||||
</label>
|
||||
<input type="url" class="form-control font-monospace" id="og_image" name="og_image"
|
||||
value="<?= htmlspecialchars($ogImage ?? '') ?>"
|
||||
placeholder="https://varlog.a5l.fr/…">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-3 flex-wrap mb-4">
|
||||
<button type="submit" class="btn btn-success">Enregistrer</button>
|
||||
<a href="/" class="btn btn-secondary">Annuler</a>
|
||||
@@ -365,6 +312,7 @@ if ($hasSources):
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php if (!empty($article['revisions'])): ?>
|
||||
<hr class="my-4">
|
||||
<div>
|
||||
|
||||
@@ -5,7 +5,7 @@ $Parsedown = new Parsedown();
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
<div class="post-grid">
|
||||
<?php foreach ($posts as $i => $post): ?>
|
||||
<?php
|
||||
$html = $Parsedown->text($post['content']);
|
||||
@@ -19,8 +19,7 @@ ob_start();
|
||||
$isPrivate = $postCat !== '' && in_array($postCat, $privateCats ?? [], true);
|
||||
$isLocked = $isAvantPremiere;
|
||||
?>
|
||||
<div class="col">
|
||||
<article class="card h-100">
|
||||
<article class="card">
|
||||
<?php if ($isDraft): ?>
|
||||
<div class="draft-ribbon">Brouillon</div>
|
||||
<?php elseif ($isAvantPremiere): ?>
|
||||
@@ -67,8 +66,7 @@ ob_start();
|
||||
<?php if (!$isLocked): ?>
|
||||
<a href="<?= htmlspecialchars($postUrl) ?>" class="stretched-link"></a>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
@@ -124,4 +122,5 @@ if (empty($cursor) && $filterCat === '') {
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
$mainClass = 'container-fluid';
|
||||
include __DIR__ . '/layout.php';
|
||||
|
||||
Reference in New Issue
Block a user