feat: déplacer 'À lire aussi' après les réactions dans la colonne principale
This commit is contained in:
@@ -36,6 +36,12 @@ RewriteRule ^admin/role/([a-z0-9_-]+)/?$ /index.php?action=admin_role_edit&role_
|
||||
RewriteRule ^admin/([a-z0-9-]+)/?$ /index.php?action=admin&tab=$1 [L,QSA]
|
||||
RewriteRule ^admin/?$ /index.php?action=admin [L,QSA]
|
||||
|
||||
# Réactions et commentaires
|
||||
RewriteRule ^react/?$ /index.php?action=react [L,QSA]
|
||||
RewriteRule ^comment/?$ /index.php?action=comment [L,QSA]
|
||||
RewriteRule ^comment-moderate/?$ /index.php?action=comment_moderate [L,QSA]
|
||||
RewriteRule ^verify-comment/([0-9]{6})/?$ /index.php?action=verify_comment&code=$1 [L,QSA]
|
||||
|
||||
# Pages de gestion
|
||||
RewriteRule ^categories/?$ /index.php?action=categories [L,QSA]
|
||||
RewriteRule ^profile/?$ /index.php?action=profile [L,QSA]
|
||||
|
||||
@@ -681,6 +681,36 @@ textarea.form-control {
|
||||
border-color: var(--vl-accent);
|
||||
}
|
||||
|
||||
.also-read-title {
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--vl-muted);
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.also-read-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .75rem;
|
||||
}
|
||||
|
||||
.also-read-grid .related-card {
|
||||
flex: 1 1 200px;
|
||||
max-width: 280px;
|
||||
background: var(--vl-surface);
|
||||
border-radius: var(--vl-radius);
|
||||
padding: .65rem;
|
||||
box-shadow: var(--vl-shadow-sm);
|
||||
transition: box-shadow .15s, transform .15s;
|
||||
}
|
||||
|
||||
.also-read-grid .related-card:hover {
|
||||
box-shadow: var(--vl-shadow-md);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.related-card {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
|
||||
+212
-1
@@ -22,7 +22,7 @@ $action = $_GET['action'] ?? 'list';
|
||||
$uuid = $_GET['uuid'] ?? '';
|
||||
$slug = $_GET['slug'] ?? '';
|
||||
|
||||
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed', 'add_link', 'delete_link', 'reorder_links'];
|
||||
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed', 'add_link', 'delete_link', 'reorder_links', 'react', 'comment', 'verify_comment', 'comment_moderate'];
|
||||
$metaRobots = in_array($action, $_noindexActions, true) ? 'noindex, nofollow' : null;
|
||||
unset($_noindexActions);
|
||||
|
||||
@@ -635,6 +635,38 @@ switch ($action) {
|
||||
|
||||
$backlinks = $articles->getBacklinks($article['slug'] ?? '', $article['uuid']);
|
||||
|
||||
// Réactions et commentaires
|
||||
require_once BASE_PATH . '/src/ReactionManager.php';
|
||||
require_once BASE_PATH . '/src/CommentManager.php';
|
||||
$reactionStats = array_fill_keys(ReactionManager::TYPES, 0);
|
||||
$visitorReactions = [];
|
||||
$comments = [];
|
||||
$commentFlash = isset($_GET['commented']);
|
||||
$commentVerified = isset($_GET['verified']);
|
||||
$commentError = null;
|
||||
if ($pdo) {
|
||||
$reactionMgr = new ReactionManager($pdo);
|
||||
$commentMgr = new CommentManager($pdo);
|
||||
|
||||
// Cookie visiteur (fingerprint anti-doublon)
|
||||
if (empty($_COOKIE['vl_vid'])) {
|
||||
$vid = bin2hex(random_bytes(16));
|
||||
setcookie('vl_vid', $vid, [
|
||||
'expires' => time() + 365 * 86400,
|
||||
'path' => '/',
|
||||
'secure' => !empty($_SERVER['HTTPS']),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
} else {
|
||||
$vid = $_COOKIE['vl_vid'];
|
||||
}
|
||||
|
||||
$reactionStats = $reactionMgr->statsForArticle($article['uuid']);
|
||||
$visitorReactions = $reactionMgr->visitorReactions($article['uuid'], $vid);
|
||||
$comments = $commentMgr->forArticle($article['uuid']);
|
||||
}
|
||||
|
||||
include BASE_PATH . '/templates/post_view.php';
|
||||
break;
|
||||
|
||||
@@ -1616,6 +1648,161 @@ switch ($action) {
|
||||
header('Location: /edit/' . rawurlencode($uuid));
|
||||
exit;
|
||||
|
||||
case 'react':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
$reactUuid = trim($_POST['uuid'] ?? '');
|
||||
$reactType = trim($_POST['type'] ?? '');
|
||||
$isAjax = ($_POST['_ajax'] ?? '') === '1';
|
||||
|
||||
// Cookie visiteur
|
||||
if (empty($_COOKIE['vl_vid'])) {
|
||||
$vid = bin2hex(random_bytes(16));
|
||||
setcookie('vl_vid', $vid, [
|
||||
'expires' => time() + 365 * 86400,
|
||||
'path' => '/',
|
||||
'secure' => !empty($_SERVER['HTTPS']),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
} else {
|
||||
$vid = $_COOKIE['vl_vid'];
|
||||
}
|
||||
|
||||
$pdo = dbPdo();
|
||||
if ($pdo && $reactUuid !== '') {
|
||||
require_once BASE_PATH . '/src/ReactionManager.php';
|
||||
$rm = new ReactionManager($pdo);
|
||||
$added = $rm->toggle($reactUuid, $reactType, $vid);
|
||||
$count = $rm->statsForArticle($reactUuid)[$reactType] ?? 0;
|
||||
|
||||
if ($isAjax) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['ok' => true, 'active' => $added, 'count' => $count]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$reactBack = $_POST['_back'] ?? '/';
|
||||
header('Location: ' . $reactBack);
|
||||
exit;
|
||||
|
||||
case 'comment':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Honeypot
|
||||
if (($_POST['website'] ?? '') !== '') {
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
|
||||
// CSRF
|
||||
$csrfOk = isset($_POST['_token'], $_SESSION['comment_csrf'])
|
||||
&& hash_equals($_SESSION['comment_csrf'], $_POST['_token']);
|
||||
unset($_SESSION['comment_csrf']);
|
||||
if (!$csrfOk) {
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
|
||||
$cmtUuid = trim($_POST['uuid'] ?? '');
|
||||
$cmtName = trim($_POST['author_name'] ?? '');
|
||||
$cmtEmail = trim($_POST['author_email'] ?? '');
|
||||
$cmtContent = trim($_POST['content'] ?? '');
|
||||
|
||||
$cmtArticle = $cmtUuid !== '' ? $articles->getByUuid($cmtUuid) : null;
|
||||
$cmtBack = $cmtArticle ? '/post/' . rawurlencode($cmtArticle['slug'] ?? $cmtUuid) : '/';
|
||||
|
||||
$pdo = dbPdo();
|
||||
if (!$pdo || !$cmtArticle || $cmtName === '' || !filter_var($cmtEmail, FILTER_VALIDATE_EMAIL) || $cmtContent === '') {
|
||||
header('Location: ' . $cmtBack . '#comment-form-card');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (mb_strlen($cmtContent) > 2000) {
|
||||
header('Location: ' . $cmtBack . '#comment-form-card');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once BASE_PATH . '/src/CommentManager.php';
|
||||
require_once BASE_PATH . '/src/mailer.php';
|
||||
|
||||
$cm = new CommentManager($pdo);
|
||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
$code = $cm->submit($cmtUuid, $cmtName, $cmtEmail, $cmtContent, $ip, $ua);
|
||||
|
||||
$verifyUrl = rtrim(APP_URL, '/') . '/verify-comment/' . $code;
|
||||
$articleUrl = rtrim(APP_URL, '/') . $cmtBack;
|
||||
$subject = '[' . siteTitle() . '] Confirmez votre commentaire';
|
||||
$html = '<!DOCTYPE html><html><body style="font-family:sans-serif;max-width:560px;margin:0 auto">'
|
||||
. '<p>Bonjour ' . htmlspecialchars($cmtName) . ',</p>'
|
||||
. '<p>Cliquez sur le bouton ci-dessous pour confirmer votre commentaire sur <em>' . htmlspecialchars($cmtArticle['title']) . '</em> :</p>'
|
||||
. '<p><a href="' . htmlspecialchars($verifyUrl) . '" style="display:inline-block;padding:10px 20px;background:#0d6efd;color:#fff;text-decoration:none;border-radius:4px">Confirmer mon commentaire</a></p>'
|
||||
. '<p style="color:#888;font-size:.875em">Ou copiez ce lien dans votre navigateur :<br>' . htmlspecialchars($verifyUrl) . '</p>'
|
||||
. '<p style="color:#888;font-size:.875em">Ce lien expire dans 24 heures. Si vous n\'êtes pas à l\'origine de ce message, ignorez-le.</p>'
|
||||
. '</body></html>';
|
||||
|
||||
try {
|
||||
envoyer_mail_smtp($cmtEmail, $subject, $html);
|
||||
} catch (\RuntimeException) {
|
||||
// Taux limité ou erreur SMTP : on continue sans planter le visiteur
|
||||
}
|
||||
|
||||
header('Location: ' . $cmtBack . '?commented=1#comments');
|
||||
exit;
|
||||
|
||||
case 'verify_comment':
|
||||
$vcCode = trim($_GET['code'] ?? '');
|
||||
$pdo = dbPdo();
|
||||
if ($pdo && preg_match('/^[0-9]{6}$/', $vcCode)) {
|
||||
require_once BASE_PATH . '/src/CommentManager.php';
|
||||
$cm = new CommentManager($pdo);
|
||||
$vcUuid = $cm->verify($vcCode);
|
||||
if ($vcUuid !== null) {
|
||||
$vcArticle = $articles->getByUuid($vcUuid);
|
||||
$vcSlug = $vcArticle ? ($vcArticle['slug'] ?? $vcUuid) : $vcUuid;
|
||||
header('Location: /post/' . rawurlencode($vcSlug) . '?verified=1#comments');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
// Code invalide ou expiré
|
||||
http_response_code(404);
|
||||
ob_start();
|
||||
?>
|
||||
<div class="container py-5 text-center">
|
||||
<h1 class="h2 mb-3">Lien invalide ou expiré</h1>
|
||||
<p class="text-muted mb-4">Ce lien de confirmation n'est plus valide (expiré après 24 h) ou a déjà été utilisé.</p>
|
||||
<a href="/" class="btn btn-primary">← Retour à l'accueil</a>
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
$title = 'Lien invalide — ' . siteTitle();
|
||||
$metaRobots = 'noindex, nofollow';
|
||||
include BASE_PATH . '/templates/layout.php';
|
||||
break;
|
||||
|
||||
case 'comment_moderate':
|
||||
requireAuth();
|
||||
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$modId = (int)($_POST['id'] ?? 0);
|
||||
$modPub = (int)($_POST['pub'] ?? 1);
|
||||
$pdo = dbPdo();
|
||||
if ($pdo && $modId > 0) {
|
||||
require_once BASE_PATH . '/src/CommentManager.php';
|
||||
(new CommentManager($pdo))->setPublished($modId, $modPub === 1);
|
||||
}
|
||||
header('Location: /admin/comments');
|
||||
exit;
|
||||
|
||||
case 'rate':
|
||||
requireAuth();
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
@@ -1780,6 +1967,30 @@ switch ($action) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($tab === 'comments') {
|
||||
if (!isAdmin()) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$pdo = dbPdo();
|
||||
if ($pdo) {
|
||||
require_once BASE_PATH . '/src/CommentManager.php';
|
||||
$adminData['comments'] = (new CommentManager($pdo))->allForAdmin();
|
||||
// Enrichit avec le slug de chaque article pour les liens
|
||||
$adminData['articleSlugs'] = [];
|
||||
foreach ($adminData['comments'] as $cmtRow) {
|
||||
$uuid = $cmtRow['article_uuid'];
|
||||
if (!isset($adminData['articleSlugs'][$uuid])) {
|
||||
$a = $articles->getByUuid($uuid);
|
||||
$adminData['articleSlugs'][$uuid] = $a ? ($a['slug'] ?? null) : null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$adminData['comments'] = [];
|
||||
$adminData['articleSlugs'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
include BASE_PATH . '/templates/admin.php';
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user