v1.6.25 — intégration IA éditeur, onglet admin IA, corrections CSP #98
@@ -5,6 +5,18 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnag
|
||||
|
||||
---
|
||||
|
||||
## [1.6.15] - 2026-05-16
|
||||
|
||||
### Ajouté
|
||||
- `admin/articles` : champ de recherche par titre (`filter_search`), cumulable avec les autres filtres (#85)
|
||||
- `admin/articles` : colonne « ★ À la une » avec toggle rapide par ligne et filtre `filter_featured` (#84)
|
||||
- `post/` : date de modification affichée sous la date de publication si l'article a été modifié après sa mise en ligne (#81)
|
||||
|
||||
### Modifié
|
||||
- `sources/` : bouton « ← Modifier » remplacé par « ← Retour à l'article » pointant vers `post/<slug>` (#83)
|
||||
|
||||
---
|
||||
|
||||
## [1.6.14] - 2026-05-15
|
||||
|
||||
### Modifié
|
||||
|
||||
@@ -2355,9 +2355,13 @@ switch ($action) {
|
||||
$filterAuthor = trim($_GET['filter_author'] ?? '');
|
||||
$filterCategory = trim($_GET['filter_category'] ?? '');
|
||||
$filterStatus = trim($_GET['filter_status'] ?? '');
|
||||
$filterSearch = trim($_GET['filter_search'] ?? '');
|
||||
$filterFeatured = trim($_GET['filter_featured'] ?? '');
|
||||
$adminData['filter_author'] = $filterAuthor;
|
||||
$adminData['filter_category'] = $filterCategory;
|
||||
$adminData['filter_status'] = $filterStatus;
|
||||
$adminData['filter_search'] = $filterSearch;
|
||||
$adminData['filter_featured'] = $filterFeatured;
|
||||
|
||||
$nowTs = time();
|
||||
if ($filterAuthor !== '') {
|
||||
@@ -2373,6 +2377,12 @@ switch ($action) {
|
||||
} elseif ($filterStatus === 'preview') {
|
||||
$allArticles = array_values(array_filter($allArticles, fn ($a) => $a['published'] && strtotime((string)($a['published_at'] ?? '')) > $nowTs));
|
||||
}
|
||||
if ($filterSearch !== '') {
|
||||
$allArticles = array_values(array_filter($allArticles, fn ($a) => mb_stripos($a['title'] ?? '', $filterSearch) !== false));
|
||||
}
|
||||
if ($filterFeatured === 'yes') {
|
||||
$allArticles = array_values(array_filter($allArticles, fn ($a) => !empty($a['featured'])));
|
||||
}
|
||||
|
||||
$sortBy = in_array($_GET['sort'] ?? '', ['title', 'published', 'updated']) ? $_GET['sort'] : 'updated';
|
||||
$sortDir = ($_GET['dir'] ?? '') === 'asc' ? 'asc' : 'desc';
|
||||
@@ -2741,6 +2751,21 @@ switch ($action) {
|
||||
header('Location: /admin/smtp');
|
||||
exit;
|
||||
|
||||
case 'admin_toggle_featured':
|
||||
requireAuth();
|
||||
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$uid = trim((string)($_POST['uuid'] ?? ''));
|
||||
$art = $uid !== '' ? $articles->getByUuid($uid) : null;
|
||||
if ($art) {
|
||||
$articles->setFeatured($uid, !((bool)($art['featured'] ?? false)));
|
||||
}
|
||||
$back = $_POST['_back'] ?? '/admin/articles';
|
||||
header('Location: ' . $back);
|
||||
exit;
|
||||
|
||||
case 'admin_bulk_delete':
|
||||
requireAuth();
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.6.14
|
||||
1.6.15
|
||||
|
||||
+30
-1
@@ -214,6 +214,8 @@ function adminStatusBadge(array $a, int $now): string
|
||||
'filter_author' => $adminData['filter_author'] ?? '',
|
||||
'filter_category' => $adminData['filter_category'] ?? '',
|
||||
'filter_status' => $adminData['filter_status'] ?? '',
|
||||
'filter_search' => $adminData['filter_search'] ?? '',
|
||||
'filter_featured' => $adminData['filter_featured'] ?? '',
|
||||
], fn ($v) => $v !== '');
|
||||
$p['sort'] = $col;
|
||||
$p['dir'] = $dir;
|
||||
@@ -263,9 +265,19 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<option value="preview" <?= ($adminData['filter_status'] ?? '') === 'preview' ? 'selected' : '' ?>>Avant-première</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select name="filter_featured" class="form-select form-select-sm">
|
||||
<option value="">Tous</option>
|
||||
<option value="yes" <?= ($adminData['filter_featured'] ?? '') === 'yes' ? 'selected' : '' ?>>★ À la une</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<input type="text" name="filter_search" class="form-control form-control-sm"
|
||||
placeholder="Rechercher…" value="<?= htmlspecialchars($adminData['filter_search'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-auto d-flex gap-2">
|
||||
<button type="submit" class="btn btn-secondary btn-sm">Filtrer</button>
|
||||
<?php $hasFilter = ($adminData['filter_author'] ?? '') !== '' || ($adminData['filter_category'] ?? '') !== '' || ($adminData['filter_status'] ?? '') !== ''; ?>
|
||||
<?php $hasFilter = ($adminData['filter_author'] ?? '') !== '' || ($adminData['filter_category'] ?? '') !== '' || ($adminData['filter_status'] ?? '') !== '' || ($adminData['filter_search'] ?? '') !== '' || ($adminData['filter_featured'] ?? '') !== ''; ?>
|
||||
<?php if ($hasFilter): ?>
|
||||
<a href="/admin/articles" class="btn btn-link btn-sm p-0">Réinitialiser</a>
|
||||
<?php endif; ?>
|
||||
@@ -304,6 +316,7 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<?php if (isAdmin()): ?><th>Auteur</th><?php endif; ?>
|
||||
<th>Catégorie</th>
|
||||
<th>Statut</th>
|
||||
<th title="À la une">★</th>
|
||||
<th>
|
||||
<a href="<?= htmlspecialchars($_mkSortUrl('published')) ?>"
|
||||
class="text-decoration-none text-reset">
|
||||
@@ -330,6 +343,22 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<?php endif; ?>
|
||||
<td class="text-muted small"><?= htmlspecialchars($a['category'] ?? '–') ?></td>
|
||||
<td><?= adminStatusBadge($a, $now) ?></td>
|
||||
<td class="text-center">
|
||||
<?php if (isAdmin()): ?>
|
||||
<?php $_isFeatured = !empty($a['featured']); ?>
|
||||
<?php $_backUrl = '/admin/articles?' . http_build_query(array_filter(['filter_author' => $adminData['filter_author'] ?? '', 'filter_category' => $adminData['filter_category'] ?? '', 'filter_status' => $adminData['filter_status'] ?? '', 'filter_search' => $adminData['filter_search'] ?? '', 'filter_featured' => $adminData['filter_featured'] ?? '', 'sort' => $_sortBy, 'dir' => $_sortDir], fn ($v) => $v !== '')); ?>
|
||||
<form method="post" action="/?action=admin_toggle_featured" class="d-inline m-0">
|
||||
<input type="hidden" name="uuid" value="<?= htmlspecialchars($a['uuid']) ?>">
|
||||
<input type="hidden" name="_back" value="<?= htmlspecialchars($_backUrl) ?>">
|
||||
<button type="submit" class="btn btn-link p-0 border-0 lh-1 fs-6"
|
||||
title="<?= $_isFeatured ? 'Retirer de la une' : 'Mettre à la une' ?>">
|
||||
<?= $_isFeatured ? '★' : '<span class="text-muted">☆</span>' ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<?= !empty($a['featured']) ? '★' : '' ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-muted small text-nowrap">
|
||||
<?= htmlspecialchars(date('d/m/Y', strtotime((string)($a['published_at'] ?? $a['created_at'] ?? '')))) ?>
|
||||
</td>
|
||||
|
||||
@@ -96,6 +96,14 @@ $authorName = ($authorEmail !== '' && function_exists('authorDisplayName')
|
||||
$authorProfileUrl = ($authorEmail !== '' && function_exists('authorProfileUrl')) ? authorProfileUrl($authorEmail) : '';
|
||||
$authorSlugVal = ($authorEmail !== '' && function_exists('authorSlug')) ? authorSlug($authorEmail) : '';
|
||||
$pubDate = htmlspecialchars(date('d/m/Y', strtotime((string)($article['published_at'] ?? $article['created_at'] ?? ''))));
|
||||
$modDate = '';
|
||||
$_updatedTs = strtotime((string)($article['updated_at'] ?? ''));
|
||||
$_publishedTs = strtotime((string)($article['published_at'] ?? $article['created_at'] ?? ''));
|
||||
if ($_updatedTs > 0 && $_publishedTs > 0 && $_updatedTs > $_publishedTs) {
|
||||
$_frMonths = ['janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'];
|
||||
$modDate = 'Modifié le ' . (int)date('j', $_updatedTs) . ' ' . $_frMonths[(int)date('n', $_updatedTs) - 1]
|
||||
. ' ' . date('Y', $_updatedTs) . ' à ' . date('H', $_updatedTs) . 'h' . date('i', $_updatedTs);
|
||||
}
|
||||
$hasCover = $coverFile !== '';
|
||||
$heroExtraClass = $hasCover ? '' : ' article-cover--gradient';
|
||||
$heroStyle = $hasCover ? '' : ' style="background:' . htmlspecialchars($gradient) . '"';
|
||||
@@ -137,6 +145,9 @@ $hasSources = (!empty($externalLinks) || !empty($files))
|
||||
<span class="mx-1 opacity-50">·</span>
|
||||
<?php endif; ?>
|
||||
<?= $pubDate ?>
|
||||
<?php if ($modDate !== ''): ?>
|
||||
<br><small class="opacity-75"><?= htmlspecialchars($modDate) ?></small>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="article-hero-right">
|
||||
|
||||
@@ -38,7 +38,7 @@ function renderMetaCell(string $key, mixed $val, array $row = []): string
|
||||
?>
|
||||
|
||||
<div class="d-flex align-items-center gap-3 mb-1">
|
||||
<a href="/edit/<?= rawurlencode($article['uuid']) ?>" class="btn btn-secondary btn-sm">← Modifier</a>
|
||||
<a href="/post/<?= rawurlencode($article['slug'] ?? $article['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour à l'article</a>
|
||||
<h1 class="h4 mb-0">Sources & médias</h1>
|
||||
</div>
|
||||
<p class="text-muted small mb-4"><?= htmlspecialchars($article['title']) ?></p>
|
||||
|
||||
Reference in New Issue
Block a user