feat : RSS content, feed catégorie, cookie commentaires, flux erreurs, email preview (v1.6.17)

- RSS : content:encoded (HTML complet) + fix description via plain (#42)
- RSS : flux filtré par ?category=nom (#43)
- Commentaires : cookie nom/email pour pré-remplir le formulaire (#51)
- flux/ : bandeau admin des feeds en erreur (#45)
- admin/emails : bouton « Voir ↗ » vers /admin/email-preview/{id} en nouvel onglet (#37)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 10:00:37 +02:00
parent dc4701d667
commit 51055b7321
8 changed files with 131 additions and 32 deletions
+2 -1
View File
@@ -41,8 +41,9 @@ RewriteRule ^diff/([0-9a-f-]{36})/(\d+)/?$ /index.php?action=diff&uuid=$1&rev=$2
RewriteRule ^files/([0-9a-f-]{36})/add/?$ /index.php?action=add_files&uuid=$1 [L,QSA]
RewriteRule ^import/([0-9a-f-]{36})/?$ /index.php?action=import_image&uuid=$1 [L,QSA]
# Admin (regen-thumbs et role/<email> avant la règle générique admin/<tab>)
# Admin (regen-thumbs, email-preview et role/<email> avant la règle générique admin/<tab>)
RewriteRule ^admin/regen-thumbs/?$ /index.php?action=regen_thumbs [L,QSA]
RewriteRule ^admin/email-preview/(\d+)/?$ /index.php?action=admin_email_preview&id=$1 [L,QSA]
RewriteRule ^admin/role/([a-z0-9_-]+)/?$ /index.php?action=admin_role_edit&role_name=$1 [L,QSA]
RewriteRule ^admin/([a-z0-9-]+)/?$ /index.php?action=admin&tab=$1 [L,QSA]
RewriteRule ^admin/?$ /index.php?action=admin [L,QSA]
+32 -15
View File
@@ -16,17 +16,24 @@ $articles = new ArticleManager(DATA_PATH);
$privateCats = $articles->getPrivateCategories();
$Parsedown = new Parsedown();
$now = time();
$base = rtrim(APP_URL, '/');
$now = time();
$base = rtrim(APP_URL, '/');
$filterCat = trim($_GET['category'] ?? '');
$all = array_values(array_filter(
$articles->getAll(publishedOnly: true),
static function (array $a) use ($now, $privateCats): bool {
static function (array $a) use ($now, $privateCats, $filterCat): bool {
if (strtotime((string)($a['published_at'] ?? '')) > $now) {
return false;
}
$cat = trim($a['category'] ?? '');
return $cat === '' || !in_array($cat, $privateCats, true);
if ($cat !== '' && in_array($cat, $privateCats, true)) {
return false;
}
if ($filterCat !== '' && $cat !== $filterCat) {
return false;
}
return true;
}
));
@@ -42,13 +49,16 @@ if ($after !== '') {
}
}
$items = array_slice($all, $offset, FEED_PAGE_SIZE);
$items = array_slice($all, $offset, FEED_PAGE_SIZE);
$nextCursor = (count($all) > $offset + FEED_PAGE_SIZE)
? ($all[$offset + FEED_PAGE_SIZE - 1]['uuid'] ?? null)
: null;
$feedUrl = $base . '/feed';
$feedNextUrl = $nextCursor !== null ? $base . '/feed/' . $nextCursor : null;
$feedUrl = $base . '/feed' . ($filterCat !== '' ? '?category=' . rawurlencode($filterCat) : '');
$feedNextUrl = $nextCursor !== null ? $base . '/feed/' . $nextCursor . ($filterCat !== '' ? '?category=' . rawurlencode($filterCat) : '') : null;
$channelTitle = siteTitle() . ($filterCat !== '' ? ' — ' . $filterCat : '');
$channelDesc = $filterCat !== '' ? 'Articles de la catégorie « ' . $filterCat . ' »' : siteClaim();
// ─── lastBuildDate ───────────────────────────────────────────────────────────
$lastBuild = '';
@@ -69,11 +79,12 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
?>
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:fh="http://purl.org/syndication/history/1.0">
<channel>
<title><?= htmlspecialchars(siteTitle()) ?></title>
<title><?= htmlspecialchars($channelTitle) ?></title>
<link><?= htmlspecialchars($base) ?></link>
<description><?= htmlspecialchars(siteClaim()) ?></description>
<description><?= htmlspecialchars($channelDesc) ?></description>
<language><?= htmlspecialchars(siteLang()) ?></language>
<lastBuildDate><?= htmlspecialchars($lastBuild) ?></lastBuildDate>
@@ -91,17 +102,23 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
<?php endif; ?>
<?php foreach ($items as $article):
$pubDate = date(DATE_RSS, (int)strtotime((string)($article['published_at'] ?? $article['created_at'] ?? '')));
$link = $base . '/post/' . rawurlencode($article['slug'] ?? '');
$title = htmlspecialchars($article['title'] ?? '', ENT_XML1);
$plain = preg_replace('/\s+/', ' ', strip_tags($Parsedown->text($article['content'] ?? '')));
$desc = htmlspecialchars(mb_strimwidth(trim((string)$plain), 0, 300, '…'), ENT_XML1);
$guid = htmlspecialchars($base . '/post/' . rawurlencode($article['slug'] ?? ''), ENT_XML1);
$pubDate = date(DATE_RSS, (int)strtotime((string)($article['published_at'] ?? $article['created_at'] ?? '')));
$link = $base . '/post/' . rawurlencode($article['slug'] ?? '');
$title = htmlspecialchars($article['title'] ?? '', ENT_XML1);
$plain = preg_replace('/\s+/', ' ', trim($article['plain'] ?? ''));
$desc = htmlspecialchars(mb_strimwidth($plain, 0, 300, '…'), ENT_XML1);
$guid = htmlspecialchars($base . '/post/' . rawurlencode($article['slug'] ?? ''), ENT_XML1);
$mdPath = DATA_PATH . '/' . ($article['uuid'] ?? '') . '/index.md';
$rawMd = file_exists($mdPath) ? (string)file_get_contents($mdPath) : '';
$fullHtml = $rawMd !== '' ? $Parsedown->text($rawMd) : '';
?>
<item>
<title><?= $title ?></title>
<link><?= htmlspecialchars($link) ?></link>
<description><?= $desc ?></description>
<?php if ($fullHtml !== ''): ?>
<content:encoded><![CDATA[<?= $fullHtml ?>]]></content:encoded>
<?php endif; ?>
<pubDate><?= htmlspecialchars($pubDate) ?></pubDate>
<guid isPermaLink="true"><?= $guid ?></guid>
</item>
+34 -4
View File
@@ -1402,9 +1402,10 @@ switch ($action) {
case 'flux':
require_once BASE_PATH . '/src/FeedFetcher.php';
$fetcher = new FeedFetcher(DATA_PATH . '/_cache/feeds');
$fluxItems = [];
$pdo = dbPdo();
$fetcher = new FeedFetcher(DATA_PATH . '/_cache/feeds');
$fluxItems = [];
$fluxErrors = [];
$pdo = dbPdo();
if ($pdo) {
try {
$st = $pdo->query(
@@ -1417,6 +1418,11 @@ switch ($action) {
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $_row) {
$data = $fetcher->get($_row['feed_url']);
if (!$data) {
$fluxErrors[] = [
'feed_url' => $_row['feed_url'],
'label' => $_row['label'],
'user_email' => $_row['user_email'],
];
continue;
}
$feedTitle = $_row['label'] !== '' ? $_row['label'] : $data['feed_title'];
@@ -2541,7 +2547,7 @@ switch ($action) {
'queued' => (int)($row['queued'] ?? 0),
];
$adminData['emails'] = $pdo->query(
"SELECT id, created_at, to_email, subject, status, error_message, content_text, sent_at
"SELECT id, created_at, to_email, subject, status, error_message, content_text, content_html, sent_at
FROM journal_smtp $whereEml
ORDER BY created_at DESC
LIMIT $emlLimit OFFSET $emlOffset"
@@ -2757,6 +2763,30 @@ switch ($action) {
header('Location: /admin/smtp');
exit;
case 'admin_email_preview':
requireAuth();
if (!isAdmin()) {
http_response_code(403);
exit;
}
$previewId = (int)($_GET['id'] ?? 0);
$pdo = dbPdo();
$emailRow = null;
if ($pdo && $previewId > 0) {
$st = $pdo->prepare('SELECT subject, content_html, content_text FROM journal_smtp WHERE id = :id');
$st->execute([':id' => $previewId]);
$emailRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
}
if (!$emailRow) {
http_response_code(404);
echo 'Email introuvable.';
exit;
}
header('Content-Type: text/html; charset=UTF-8');
$previewHtml = !empty($emailRow['content_html']) ? $emailRow['content_html'] : nl2br(htmlspecialchars((string)$emailRow['content_text']));
echo '<!doctype html><html lang="fr"><head><meta charset="utf-8"><title>' . htmlspecialchars((string)$emailRow['subject']) . '</title></head><body>' . $previewHtml . '</body></html>';
exit;
case 'admin_toggle_featured':
requireAuth();
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
+1 -1
View File
@@ -1 +1 @@
1.6.16
1.6.17