Files
folio/templates/comments_section.php
cedricAbonnel 24bb244352 perf : session lazy + CSRF cookie + 410 DokuWiki
Contexte : sur abonnel.fr, session_start() était appelé sur chaque
requête PHP (y compris bots), créant ~17 000 fichiers de session/jour
dans un répertoire custom non nettoyé par le cron Debian. Les workers
PHP-FPM grossissaient en mémoire et le pool saturait (1 188 erreurs
503 en 30 minutes).

Changements :

public/index.php
- session_start() uniquement si le cookie de session existe déjà ou si
  la requête est POST. Les bots (GET sans cookie) ne créent plus de
  session.
- CSRF commentaires migré de $_SESSION['comment_csrf'] vers un double-
  submit cookie (_csrf_c, SameSite=Strict, HttpOnly). La session n'est
  plus requise pour les visiteurs anonymes qui postent un commentaire.

templates/comments_section.php
- Génère le token CSRF et le pose en cookie (_csrf_c) au lieu de
  l'écrire en session.

public/.htaccess
- Règle Apache 410 Gone pour toute URL contenant un paramètre ?do=
  (anciens paramètres DokuWiki : do=media, do=export_pdf…). Traité par
  Apache en 2ms sans toucher PHP-FPM.

public/oidc/{start,callback,me}.php
- Correction du bug introduit par 0b8077e : config.php (qui utilise
  BASE_PATH) était chargé avant bootstrap.php (qui définit BASE_PATH).
  Fix : define('BASE_PATH', …) ajouté avant le require config.php.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:06:54 +02:00

145 lines
7.0 KiB
PHP

<?php
// Variables attendues (injectées depuis index.php) :
// $article — tableau article courant (uuid, slug, title)
// $reactionStats — array<string, int>
// $visitorReactions — string[] (types déjà cliqués par ce visiteur)
// $comments — array de commentaires publiés
// $commentFlash — bool|null (commentaire soumis, email envoyé)
// $commentVerified — bool|null (commentaire vérifié et publié)
// $commentError — string|null (message d'erreur)
$_reactionDefs = [
'useful' => ['👍', 'Utile'],
'important' => ['🔥', 'Important'],
'interesting' => ['🤔', 'À creuser'],
];
$_csrfToken = bin2hex(random_bytes(16));
setcookie('_csrf_c', $_csrfToken, [
'expires' => 0,
'path' => '/',
'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
'httponly' => true,
'samesite' => 'Strict',
]);
?>
<?php if (!empty($alsoReadArticles ?? [])): ?>
<!-- ── À lire aussi ──────────────────────────────────────────────── -->
<div class="also-read mb-4" id="also-read">
<h6 class="also-read-title">À lire aussi</h6>
<div class="also-read-grid">
<?php foreach ($alsoReadArticles as $_also):
$_alsoCover = $_also['cover'] ?? '';
$_alsoCat = trim($_also['category'] ?? '');
$_alsoGradient = coverGradient($_alsoCat !== '' ? $_alsoCat : $_also['uuid'], $allCats ?? []);
$_alsoDate = date('d/m/Y', strtotime((string)($_also['published_at'] ?? $_also['created_at'] ?? '')));
?>
<a href="/post/<?= rawurlencode($_also['slug'] ?? '') ?>" class="related-card">
<div class="related-card-thumb" style="<?= $_alsoCover !== ''
? 'background-image:url(/file?uuid=' . rawurlencode($_also['uuid']) . '&name=' . rawurlencode($_alsoCover) . ');background-size:cover;background-position:center'
: 'background:' . htmlspecialchars($_alsoGradient) ?>"></div>
<div class="related-card-body">
<div class="related-card-title"><?= htmlspecialchars($_also['title']) ?></div>
<div class="related-card-date"><?= $_alsoDate ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- ── Commentaires ───────────────────────────────────────────────── -->
<div id="comments" class="mb-4">
<h5 class="mb-3">
Commentaires
<?php if (!empty($comments)): ?>
<span class="badge bg-secondary ms-1"><?= count($comments) ?></span>
<?php endif; ?>
</h5>
<?php if ($commentFlash ?? false): ?>
<div class="alert alert-info">
Un code de confirmation vous a été envoyé par email.
Cliquez sur le lien reçu, puis saisissez le code à 6 chiffres pour publier votre commentaire.
</div>
<?php endif; ?>
<?php if ($commentVerified ?? false): ?>
<div class="alert alert-success">Votre commentaire a été publié. Merci !</div>
<?php endif; ?>
<?php if (!empty($commentError ?? null)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($commentError) ?></div>
<?php endif; ?>
<?php foreach ($comments as $c): ?>
<div class="card mb-3 comment-card" id="comment-<?= (int)$c['id'] ?>">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong class="comment-author"><?= htmlspecialchars($c['author_name']) ?></strong>
<small class="text-muted">
<?= htmlspecialchars(date('d/m/Y à H\hi', strtotime((string)$c['created_at']))) ?>
</small>
</div>
<div class="comment-content"><?= nl2br(htmlspecialchars((string)$c['content'])) ?></div>
<?php if (function_exists('isAdmin') && isAdmin()): ?>
<div class="mt-2">
<form method="post" action="/comment-moderate" class="d-inline">
<input type="hidden" name="id" value="<?= (int)$c['id'] ?>">
<input type="hidden" name="pub" value="0">
<button type="submit" class="btn btn-sm btn-outline-danger">Masquer</button>
</form>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php if (empty($comments) && !($commentFlash ?? false) && !($commentVerified ?? false)): ?>
<p class="text-muted mb-4">Aucun commentaire pour l'instant. Soyez le premier !</p>
<?php endif; ?>
<!-- Formulaire -->
<div class="card mt-3" id="comment-form-card">
<div class="card-body">
<h6 class="card-title mb-3">Laisser un commentaire</h6>
<form method="post" action="/comment" id="comment-form">
<input type="hidden" name="_token" value="<?= htmlspecialchars($_csrfToken) ?>">
<input type="hidden" name="uuid" value="<?= htmlspecialchars($article['uuid']) ?>">
<!-- honeypot -->
<div class="d-none" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
<div class="row g-3">
<div class="col-sm-6">
<label class="form-label" for="comment-name">Nom <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="comment-name" name="author_name"
maxlength="100" required placeholder="Votre nom">
</div>
<div class="col-sm-6">
<label class="form-label" for="comment-email">
Email <span class="text-danger">*</span>
<span class="text-muted fw-normal">(non publié)</span>
</label>
<input type="email" class="form-control" id="comment-email" name="author_email"
maxlength="254" required placeholder="votre@email.fr">
</div>
<div class="col-12">
<label class="form-label" for="comment-content">Commentaire <span class="text-danger">*</span></label>
<textarea class="form-control" id="comment-content" name="content"
rows="4" maxlength="2000" required
placeholder="Votre commentaire (2000 caractères max)"></textarea>
</div>
<div class="col-12 d-flex align-items-center gap-3">
<button type="submit" class="btn btn-primary">Envoyer</button>
<small class="text-muted">Un code de vérification sera envoyé à votre adresse email.</small>
</div>
</div>
</form>
</div>
</div>
</div>