v1.6.25 — intégration IA éditeur, onglet admin IA, corrections CSP #98
@@ -5,6 +5,14 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnag
|
||||
|
||||
---
|
||||
|
||||
## [1.6.22] - 2026-05-16
|
||||
|
||||
### Ajouté
|
||||
- Widget de notation ★ (1-5 étoiles) sur les articles, accessible aux utilisateurs connectés ; affiche la moyenne et le nombre de votes pour tous (#13)
|
||||
- Admin `flux` : onglet listant tous les flux RSS agrégés avec action de suppression admin (#87)
|
||||
|
||||
---
|
||||
|
||||
## [1.6.21] - 2026-05-16
|
||||
|
||||
### Ajouté
|
||||
|
||||
+34
-1
@@ -45,7 +45,7 @@ $action = $_GET['action'] ?? 'list';
|
||||
$uuid = $_GET['uuid'] ?? '';
|
||||
$slug = $_GET['slug'] ?? '';
|
||||
|
||||
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed', 'add_link', 'delete_link', 'reorder_links', 'react', 'comment', 'verify_comment', 'comment_moderate', 'comment_delete', 'comment_resend', 'create_tag_type', 'delete_tag_type', 'edit_tags', 'book_save', 'book_delete', 'admin_save_as_groups', 'admin_save_folio_config', 'run_engine_update', 'run_content_migrations'];
|
||||
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed', 'add_link', 'delete_link', 'reorder_links', 'react', 'comment', 'verify_comment', 'comment_moderate', 'comment_delete', 'comment_resend', 'create_tag_type', 'delete_tag_type', 'edit_tags', 'book_save', 'book_delete', 'admin_save_as_groups', 'admin_save_folio_config', 'run_engine_update', 'run_content_migrations', 'admin_delete_feed', 'rate'];
|
||||
$metaRobots = in_array($action, $_noindexActions, true) ? 'noindex, nofollow' : null;
|
||||
unset($_noindexActions);
|
||||
|
||||
@@ -2735,6 +2735,21 @@ switch ($action) {
|
||||
$adminData['tagTypes'] = $articles->getTagTypes();
|
||||
}
|
||||
|
||||
if ($tab === 'flux') {
|
||||
if (!isAdmin()) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$pdo = dbPdo();
|
||||
$adminData['flux_feeds'] = [];
|
||||
if ($pdo) {
|
||||
try {
|
||||
$st = $pdo->query('SELECT id, user_email, feed_url, label, created_at FROM rss_feeds ORDER BY created_at DESC');
|
||||
$adminData['flux_feeds'] = $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (\Throwable) {}
|
||||
}
|
||||
}
|
||||
|
||||
if ($tab === 'books') {
|
||||
if (!isAdmin()) {
|
||||
http_response_code(403);
|
||||
@@ -3366,6 +3381,24 @@ switch ($action) {
|
||||
header('Location: /profile#feeds');
|
||||
exit;
|
||||
|
||||
case 'admin_delete_feed':
|
||||
requireAuth();
|
||||
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$feedId = (int)($_POST['id'] ?? 0);
|
||||
if ($feedId > 0) {
|
||||
$pdo = dbPdo();
|
||||
if ($pdo) {
|
||||
try {
|
||||
$pdo->prepare('DELETE FROM rss_feeds WHERE id = :id')->execute([':id' => $feedId]);
|
||||
} catch (\Throwable) {}
|
||||
}
|
||||
}
|
||||
header('Location: /admin/flux?deleted=1');
|
||||
exit;
|
||||
|
||||
case 'search_files':
|
||||
requireAuth();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.6.21
|
||||
1.6.22
|
||||
|
||||
@@ -69,6 +69,10 @@ function adminStatusBadge(array $a, int $now): string
|
||||
<a class="nav-link <?= $tab === 'books' ? 'active' : '' ?>"
|
||||
href="/admin/books">Livres</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $tab === 'flux' ? 'active' : '' ?>"
|
||||
href="/admin/flux">Flux</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $tab === 'stats' ? 'active' : '' ?>"
|
||||
href="/admin/stats">Statistiques</a>
|
||||
@@ -1267,6 +1271,57 @@ foreach (COLOR_PALETTE_16 as $_i => $_rgb):
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ─────────────────────────── FLUX RSS ─────────────────────────── -->
|
||||
<?php if ($tab === 'flux' && isAdmin()): ?>
|
||||
|
||||
<?php if (($_GET['deleted'] ?? '') === '1'): ?>
|
||||
<div class="alert alert-success py-2 small">Flux supprimé.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h5>Flux RSS agrégés</h5>
|
||||
<p class="text-muted small">Tous les flux enregistrés par les utilisateurs. Seul un administrateur peut les supprimer.</p>
|
||||
|
||||
<?php if (empty($adminData['flux_feeds'] ?? [])): ?>
|
||||
<p class="text-muted">Aucun flux enregistré.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Utilisateur</th>
|
||||
<th>Libellé</th>
|
||||
<th>URL</th>
|
||||
<th>Ajouté le</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($adminData['flux_feeds'] as $_feed): ?>
|
||||
<tr>
|
||||
<td class="small"><?= htmlspecialchars($_feed['user_email'] ?? '') ?></td>
|
||||
<td class="small"><?= htmlspecialchars($_feed['label'] ?? '') ?></td>
|
||||
<td class="small text-truncate" style="max-width:260px">
|
||||
<a href="<?= htmlspecialchars($_feed['feed_url'] ?? '') ?>" target="_blank" rel="noopener" class="text-muted">
|
||||
<?= htmlspecialchars($_feed['feed_url'] ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<td class="small text-nowrap"><?= htmlspecialchars(substr($_feed['created_at'] ?? '', 0, 10)) ?></td>
|
||||
<td>
|
||||
<form method="POST" action="/?action=admin_delete_feed"
|
||||
data-confirm="Supprimer ce flux ?">
|
||||
<input type="hidden" name="id" value="<?= (int)$_feed['id'] ?>">
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm py-0">Supprimer</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ─────────────────────────── LIVRES ─────────────────────────── -->
|
||||
<?php if ($tab === 'books' && isAdmin()): ?>
|
||||
|
||||
|
||||
@@ -261,6 +261,37 @@ $hasSources = (!empty($externalLinks) || !empty($files))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (($ratingStats['count'] ?? 0) > 0 || isLoggedIn()): ?>
|
||||
<div class="d-flex align-items-center flex-wrap gap-3 my-3 py-2 border-top small">
|
||||
<span class="text-muted">Note :</span>
|
||||
<?php if (($ratingStats['avg'] ?? null) !== null): ?>
|
||||
<span>
|
||||
<strong><?= number_format((float)$ratingStats['avg'], 1) ?></strong>/5
|
||||
<span class="text-muted">(<?= (int)$ratingStats['count'] ?> vote<?= (int)$ratingStats['count'] > 1 ? 's' : '' ?>)</span>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">Pas encore noté</span>
|
||||
<?php endif; ?>
|
||||
<?php if (isLoggedIn()): ?>
|
||||
<form method="POST" action="/?action=rate" class="d-flex align-items-center gap-1 mb-0">
|
||||
<input type="hidden" name="uuid" value="<?= htmlspecialchars($article['uuid']) ?>">
|
||||
<?php for ($_s = 1; $_s <= 5; $_s++): ?>
|
||||
<button type="submit" name="rating" value="<?= $_s ?>"
|
||||
class="btn btn-link p-0 lh-1 text-decoration-none<?= (($userRating ?? 0) >= $_s) ? ' text-warning' : ' text-muted' ?>"
|
||||
title="<?= $_s ?> étoile<?= $_s > 1 ? 's' : '' ?>">
|
||||
<?= (($userRating ?? 0) >= $_s) ? '★' : '☆' ?>
|
||||
</button>
|
||||
<?php endfor; ?>
|
||||
<?php if (($userRating ?? null) !== null): ?>
|
||||
<span class="text-muted ms-1">(votre note)</span>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="text-muted fst-italic">Connectez-vous pour noter</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($article['published'] ?? false): ?>
|
||||
<?php
|
||||
$_shareUrl = rtrim(defined('APP_URL') ? APP_URL : '', '/') . '/post/' . rawurlencode($article['slug'] ?? '');
|
||||
|
||||
Reference in New Issue
Block a user