ca6cfa4ebf
- buildAutoSeoDesc() : entités HTML décodées + titre supprimé en tête (#91) - post_confirm.js : guard null sur #confirm-slug absent (#91) - feed.php : <media:thumbnail> avec image de couverture RSS (#90) - admin livres : slug auto depuis le titre + filtre articles (#89) - BookManager::sanitizeSlug() passé public Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
136 lines
5.3 KiB
PHP
136 lines
5.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
define('BASE_PATH', realpath(__DIR__ . '/../'));
|
|
|
|
require_once BASE_PATH . '/src/auth.php';
|
|
require_once BASE_PATH . '/src/SiteSettings.php';
|
|
require_once BASE_PATH . '/config/config.php';
|
|
require_once BASE_PATH . '/src/ArticleManager.php';
|
|
require_once BASE_PATH . '/src/Parsedown.php';
|
|
|
|
const FEED_PAGE_SIZE = 20;
|
|
|
|
$articles = new ArticleManager(DATA_PATH);
|
|
$privateCats = $articles->getPrivateCategories();
|
|
$Parsedown = new Parsedown();
|
|
|
|
$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, $filterCat): bool {
|
|
if (strtotime((string)($a['published_at'] ?? '')) > $now) {
|
|
return false;
|
|
}
|
|
$cat = trim($a['category'] ?? '');
|
|
if ($cat !== '' && in_array($cat, $privateCats, true)) {
|
|
return false;
|
|
}
|
|
if ($filterCat !== '' && $cat !== $filterCat) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
));
|
|
|
|
// ─── Pagination curseur ──────────────────────────────────────────────────────
|
|
$after = trim($_GET['after'] ?? '');
|
|
$offset = 0;
|
|
if ($after !== '') {
|
|
foreach ($all as $i => $a) {
|
|
if ($a['uuid'] === $after) {
|
|
$offset = $i + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$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' . ($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 = '';
|
|
foreach ($all as $a) {
|
|
$ts = (int)strtotime((string)($a['updated_at'] ?? $a['published_at'] ?? ''));
|
|
if ($ts > (int)strtotime($lastBuild ?: '1970-01-01')) {
|
|
$lastBuild = date(DATE_RSS, $ts);
|
|
}
|
|
}
|
|
if ($lastBuild === '') {
|
|
$lastBuild = date(DATE_RSS);
|
|
}
|
|
|
|
header('Content-Type: application/rss+xml; charset=UTF-8');
|
|
header('X-Content-Type-Options: nosniff');
|
|
|
|
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:media="http://search.yahoo.com/mrss/"
|
|
xmlns:fh="http://purl.org/syndication/history/1.0">
|
|
<channel>
|
|
<title><?= htmlspecialchars($channelTitle) ?></title>
|
|
<link><?= htmlspecialchars($base) ?></link>
|
|
<description><?= htmlspecialchars($channelDesc) ?></description>
|
|
<language><?= htmlspecialchars(siteLang()) ?></language>
|
|
<lastBuildDate><?= htmlspecialchars($lastBuild) ?></lastBuildDate>
|
|
|
|
<atom:link href="<?= htmlspecialchars($feedUrl) ?>" rel="self" type="application/rss+xml"/>
|
|
|
|
<?php if ($offset > 0): ?>
|
|
<atom:link href="<?= htmlspecialchars($feedUrl) ?>" rel="first" type="application/rss+xml"/>
|
|
<?php endif; ?>
|
|
<?php if ($feedNextUrl !== null): ?>
|
|
<atom:link href="<?= htmlspecialchars($feedNextUrl) ?>" rel="next" type="application/rss+xml"/>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($feedNextUrl !== null || $offset > 0): ?>
|
|
<fh:archive/>
|
|
<?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+/', ' ', 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) : '';
|
|
$imgUrl = trim($article['og_image'] ?? '');
|
|
if ($imgUrl === '' && ($article['cover'] ?? '') !== '') {
|
|
$imgUrl = $base . '/file?uuid=' . rawurlencode($article['uuid']) . '&name=' . rawurlencode($article['cover']);
|
|
}
|
|
?>
|
|
<item>
|
|
<title><?= $title ?></title>
|
|
<link><?= htmlspecialchars($link) ?></link>
|
|
<description><?= $desc ?></description>
|
|
<?php if ($fullHtml !== ''): ?>
|
|
<content:encoded><![CDATA[<?= $fullHtml ?>]]></content:encoded>
|
|
<?php endif; ?>
|
|
<?php if ($imgUrl !== ''): ?>
|
|
<media:thumbnail url="<?= htmlspecialchars($imgUrl, ENT_XML1) ?>"/>
|
|
<?php endif; ?>
|
|
<pubDate><?= htmlspecialchars($pubDate) ?></pubDate>
|
|
<guid isPermaLink="true"><?= $guid ?></guid>
|
|
</item>
|
|
<?php endforeach; ?>
|
|
</channel>
|
|
</rss>
|