fix #29 : envoyer le lien magique par email (envoyer_mail_smtp)

This commit is contained in:
Cedric Abonnel
2026-05-13 23:41:58 +02:00
commit 8a85c15372
129 changed files with 22818 additions and 0 deletions
+330
View File
@@ -0,0 +1,330 @@
<?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 /* ─── Tendances ───────────────────────────────────────────────────── */ ?>
<?php if (!empty($popularPosts)): ?>
<section class="home-section">
<h2 class="home-section-title">
Tendances <span class="home-section-title-sub">· 10 derniers jours</span>
</h2>
<div class="post-grid">
<?php foreach ($popularPosts as $_pp): ?>
<?php _renderCard($_pp, $privateCats ?? [], $allCats ?? [], $Parsedown); ?>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?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';