feat : magic link confirm, notif auteur, rate-limit IP, duplicate, cache MD, lazy img (v1.6.18)
- magic.php : GET=confirmation page, POST=consommation (protège vs scanners) (#27) - verify_comment : email de notification à l'auteur de l'article (#44) - login/index.php : rate limit par IP (MAGIC_MAX_PER_IP_HOUR=10) (#23) - ArticleManager::duplicate() + route POST /duplicate/{uuid} + bouton ⧉ admin/articles (#7) - post_view.php : cache JSON du rendu Markdown (invalidé sur mtime index.md) (#17) - post_view.php : loading="lazy" sur toutes les <img> du contenu (#21) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+55
-1
@@ -1122,6 +1122,27 @@ switch ($action) {
|
||||
header('Location: /edit/' . rawurlencode($uuid));
|
||||
exit;
|
||||
|
||||
case 'duplicate':
|
||||
requireAuth();
|
||||
if ($uuid !== '' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$srcArticle = $articles->getByUuid($uuid);
|
||||
if (!$srcArticle) {
|
||||
header('Location: /admin/articles');
|
||||
exit;
|
||||
}
|
||||
if (!isAdmin() && ($srcArticle['author'] ?? '') !== (currentUserEmail() ?? '')) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$newUuid = $articles->duplicate($uuid, currentUserEmail() ?? '');
|
||||
if ($newUuid) {
|
||||
header('Location: /edit/' . rawurlencode($newUuid) . '/1');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
header('Location: /admin/articles');
|
||||
exit;
|
||||
|
||||
case 'delete':
|
||||
requireAuth();
|
||||
if ($uuid !== '') {
|
||||
@@ -2170,11 +2191,44 @@ switch ($action) {
|
||||
$pdo = dbPdo();
|
||||
if ($pdo && preg_match('/^[0-9]{6}$/', $vcCode)) {
|
||||
require_once BASE_PATH . '/src/CommentManager.php';
|
||||
$cm = new CommentManager($pdo);
|
||||
$cm = new CommentManager($pdo);
|
||||
|
||||
// Récupère les données du commentaire avant vérification (le token est effacé après)
|
||||
$vcPreSt = $pdo->prepare(
|
||||
'SELECT author_name, content FROM comments WHERE verify_token = :t AND verified = FALSE LIMIT 1'
|
||||
);
|
||||
$vcPreSt->execute([':t' => $vcToken]);
|
||||
$vcPreInfo = $vcPreSt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
|
||||
$result = $cm->verify($vcToken, $vcCode);
|
||||
if (is_string($result)) {
|
||||
$vcArticle = $articles->getByUuid($result);
|
||||
$vcSlug = $vcArticle ? ($vcArticle['slug'] ?? $result) : $result;
|
||||
|
||||
// Notification email à l'auteur de l'article
|
||||
$vcAuthorEmail = $vcArticle['author'] ?? '';
|
||||
if ($vcAuthorEmail !== '' && $vcPreInfo) {
|
||||
require_once BASE_PATH . '/src/mailer.php';
|
||||
$vcPostUrl = rtrim(APP_URL, '/') . '/post/' . rawurlencode($vcSlug) . '#comments';
|
||||
$vcAdminUrl = rtrim(APP_URL, '/') . '/admin/comments';
|
||||
$vcExcerpt = mb_strimwidth(trim((string)$vcPreInfo['content']), 0, 200, '…');
|
||||
$vcSubject = '[' . siteTitle() . '] Nouveau commentaire sur « ' . ($vcArticle['title'] ?? '') . ' »';
|
||||
$vcHtml = '<!DOCTYPE html><html><body style="font-family:sans-serif;max-width:560px;margin:0 auto">'
|
||||
. '<p>Bonjour,</p>'
|
||||
. '<p><strong>' . htmlspecialchars((string)$vcPreInfo['author_name']) . '</strong>'
|
||||
. ' a commenté votre article <em>' . htmlspecialchars($vcArticle['title'] ?? '') . '</em> :</p>'
|
||||
. '<blockquote style="border-left:3px solid #ddd;margin:0;padding:0 1em;color:#555">'
|
||||
. nl2br(htmlspecialchars($vcExcerpt)) . '</blockquote>'
|
||||
. '<p><a href="' . htmlspecialchars($vcPostUrl) . '">Voir le commentaire</a>'
|
||||
. ' · <a href="' . htmlspecialchars($vcAdminUrl) . '">Modérer</a></p>'
|
||||
. '</body></html>';
|
||||
try {
|
||||
envoyer_mail_smtp($vcAuthorEmail, $vcSubject, $vcHtml);
|
||||
} catch (\RuntimeException) {
|
||||
// Taux limité ou SMTP indisponible, on ne bloque pas le visiteur
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: /post/' . rawurlencode($vcSlug) . '?verified=1#comments');
|
||||
exit;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user