Files
folio/templates/post_list.php
T
cedricAbonnel 5cea473d17 feat : "Meilleures audiences" + admin/stats pages via flux RSS XML (v1.6.6)
- post_list.php : section AJAX qui lit /trending?period=1h en XML (DOMParser) — plus de rendu PHP
- admin_stats.php : colonne "Pages les plus visitées" chargée en AJAX depuis /trending?period=14d XML
- index.php/stats : suppression de topGrouped pour /post/ ; seuls /book/ et ASN restent côté serveur

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 20:08:24 +02:00

369 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once BASE_PATH . '/src/Parsedown.php';
$Parsedown = new Parsedown();
ob_start();
// ─── Helpers locaux ────────────────────────────────────────────────────────
function _cardCoverStyle(array $post, array $allCats): string
{
$coverFile = $post['cover'] ?? '';
if ($coverFile !== '') {
return 'background-image: url(\'/file?uuid=' . rawurlencode($post['uuid']) . '&name=' . rawurlencode($coverFile) . '\')';
}
$cat = trim($post['category'] ?? '');
return 'background: ' . coverGradient($cat !== '' ? $cat : $post['uuid'], $allCats);
}
function _cardExcerpt(array $post, \Parsedown $pd, int $len = 120): string
{
return mb_strimwidth(strip_tags($pd->text($post['content'])), 0, $len, '…');
}
function _renderCard(array $post, array $privateCats, array $allCats, \Parsedown $pd): void
{
$postUrl = '/post/' . rawurlencode($post['slug']);
$isDraft = !$post['published'];
$isAvantPremiere = $post['published'] && strtotime((string)($post['published_at'] ?? '')) > time();
$postCat = trim($post['category'] ?? '');
$isPrivate = $postCat !== '' && in_array($postCat, $privateCats, true);
$isLocked = $isAvantPremiere && !hasCapability('view_previews');
$coverStyle = _cardCoverStyle($post, $allCats);
$preview = _cardExcerpt($post, $pd);
?>
<article class="card">
<?php if ($isDraft): ?>
<div class="draft-ribbon">Brouillon</div>
<?php elseif ($isAvantPremiere): ?>
<div class="premiere-ribbon">Avant-première</div>
<?php elseif ($isPrivate): ?>
<div class="private-ribbon">Privé</div>
<?php endif; ?>
<div class="card-cover" style="<?= $coverStyle ?>">
<?php if ($postCat !== ''): ?>
<span class="cover-category"><?= htmlspecialchars($postCat) ?></span>
<?php endif; ?>
</div>
<div class="card-body d-flex flex-column">
<h2 class="card-title">
<?php if ($isLocked): ?>
<?= htmlspecialchars($post['title']) ?>
<?php else: ?>
<a href="<?= htmlspecialchars($postUrl) ?>"><?= htmlspecialchars($post['title']) ?></a>
<?php endif; ?>
</h2>
<p class="card-text flex-grow-1"><?= htmlspecialchars($preview) ?></p>
<div class="post-entry-meta mt-auto">
<?php if ($isAvantPremiere): ?>
<span class="text-muted">Disponible le <?= htmlspecialchars(date('d/m/Y \à H\hi', strtotime((string)($post['published_at'] ?? '')))) ?></span>
<?php else: ?>
<span><?= htmlspecialchars(date('d/m/Y', strtotime((string)($post['published_at'] ?? $post['created_at'] ?? '')))) ?></span>
<?php endif; ?>
<?php if (!$isLocked): ?>
<a href="<?= htmlspecialchars($postUrl) ?>" class="post-entry-read">→ lire</a>
<?php endif; ?>
</div>
</div>
<?php if (!$isLocked): ?>
<a href="<?= htmlspecialchars($postUrl) ?>" class="stretched-link"></a>
<?php endif; ?>
</article>
<?php
}
?>
<?php if ($isHomepage ?? false): ?>
<?php /* ─── PAGE D'ACCUEIL ─────────────────────────────────────────────── */ ?>
<div class="hero-search">
<form class="hero-search-form" action="/search" method="GET" role="search">
<input class="hero-search-input"
type="search" name="q"
value="<?= htmlspecialchars($_GET['q'] ?? '') ?>"
placeholder="Rechercher un article…"
aria-label="Rechercher un article"
autofocus>
<button type="submit" class="hero-search-btn">Rechercher</button>
</form>
<p class="hero-search-stats">
<?= $totalPublished ?> article<?= $totalPublished > 1 ? 's' : '' ?>
<?php if ($totalUpcoming > 0): ?>
· <?= $totalUpcoming ?> à venir
<?php endif; ?>
</p>
</div>
<?php /* ─── Héro + derniers articles ─────────────────────────────────── */ ?>
<?php if ($heroPost): ?>
<section class="home-section home-section--first">
<h2 class="home-section-title">
Derniers articles
<?php if (!empty($heroPost['featured'] ?? false)): ?>
<span class="home-section-badge">À la une</span>
<?php endif; ?>
</h2>
<?php
$heroUrl = '/post/' . rawurlencode($heroPost['slug']);
$heroCat = trim($heroPost['category'] ?? '');
$heroCover = $heroPost['cover'] ?? '';
$heroCoverStyle = $heroCover !== ''
? 'background-image: url(\'/file?uuid=' . rawurlencode($heroPost['uuid']) . '&name=' . rawurlencode($heroCover) . '\')'
: 'background: ' . coverGradient($heroCat !== '' ? $heroCat : $heroPost['uuid'], $allCats ?? []);
$heroExcerpt = _cardExcerpt($heroPost, $Parsedown, 200);
$heroDate = date('d/m/Y', strtotime((string)($heroPost['published_at'] ?? $heroPost['created_at'] ?? '')));
$heroLocked = $heroPost['published'] && strtotime((string)($heroPost['published_at'] ?? '')) > time() && !hasCapability('view_previews');
?>
<a href="<?= $heroLocked ? '#' : htmlspecialchars($heroUrl) ?>" class="home-hero-card">
<div class="home-hero-card-cover" style="<?= $heroCoverStyle ?>">
<?php if (!empty($heroPost['featured'] ?? false)): ?>
<span class="home-hero-badge">À la une</span>
<?php endif; ?>
<div class="home-hero-card-meta">
<?php if ($heroCat !== ''): ?>
<div class="home-hero-card-cat"><?= htmlspecialchars($heroCat) ?></div>
<?php endif; ?>
<h3 class="home-hero-card-title"><?= htmlspecialchars($heroPost['title']) ?></h3>
<p class="home-hero-card-excerpt"><?= htmlspecialchars($heroExcerpt) ?></p>
<div class="home-hero-card-date"><?= htmlspecialchars($heroDate) ?> &nbsp;→ lire</div>
</div>
</div>
</a>
<?php if (!empty($latestPosts)): ?>
<div class="post-grid">
<?php foreach ($latestPosts as $_lp): ?>
<?php _renderCard($_lp, $privateCats ?? [], $allCats ?? [], $Parsedown); ?>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
$moreCursor = $allPosts[5]['uuid'] ?? null;
if ($moreCursor === null && !empty($latestPosts)) {
$moreCursor = end($latestPosts)['uuid'] ?? null;
}
?>
<?php if ($moreCursor && count($allPosts) > 6): ?>
<div class="home-more-link">
<a href="/page/<?= rawurlencode($moreCursor) ?>" class="btn btn-outline-secondary btn-sm">
Voir plus d'articles →
</a>
</div>
<?php endif; ?>
</section>
<?php endif; ?>
<?php /* ─── Meilleures audiences (AJAX — flux RSS XML /trending?period=1h) ── */ ?>
<section class="home-section" id="home-audiences-section" hidden>
<h2 class="home-section-title">
Meilleures audiences <span class="home-section-title-sub">· 1 heure</span>
</h2>
<div class="post-grid" id="home-audiences-grid"></div>
</section>
<script>
(function(){
var _g=[
'linear-gradient(135deg,#667eea 0%,#764ba2 100%)',
'linear-gradient(135deg,#f093fb 0%,#f5576c 100%)',
'linear-gradient(135deg,#4facfe 0%,#00f2fe 100%)',
'linear-gradient(135deg,#43e97b 0%,#38f9d7 100%)',
'linear-gradient(135deg,#fa709a 0%,#fee140 100%)',
'linear-gradient(135deg,#a18cd1 0%,#fbc2eb 100%)'
];
function _e(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
fetch('/trending?period=1h')
.then(function(r){return r.ok?r.text():Promise.reject();})
.then(function(xml){
var doc=new DOMParser().parseFromString(xml,'application/xml');
var items=Array.from(doc.querySelectorAll('item')).slice(0,6);
if(!items.length)return;
var grid=document.getElementById('home-audiences-grid');
if(!grid)return;
grid.innerHTML=items.map(function(item,i){
var raw=(item.querySelector('title')||{textContent:''}).textContent;
var title=raw.replace(/\s*\(\d+\s+visiteurs?\)$/,'');
var link=((item.querySelector('link')||{}).textContent||'#').trim();
var pd=(item.querySelector('pubDate')||{textContent:''}).textContent;
var date='';
try{if(pd)date=new Date(pd).toLocaleDateString('fr-FR');}catch(e){}
var grad=_g[i%_g.length];
return '<article class="card">'
+'<div class="card-cover" style="background:'+grad+'"></div>'
+'<div class="card-body d-flex flex-column">'
+'<h2 class="card-title"><a href="'+_e(link)+'">'+_e(title)+'</a></h2>'
+'<div class="post-entry-meta mt-auto">'
+(date?'<span>'+_e(date)+'</span>':'')
+'<a href="'+_e(link)+'" class="post-entry-read">→ lire</a>'
+'</div></div>'
+'<a href="'+_e(link)+'" class="stretched-link"></a>'
+'</article>';
}).join('');
var s=document.getElementById('home-audiences-section');
if(s)s.hidden=false;
})
.catch(function(){});
})();
</script>
<?php /* ─── Récemment mis à jour ──────────────────────────────────────── */ ?>
<?php if (!empty($recentlyUpdated)): ?>
<section class="home-section">
<h2 class="home-section-title">Récemment mis à jour</h2>
<div class="home-compact-list">
<?php foreach ($recentlyUpdated as $_ru):
$_ruUrl = '/post/' . rawurlencode($_ru['slug']);
$_ruCat = trim($_ru['category'] ?? '');
$_ruThumb = _cardCoverStyle($_ru, $allCats ?? []);
$_ruDate = date('d/m/Y', strtotime((string)($_ru['updated_at'] ?? $_ru['published_at'] ?? '')));
?>
<a href="<?= htmlspecialchars($_ruUrl) ?>" class="home-compact-item">
<div class="home-compact-thumb" style="<?= $_ruThumb ?>"></div>
<div class="home-compact-meta">
<div class="home-compact-title"><?= htmlspecialchars($_ru['title']) ?></div>
<div class="home-compact-date">mis à jour le <?= htmlspecialchars($_ruDate) ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?php /* ─── Redécouvertes ──────────────────────────────────────────────── */ ?>
<?php if (!empty($redecouvertes)): ?>
<section class="home-section">
<h2 class="home-section-title">Redécouvertes</h2>
<div class="home-compact-list">
<?php foreach ($redecouvertes as $_rd):
$_rdUrl = '/post/' . rawurlencode($_rd['slug']);
$_rdCat = trim($_rd['category'] ?? '');
$_rdThumb = _cardCoverStyle($_rd, $allCats ?? []);
$_rdDate = date('d/m/Y', strtotime((string)($_rd['published_at'] ?? $_rd['created_at'] ?? '')));
?>
<a href="<?= htmlspecialchars($_rdUrl) ?>" class="home-compact-item">
<div class="home-compact-thumb" style="<?= $_rdThumb ?>"></div>
<div class="home-compact-meta">
<div class="home-compact-title"><?= htmlspecialchars($_rd['title']) ?></div>
<div class="home-compact-date"><?= htmlspecialchars($_rdDate) ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?php else: /* ─── VUE PAGINÉE / FILTRÉE ─────────────────────────────── */ ?>
<?php if ($cursor === '' && $filterCat === ''): ?>
<div class="hero-search">
<form class="hero-search-form" action="/search" method="GET" role="search">
<input class="hero-search-input"
type="search" name="q"
value="<?= htmlspecialchars($_GET['q'] ?? '') ?>"
placeholder="Rechercher un article…"
aria-label="Rechercher un article"
autofocus>
<button type="submit" class="hero-search-btn">Rechercher</button>
</form>
<p class="hero-search-stats">
<?= $totalPublished ?> article<?= $totalPublished > 1 ? 's' : '' ?>
<?php if ($totalUpcoming > 0): ?>· <?= $totalUpcoming ?> à venir<?php endif; ?>
</p>
</div>
<?php endif; ?>
<div class="post-grid">
<?php foreach ($posts as $post): ?>
<?php _renderCard($post, $privateCats ?? [], $allCats ?? [], $Parsedown); ?>
<?php endforeach; ?>
</div>
<?php if ($prevCursor !== null || $nextCursor !== null): ?>
<nav class="pagination-nav mt-5" aria-label="Navigation">
<?php
$hasCat = $filterCat !== '';
$catBase = $hasCat ? '/categorie/' . rawurlencode($filterCat) : null;
?>
<?php if ($prevCursor !== null): ?>
<?php
if ($prevCursor === '') {
$prevHref = $hasCat ? $catBase : '/';
} elseif ($hasCat) {
$prevHref = $catBase . '?cursor=' . rawurlencode($prevCursor);
} else {
$prevHref = '/page/' . rawurlencode($prevCursor);
}
?>
<a class="pagination-btn" href="<?= htmlspecialchars($prevHref) ?>">← Plus récents</a>
<?php endif; ?>
<?php if ($nextCursor !== null): ?>
<?php $nextHref = $hasCat ? $catBase . '?cursor=' . rawurlencode($nextCursor) : '/page/' . rawurlencode($nextCursor); ?>
<a class="pagination-btn ms-auto" href="<?= htmlspecialchars($nextHref) ?>">Plus anciens →</a>
<?php endif; ?>
</nav>
<?php endif; ?>
<?php endif; /* fin homepage / paginated */ ?>
<?php
$_tagCats = array_filter(
$allCats ?? [],
static fn ($cat) => isLoggedIn() || !in_array($cat, $privateCats ?? [], true),
ARRAY_FILTER_USE_KEY
);
arsort($_tagCats);
if (!empty($_tagCats)):
$_minCount = min($_tagCats);
$_maxCount = max($_tagCats);
?>
<nav class="tag-cloud mt-5" aria-label="Catégories">
<?php foreach ($_tagCats as $_catName => $_catCount):
$_size = $_minCount === $_maxCount
? 1.1
: round(0.8 + ($_catCount - $_minCount) / ($_maxCount - $_minCount) * 0.9, 2);
$_active = ($filterCat === $_catName);
?>
<a href="/categorie/<?= rawurlencode($_catName) ?>"
class="tag-cloud-item<?= $_active ? ' active' : '' ?>">
<?= htmlspecialchars($_catName) ?>
<span class="tag-count"><?= $_catCount ?></span>
</a>
<?php endforeach; ?>
<?php if ($filterCat !== ''): ?>
<a href="/" class="tag-cloud-reset">× tout afficher</a>
<?php endif; ?>
</nav>
<?php endif; ?>
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
<a href="/new" class="fab-new" title="Nouvel article" aria-label="Nouvel article">+</a>
<?php endif; ?>
<?php
$content = ob_get_clean();
$title = siteTitle();
if (!empty($cursor)) {
$metaRobots = 'noindex, follow';
$canonical = rtrim(APP_URL, '/') . '/';
} elseif ($filterCat !== '') {
$canonical = rtrim(APP_URL, '/') . '/categorie/' . rawurlencode($filterCat);
} else {
$canonical = rtrim(APP_URL, '/') . '/';
}
if (empty($cursor) && $filterCat === '') {
$jsonLd = json_encode([
'@context' => 'https://schema.org',
'@type' => 'WebSite',
'name' => siteTitle(),
'url' => rtrim(APP_URL, '/') . '/',
'description' => siteClaim(),
'inLanguage' => siteLang(),
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
$mainClass = 'container-fluid';
include __DIR__ . '/layout.php';