feat: clean URLs + fix addFileMeta silent failure

This commit is contained in:
Cedric Abonnel
2026-05-12 10:04:58 +02:00
parent 045e93cffd
commit 70fd55be6f
20 changed files with 314 additions and 206 deletions
+29
View File
@@ -11,6 +11,35 @@ RewriteRule ^ - [L]
# URL propre pour les articles : /post/<slug> # URL propre pour les articles : /post/<slug>
RewriteRule ^post/([a-z0-9][a-z0-9-]*)/?$ /index.php?action=view&slug=$1 [L,QSA] RewriteRule ^post/([a-z0-9][a-z0-9-]*)/?$ /index.php?action=view&slug=$1 [L,QSA]
# Édition / création
RewriteRule ^edit/([0-9a-f-]{36})/?$ /index.php?action=edit&uuid=$1 [L,QSA]
RewriteRule ^new/?$ /index.php?action=create [L,QSA]
RewriteRule ^delete/([0-9a-f-]{36})/?$ /index.php?action=delete&uuid=$1 [L,QSA]
# Sources et diff
RewriteRule ^sources/([0-9a-f-]{36})/?$ /index.php?action=sources&uuid=$1 [L,QSA]
RewriteRule ^diff/([0-9a-f-]{36})/(\d+)/?$ /index.php?action=diff&uuid=$1&rev=$2 [L,QSA]
# Fichiers / import
RewriteRule ^files/([0-9a-f-]{36})/add/?$ /index.php?action=add_files&uuid=$1 [L,QSA]
RewriteRule ^import/([0-9a-f-]{36})/?$ /index.php?action=import_image&uuid=$1 [L,QSA]
# Admin (regen-thumbs avant la règle générique admin/<tab>)
RewriteRule ^admin/regen-thumbs/?$ /index.php?action=regen_thumbs [L,QSA]
RewriteRule ^admin/([a-z0-9-]+)/?$ /index.php?action=admin&tab=$1 [L,QSA]
RewriteRule ^admin/?$ /index.php?action=admin [L,QSA]
# Pages de gestion
RewriteRule ^categories/?$ /index.php?action=categories [L,QSA]
RewriteRule ^profile/?$ /index.php?action=profile [L,QSA]
RewriteRule ^search/?$ /index.php?action=search [L,QSA]
# Pages statiques
RewriteRule ^about/?$ /index.php?action=about [L,QSA]
RewriteRule ^legal/?$ /index.php?action=legal [L,QSA]
RewriteRule ^licenses/?$ /index.php?action=licenses [L,QSA]
RewriteRule ^contact/?$ /index.php?action=contact [L,QSA]
# Flux RSS — /feed, /rss et /rss.xml pointent tous vers feed.php # Flux RSS — /feed, /rss et /rss.xml pointent tous vers feed.php
RewriteRule ^feed/?$ /feed.php [L,QSA] RewriteRule ^feed/?$ /feed.php [L,QSA]
RewriteRule ^rss/?$ /feed.php [L,QSA] RewriteRule ^rss/?$ /feed.php [L,QSA]
+52 -29
View File
@@ -463,7 +463,7 @@ switch ($action) {
} }
} }
$formAction = '/?action=create'; $formAction = '/new';
$action = 'create'; $action = 'create';
include BASE_PATH . '/templates/post_form.php'; include BASE_PATH . '/templates/post_form.php';
break; break;
@@ -637,7 +637,7 @@ switch ($action) {
} }
} }
$formAction = '/?action=edit&uuid=' . rawurlencode($uuid); $formAction = '/edit/' . rawurlencode($uuid);
$action = 'edit'; $action = 'edit';
$existingFiles = $articles->getFiles($uuid); $existingFiles = $articles->getFiles($uuid);
$insertUrl = ''; $insertUrl = '';
@@ -653,7 +653,7 @@ switch ($action) {
if ($uuid !== '' && $fileName !== '' && $fileName[0] !== '.') { if ($uuid !== '' && $fileName !== '' && $fileName[0] !== '.') {
$articles->deleteFile($uuid, $fileName); $articles->deleteFile($uuid, $fileName);
} }
header('Location: /?action=edit&uuid=' . rawurlencode($uuid)); header('Location: /edit/' . rawurlencode($uuid));
exit; exit;
case 'delete': case 'delete':
@@ -664,6 +664,30 @@ switch ($action) {
header('Location: /'); header('Location: /');
exit; exit;
case 'delete_revision':
requireAuth();
if (!isAdmin()) {
http_response_code(403);
exit;
}
if ($uuid !== '' && isset($_POST['rev_n'])) {
$articles->deleteRevision($uuid, (int)$_POST['rev_n']);
}
header('Location: /edit/' . rawurlencode($uuid) . '#historyPanel');
exit;
case 'delete_all_revisions':
requireAuth();
if (!isAdmin()) {
http_response_code(403);
exit;
}
if ($uuid !== '') {
$articles->deleteAllRevisions($uuid);
}
header('Location: /edit/' . rawurlencode($uuid));
exit;
case 'categories': case 'categories':
requireAuth(); requireAuth();
$cats = $articles->getCategories(); $cats = $articles->getCategories();
@@ -680,7 +704,7 @@ switch ($action) {
$articles->renameCategory($old, $new); $articles->renameCategory($old, $new);
} }
} }
header('Location: /?action=categories'); header('Location: /categories');
exit; exit;
case 'delete_category': case 'delete_category':
@@ -691,7 +715,7 @@ switch ($action) {
$articles->deleteCategory($cat); $articles->deleteCategory($cat);
} }
} }
header('Location: /?action=categories'); header('Location: /categories');
exit; exit;
case 'toggle_private_category': case 'toggle_private_category':
@@ -702,7 +726,7 @@ switch ($action) {
$articles->togglePrivateCategory($cat); $articles->togglePrivateCategory($cat);
} }
} }
header('Location: /?action=categories'); header('Location: /categories');
exit; exit;
case 'about': case 'about':
@@ -740,7 +764,7 @@ switch ($action) {
} }
} }
if ($revIndex === null || $revN < 1) { if ($revIndex === null || $revN < 1) {
header('Location: /?action=edit&uuid=' . rawurlencode($uuid)); header('Location: /edit/' . rawurlencode($uuid));
exit; exit;
} }
$oldContent = $articles->getRevisionContent($uuid, $revN); $oldContent = $articles->getRevisionContent($uuid, $revN);
@@ -789,7 +813,7 @@ switch ($action) {
]); ]);
} }
} }
header('Location: /?action=edit&uuid=' . rawurlencode($uuid)); header('Location: /edit/' . rawurlencode($uuid));
exit; exit;
} }
include BASE_PATH . '/templates/add_files.php'; include BASE_PATH . '/templates/add_files.php';
@@ -816,7 +840,7 @@ switch ($action) {
case 'import_image_step2': case 'import_image_step2':
requireAuth(); requireAuth();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /?action=import_image&uuid=' . rawurlencode($uuid)); header('Location: /import/' . rawurlencode($uuid));
exit; exit;
} }
$step2Article = $articles->getByUuid($uuid); $step2Article = $articles->getByUuid($uuid);
@@ -827,12 +851,12 @@ switch ($action) {
} }
$step2Url = trim($_POST['image_url'] ?? ''); $step2Url = trim($_POST['image_url'] ?? '');
if (!filter_var($step2Url, FILTER_VALIDATE_URL) || !preg_match('#^https?://#i', $step2Url)) { if (!filter_var($step2Url, FILTER_VALIDATE_URL) || !preg_match('#^https?://#i', $step2Url)) {
header('Location: /?action=import_image&uuid=' . rawurlencode($uuid) . '&error=1'); header('Location: /import/' . rawurlencode($uuid) . '?error=1');
exit; exit;
} }
$step2Meta = fetchUrlMeta($step2Url); $step2Meta = fetchUrlMeta($step2Url);
if (!($step2Meta['ok'] ?? false)) { if (!($step2Meta['ok'] ?? false)) {
header('Location: /?action=import_image&uuid=' . rawurlencode($uuid) . '&error=1'); header('Location: /import/' . rawurlencode($uuid) . '?error=1');
exit; exit;
} }
// Capture d'écran pour prévisualisation (pages HTML uniquement) // Capture d'écran pour prévisualisation (pages HTML uniquement)
@@ -862,7 +886,7 @@ switch ($action) {
$ackUrl = filter_var($_GET['image_url'] ?? '', FILTER_VALIDATE_URL) $ackUrl = filter_var($_GET['image_url'] ?? '', FILTER_VALIDATE_URL)
? $_GET['image_url'] : ''; ? $_GET['image_url'] : '';
if ($ackUrl === '') { if ($ackUrl === '') {
header('Location: /?action=import_image&uuid=' . rawurlencode($uuid)); header('Location: /import/' . rawurlencode($uuid));
exit; exit;
} }
$ackTitle = $_GET['img_title'] ?? ''; $ackTitle = $_GET['img_title'] ?? '';
@@ -903,7 +927,7 @@ switch ($action) {
$urlArticle = $articles->getByUuid($urlUuid); $urlArticle = $articles->getByUuid($urlUuid);
if (!$urlArticle || $imageUrl === '' || !filter_var($imageUrl, FILTER_VALIDATE_URL)) { if (!$urlArticle || $imageUrl === '' || !filter_var($imageUrl, FILTER_VALIDATE_URL)) {
header('Location: /?action=import_image&uuid=' . rawurlencode($urlUuid)); header('Location: /import/' . rawurlencode($urlUuid));
exit; exit;
} }
@@ -911,13 +935,13 @@ switch ($action) {
if ($mode === 'screenshot') { if ($mode === 'screenshot') {
if ($screenshotFile === '' || $screenshotFile !== '_preview.png') { if ($screenshotFile === '' || $screenshotFile !== '_preview.png') {
header('Location: /?action=import_image&uuid=' . rawurlencode($urlUuid) . '&error=1'); header('Location: /import/' . rawurlencode($urlUuid) . '?error=1');
exit; exit;
} }
$filesDir = BASE_PATH . '/data/' . $urlUuid . '/files'; $filesDir = BASE_PATH . '/data/' . $urlUuid . '/files';
$previewPath = $filesDir . '/' . $screenshotFile; $previewPath = $filesDir . '/' . $screenshotFile;
if (!file_exists($previewPath)) { if (!file_exists($previewPath)) {
header('Location: /?action=import_image&uuid=' . rawurlencode($urlUuid) . '&error=1'); header('Location: /import/' . rawurlencode($urlUuid) . '?error=1');
exit; exit;
} }
$hash = substr(hash_file('sha256', $previewPath), 0, 16); $hash = substr(hash_file('sha256', $previewPath), 0, 16);
@@ -928,7 +952,7 @@ switch ($action) {
if ($isCover) { if ($isCover) {
$articles->setCover($urlUuid, $destName); $articles->setCover($urlUuid, $destName);
} }
header('Location: /?action=edit&uuid=' . rawurlencode($urlUuid)); header('Location: /edit/' . rawurlencode($urlUuid));
exit; exit;
} }
@@ -993,7 +1017,7 @@ switch ($action) {
@unlink($filesDir . '/' . $screenshotFile); @unlink($filesDir . '/' . $screenshotFile);
} }
$articles->addExternalLink($urlUuid, $imageUrl, $imgTitle, $imgAuthor, $importedMeta); $articles->addExternalLink($urlUuid, $imageUrl, $imgTitle, $imgAuthor, $importedMeta);
header('Location: /?action=edit&uuid=' . rawurlencode($urlUuid)); header('Location: /edit/' . rawurlencode($urlUuid));
exit; exit;
} }
@@ -1013,9 +1037,9 @@ switch ($action) {
$imported = $articles->addFileFromUrl($urlUuid, $imageUrl, $isCover, $imgAuthor, $imgSource, $imgTitle, $importedMeta); $imported = $articles->addFileFromUrl($urlUuid, $imageUrl, $isCover, $imgAuthor, $imgSource, $imgTitle, $importedMeta);
if ($imported) { if ($imported) {
header('Location: /?action=edit&uuid=' . rawurlencode($urlUuid)); header('Location: /edit/' . rawurlencode($urlUuid));
} else { } else {
header('Location: /?action=import_image&uuid=' . rawurlencode($urlUuid) . '&error=1&mode=download'); header('Location: /import/' . rawurlencode($urlUuid) . '?error=1&mode=download');
} }
exit; exit;
@@ -1044,8 +1068,7 @@ switch ($action) {
ob_start(); ob_start();
?> ?>
<h1 class="h4 mb-4">Génération des aperçus de liens</h1> <h1 class="h4 mb-4">Génération des aperçus de liens</h1>
<form method="get" action="/"> <form method="get" action="/admin/regen-thumbs">
<input type="hidden" name="action" value="regen_thumbs">
<input type="hidden" name="run" value="1"> <input type="hidden" name="run" value="1">
<div class="card p-4 mb-4" style="max-width:480px"> <div class="card p-4 mb-4" style="max-width:480px">
<div class="form-check mb-3"> <div class="form-check mb-3">
@@ -1195,7 +1218,7 @@ switch ($action) {
echo $done . ' capturé' . ($done > 1 ? 's' : '') . ', '; echo $done . ' capturé' . ($done > 1 ? 's' : '') . ', ';
echo $fail . ' échec' . ($fail > 1 ? 's' : '') . ', '; echo $fail . ' échec' . ($fail > 1 ? 's' : '') . ', ';
echo $skip . ' ignoré' . ($skip > 1 ? 's' : '') . '.</p>'; echo $skip . ' ignoré' . ($skip > 1 ? 's' : '') . '.</p>';
echo '<a href="/?action=regen_thumbs" class="btn btn-secondary btn-sm">← Retour</a>'; echo '<a href="/admin/regen-thumbs" class="btn btn-secondary btn-sm">← Retour</a>';
echo '</body></html>'; echo '</body></html>';
exit; exit;
@@ -1207,7 +1230,7 @@ switch ($action) {
$articles->removeExternalLink($uuid, $linkUrl); $articles->removeExternalLink($uuid, $linkUrl);
} }
} }
header('Location: /?action=edit&uuid=' . rawurlencode($uuid)); header('Location: /edit/' . rawurlencode($uuid));
exit; exit;
case 'rate': case 'rate':
@@ -1368,7 +1391,7 @@ switch ($action) {
$st->execute([':email' => $targetEmail, ':role' => $roleName, ':by' => currentUserEmail()]); $st->execute([':email' => $targetEmail, ':role' => $roleName, ':by' => currentUserEmail()]);
} }
} }
header('Location: /?action=admin&tab=users'); header('Location: /admin/users');
exit; exit;
case 'admin_revoke_role': case 'admin_revoke_role':
@@ -1390,7 +1413,7 @@ switch ($action) {
$st->execute([':email' => $targetEmail, ':role' => $roleName]); $st->execute([':email' => $targetEmail, ':role' => $roleName]);
} }
} }
header('Location: /?action=admin&tab=users'); header('Location: /admin/users');
exit; exit;
case 'admin_create_role': case 'admin_create_role':
@@ -1411,7 +1434,7 @@ switch ($action) {
} }
} }
} }
header('Location: /?action=admin&tab=roles'); header('Location: /admin/roles');
exit; exit;
case 'admin_update_role': case 'admin_update_role':
@@ -1429,7 +1452,7 @@ switch ($action) {
$st->execute([':l' => $roleLabel, ':id' => $roleId]); $st->execute([':l' => $roleLabel, ':id' => $roleId]);
} }
} }
header('Location: /?action=admin&tab=roles'); header('Location: /admin/roles');
exit; exit;
case 'admin_delete_role': case 'admin_delete_role':
@@ -1446,7 +1469,7 @@ switch ($action) {
$st->execute([':id' => $roleId]); $st->execute([':id' => $roleId]);
} }
} }
header('Location: /?action=admin&tab=roles'); header('Location: /admin/roles');
exit; exit;
case 'admin_update_role_caps': case 'admin_update_role_caps':
@@ -1469,7 +1492,7 @@ switch ($action) {
unset($_SESSION['user_capabilities']); unset($_SESSION['user_capabilities']);
} }
} }
header('Location: /?action=admin&tab=roles'); header('Location: /admin/roles');
exit; exit;
case 'profile': case 'profile':
+1 -5
View File
@@ -2,10 +2,6 @@
declare(strict_types=1); declare(strict_types=1);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__DIR__, 2) . '/bootstrap.php'; require_once dirname(__DIR__, 2) . '/bootstrap.php';
require_once dirname(__DIR__, 2) . '/config/config.php'; require_once dirname(__DIR__, 2) . '/config/config.php';
@@ -29,7 +25,7 @@ $debug = (env('APP_DEBUG', '0') === '1');
$OIDC_ISSUER = rtrim((string)(env('OIDC_ISSUER') ?? ''), '/'); $OIDC_ISSUER = rtrim((string)(env('OIDC_ISSUER') ?? ''), '/');
$OIDC_CLIENT_ID = (string)(env('OIDC_CLIENT_ID') ?? ''); $OIDC_CLIENT_ID = (string)(env('OIDC_CLIENT_ID') ?? '');
$OIDC_CLIENT_SECRET = (string)(env('OIDC_CLIENT_SECRET') ?? ''); $OIDC_CLIENT_SECRET = (string)(env('OIDC_CLIENT_SECRET') ?? '');
$OIDC_REDIRECT_URI = (string)(env('OIDC_REDIRECT_URI') ?: url('oidc/callback.php')); $OIDC_REDIRECT_URI = (string)(env('OIDC_REDIRECT_URI') ?: url('oidc/callback'));
if (!$OIDC_ISSUER || !$OIDC_CLIENT_ID || !$OIDC_REDIRECT_URI) { if (!$OIDC_ISSUER || !$OIDC_CLIENT_ID || !$OIDC_REDIRECT_URI) {
http_response_code(500); http_response_code(500);
-4
View File
@@ -4,10 +4,6 @@
// version : 20251005 // version : 20251005
declare(strict_types=1); declare(strict_types=1);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__DIR__, 2) . '/bootstrap.php'; require_once dirname(__DIR__, 2) . '/bootstrap.php';
require_once dirname(__DIR__, 2) . '/config/config.php'; require_once dirname(__DIR__, 2) . '/config/config.php';
-4
View File
@@ -2,10 +2,6 @@
declare(strict_types=1); declare(strict_types=1);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__DIR__, 2) . '/bootstrap.php'; require_once dirname(__DIR__, 2) . '/bootstrap.php';
require_once dirname(__DIR__, 2) . '/config/config.php'; require_once dirname(__DIR__, 2) . '/config/config.php';
+51 -6
View File
@@ -40,7 +40,7 @@ class ArticleManager
$articles[] = $article; $articles[] = $article;
} }
usort($articles, static fn ($a, $b) => strcmp($b['created_at'] ?? '', $a['created_at'] ?? '')); usort($articles, static fn ($a, $b) => strcmp($b['published_at'] ?? '', $a['published_at'] ?? ''));
return $articles; return $articles;
} }
@@ -131,8 +131,12 @@ class ArticleManager
$slug = $slug !== '' ? $this->sanitizeSlug($slug) : $this->generateSlug($title); $slug = $slug !== '' ? $this->sanitizeSlug($slug) : $this->generateSlug($title);
$slug = $this->uniqueSlug($slug, $uuid); $slug = $this->uniqueSlug($slug, $uuid);
// Snapshot de l'état courant avant écrasement // Snapshot de l'état courant avant écrasement — uniquement si le contenu ou le titre a changé
$revisions = $article['revisions'] ?? []; $revisions = $article['revisions'] ?? [];
$contentChanged = ltrim($content) !== ($article['content'] ?? '');
$titleChanged = $title !== ($article['title'] ?? '');
if ($contentChanged || $titleChanged) {
$revDir = $this->dataDir . '/' . $uuid . '/revisions'; $revDir = $this->dataDir . '/' . $uuid . '/revisions';
if (!is_dir($revDir)) { if (!is_dir($revDir)) {
mkdir($revDir, 0755, true); mkdir($revDir, 0755, true);
@@ -153,6 +157,7 @@ class ArticleManager
$oldest = array_shift($revisions); $oldest = array_shift($revisions);
@unlink(sprintf('%s/%04d.md', $revDir, (int)($oldest['n'] ?? 0))); @unlink(sprintf('%s/%04d.md', $revDir, (int)($oldest['n'] ?? 0)));
} }
} // fin if ($contentChanged || $titleChanged)
$meta = [ $meta = [
'uuid' => $uuid, 'uuid' => $uuid,
@@ -211,10 +216,6 @@ class ArticleManager
return; return;
} }
$filename = basename($filename); $filename = basename($filename);
$path = $this->dataDir . '/' . $uuid . '/files/' . $filename;
if (!file_exists($path)) {
return;
}
$raw = file_get_contents($this->dataDir . '/' . $uuid . '/meta.json'); $raw = file_get_contents($this->dataDir . '/' . $uuid . '/meta.json');
if ($raw === false) { if ($raw === false) {
@@ -774,6 +775,50 @@ class ArticleManager
return $meta; return $meta;
} }
public function deleteRevision(string $uuid, int $revN): void
{
if (!$this->isValidUuid($uuid) || $revN < 1) {
return;
}
$dir = $this->dataDir . '/' . $uuid;
$raw = @file_get_contents($dir . '/meta.json');
$meta = $raw !== false ? json_decode($raw, true) : null;
if (!is_array($meta)) {
return;
}
$revisions = $meta['revisions'] ?? [];
$newRevisions = [];
foreach ($revisions as $rev) {
if ((int)($rev['n'] ?? 0) === $revN) {
@unlink(sprintf('%s/revisions/%04d.md', $dir, $revN));
} else {
$newRevisions[] = $rev;
}
}
$meta['revisions'] = array_values($newRevisions);
$this->writeMeta($dir, $meta);
}
public function deleteAllRevisions(string $uuid): void
{
if (!$this->isValidUuid($uuid)) {
return;
}
$dir = $this->dataDir . '/' . $uuid;
$revDir = $dir . '/revisions';
if (is_dir($revDir)) {
foreach (glob($revDir . '/*.md') ?: [] as $f) {
@unlink($f);
}
}
$raw = @file_get_contents($dir . '/meta.json');
$meta = $raw !== false ? json_decode($raw, true) : null;
if (is_array($meta)) {
$meta['revisions'] = [];
$this->writeMeta($dir, $meta);
}
}
private function writeMeta(string $dir, array $meta): void private function writeMeta(string $dir, array $meta): void
{ {
file_put_contents( file_put_contents(
+1 -1
View File
@@ -85,7 +85,7 @@ ob_start();
<div class="card mb-4"> <div class="card mb-4">
<div class="card-body"> <div class="card-body">
<p class="mb-0"> <p class="mb-0">
Vous pouvez me joindre via le <a href="/?action=contact">formulaire de contact</a>. Vous pouvez me joindre via le <a href="/contact">formulaire de contact</a>.
Je lis tous les messages, même si je ne réponds pas toujours vite. Je lis tous les messages, même si je ne réponds pas toujours vite.
</p> </p>
</div> </div>
+3 -3
View File
@@ -4,7 +4,7 @@ $existingFiles = $articles->getFiles($addFilesArticle['uuid']);
?> ?>
<div class="d-flex align-items-center gap-3 mb-4"> <div class="d-flex align-items-center gap-3 mb-4">
<a href="/?action=edit&uuid=<?= rawurlencode($addFilesArticle['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a> <a href="/edit/<?= rawurlencode($addFilesArticle['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a>
<h1 class="h4 mb-0">Ajouter des fichiers</h1> <h1 class="h4 mb-0">Ajouter des fichiers</h1>
</div> </div>
@@ -19,7 +19,7 @@ $existingFiles = $articles->getFiles($addFilesArticle['uuid']);
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form method="POST" <form method="POST"
action="/?action=add_files&uuid=<?= rawurlencode($addFilesArticle['uuid']) ?>" action="/files/<?= rawurlencode($addFilesArticle['uuid']) ?>/add"
enctype="multipart/form-data"> enctype="multipart/form-data">
<div class="mb-3"> <div class="mb-3">
@@ -34,7 +34,7 @@ $existingFiles = $articles->getFiles($addFilesArticle['uuid']);
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Uploader</button> <button type="submit" class="btn btn-primary">Uploader</button>
<a href="/?action=edit&uuid=<?= rawurlencode($addFilesArticle['uuid']) ?>" <a href="/edit/<?= rawurlencode($addFilesArticle['uuid']) ?>"
class="btn btn-outline-secondary">Annuler</a> class="btn btn-outline-secondary">Annuler</a>
</div> </div>
+7 -7
View File
@@ -17,7 +17,7 @@ function adminStatusBadge(array $a, int $now): string
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">Administration</h1> <h1 class="h3 mb-0">Administration</h1>
<a href="/?action=create" class="btn btn-primary btn-sm">+ Nouvel article</a> <a href="/new" class="btn btn-primary btn-sm">+ Nouvel article</a>
</div> </div>
<!-- Onglets --> <!-- Onglets -->
@@ -25,24 +25,24 @@ function adminStatusBadge(array $a, int $now): string
<?php if (isAdmin()): ?> <?php if (isAdmin()): ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= $tab === 'dashboard' ? 'active' : '' ?>" <a class="nav-link <?= $tab === 'dashboard' ? 'active' : '' ?>"
href="/?action=admin&tab=dashboard">Tableau de bord</a> href="/admin/dashboard">Tableau de bord</a>
</li> </li>
<?php endif; ?> <?php endif; ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= $tab === 'articles' ? 'active' : '' ?>" <a class="nav-link <?= $tab === 'articles' ? 'active' : '' ?>"
href="/?action=admin&tab=articles"><?= isAdmin() ? 'Articles' : 'Mes articles' ?></a> href="/admin/articles"><?= isAdmin() ? 'Articles' : 'Mes articles' ?></a>
</li> </li>
<?php if (isAdmin()): ?> <?php if (isAdmin()): ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= $tab === 'users' ? 'active' : '' ?>" <a class="nav-link <?= $tab === 'users' ? 'active' : '' ?>"
href="/?action=admin&tab=users">Utilisateurs</a> href="/admin/users">Utilisateurs</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= $tab === 'roles' ? 'active' : '' ?>" <a class="nav-link <?= $tab === 'roles' ? 'active' : '' ?>"
href="/?action=admin&tab=roles">Rôles</a> href="/admin/roles">Rôles</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/?action=categories">Catégories</a> <a class="nav-link" href="/categories">Catégories</a>
</li> </li>
<?php endif; ?> <?php endif; ?>
</ul> </ul>
@@ -132,7 +132,7 @@ function adminStatusBadge(array $a, int $now): string
<?= htmlspecialchars(date('d/m/Y', strtotime((string)($a['published_at'] ?? $a['created_at'] ?? '')))) ?> <?= htmlspecialchars(date('d/m/Y', strtotime((string)($a['published_at'] ?? $a['created_at'] ?? '')))) ?>
</td> </td>
<td class="text-end text-nowrap"> <td class="text-end text-nowrap">
<a href="/?action=edit&uuid=<?= htmlspecialchars($a['uuid']) ?>" <a href="/edit/<?= htmlspecialchars($a['uuid']) ?>"
class="btn btn-outline-secondary btn-sm">Modifier</a> class="btn btn-outline-secondary btn-sm">Modifier</a>
</td> </td>
</tr> </tr>
+2 -2
View File
@@ -4,7 +4,7 @@
<div class="col-lg-8 col-xl-7"> <div class="col-lg-8 col-xl-7">
<div class="d-flex align-items-center gap-3 mb-4"> <div class="d-flex align-items-center gap-3 mb-4">
<a href="/?action=import_image&uuid=<?= rawurlencode($ackArticle['uuid']) ?>" <a href="/import/<?= rawurlencode($ackArticle['uuid']) ?>"
class="btn btn-secondary btn-sm">← Retour</a> class="btn btn-secondary btn-sm">← Retour</a>
<h1 class="h4 mb-0">Confirmation — droits d'auteur</h1> <h1 class="h4 mb-0">Confirmation — droits d'auteur</h1>
</div> </div>
@@ -99,7 +99,7 @@
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Télécharger et insérer</button> <button type="submit" class="btn btn-primary">Télécharger et insérer</button>
<a href="/?action=import_image&uuid=<?= rawurlencode($ackArticle['uuid']) ?>" <a href="/import/<?= rawurlencode($ackArticle['uuid']) ?>"
class="btn btn-outline-secondary">Annuler</a> class="btn btn-outline-secondary">Annuler</a>
</div> </div>
</form> </form>
+1 -1
View File
@@ -4,7 +4,7 @@ $revMeta = $revisions[$revIndex] ?? [];
?> ?>
<div class="d-flex align-items-center gap-3 mb-3 flex-wrap"> <div class="d-flex align-items-center gap-3 mb-3 flex-wrap">
<a href="/?action=edit&uuid=<?= htmlspecialchars($article['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a> <a href="/edit/<?= htmlspecialchars($article['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a>
<div> <div>
<strong><?= htmlspecialchars($article['title']) ?></strong> <strong><?= htmlspecialchars($article['title']) ?></strong>
&mdash; révision #<?= (int)($revMeta['n'] ?? $revIndex + 1) ?> &mdash; révision #<?= (int)($revMeta['n'] ?? $revIndex + 1) ?>
+2 -2
View File
@@ -1,7 +1,7 @@
<?php ob_start(); ?> <?php ob_start(); ?>
<div class="d-flex align-items-center gap-3 mb-4"> <div class="d-flex align-items-center gap-3 mb-4">
<a href="/?action=edit&uuid=<?= rawurlencode($importArticle['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a> <a href="/edit/<?= rawurlencode($importArticle['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a>
<h1 class="h4 mb-0">Importer un fichier depuis une URL</h1> <h1 class="h4 mb-0">Importer un fichier depuis une URL</h1>
</div> </div>
@@ -28,7 +28,7 @@
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Suivant →</button> <button type="submit" class="btn btn-primary">Suivant →</button>
<a href="/?action=edit&uuid=<?= rawurlencode($importArticle['uuid']) ?>" <a href="/edit/<?= rawurlencode($importArticle['uuid']) ?>"
class="btn btn-outline-secondary">Annuler</a> class="btn btn-outline-secondary">Annuler</a>
</div> </div>
</form> </form>
+2 -2
View File
@@ -40,7 +40,7 @@ $preSource = $step2Meta['canonical'] ?? $step2Meta['source'] ?? $step2Url;
?> ?>
<div class="d-flex align-items-center gap-3 mb-4"> <div class="d-flex align-items-center gap-3 mb-4">
<a href="/?action=import_image&uuid=<?= rawurlencode($step2Article['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a> <a href="/import/<?= rawurlencode($step2Article['uuid']) ?>" class="btn btn-secondary btn-sm">← Retour</a>
<h1 class="h4 mb-0">Importer un fichier</h1> <h1 class="h4 mb-0">Importer un fichier</h1>
</div> </div>
@@ -192,7 +192,7 @@ if ($visibleRows): ?>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Valider</button> <button type="submit" class="btn btn-primary">Valider</button>
<a href="/?action=edit&uuid=<?= rawurlencode($step2Article['uuid']) ?>" <a href="/edit/<?= rawurlencode($step2Article['uuid']) ?>"
class="btn btn-outline-secondary">Annuler</a> class="btn btn-outline-secondary">Annuler</a>
</div> </div>
</form> </form>
+6 -6
View File
@@ -100,11 +100,11 @@
<ul class="navbar-nav"> <ul class="navbar-nav">
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?> <?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
<li class="nav-item"><a class="nav-link" href="/?action=admin">Admin</a></li> <li class="nav-item"><a class="nav-link" href="/admin">Admin</a></li>
<?php endif; ?> <?php endif; ?>
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?> <?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/?action=profile"> <a class="nav-link" href="/profile">
<?= htmlspecialchars(function_exists('currentUserName') ? currentUserName() : (currentUserEmail() ?? '')) ?> <?= htmlspecialchars(function_exists('currentUserName') ? currentUserName() : (currentUserEmail() ?? '')) ?>
</a> </a>
</li> </li>
@@ -133,10 +133,10 @@
<small>&copy; <?= date('Y') ?> &mdash; <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">CC BY 4.0</a></small> <small>&copy; <?= date('Y') ?> &mdash; <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">CC BY 4.0</a></small>
</div> </div>
<nav class="footer-nav" aria-label="Liens du site"> <nav class="footer-nav" aria-label="Liens du site">
<a href="/?action=about">À propos</a> <a href="/about">À propos</a>
<a href="/?action=contact">Contact</a> <a href="/contact">Contact</a>
<a href="/?action=legal">Mentions légales</a> <a href="/legal">Mentions légales</a>
<a href="/?action=licenses">Licences</a> <a href="/licenses">Licences</a>
</nav> </nav>
</div> </div>
</div> </div>
+3 -3
View File
@@ -35,7 +35,7 @@ ob_start();
<div class="card-body"> <div class="card-body">
<p class="mb-1"><strong>Responsable de publication :</strong> Cédric Abonnel</p> <p class="mb-1"><strong>Responsable de publication :</strong> Cédric Abonnel</p>
<p class="mb-1"><strong>Qualité :</strong> Particulier — site personnel non commercial</p> <p class="mb-1"><strong>Qualité :</strong> Particulier — site personnel non commercial</p>
<p class="mb-0"><strong>Contact :</strong> <a href="/?action=contact">formulaire de contact</a></p> <p class="mb-0"><strong>Contact :</strong> <a href="/contact">formulaire de contact</a></p>
</div> </div>
</div> </div>
</section> </section>
@@ -74,7 +74,7 @@ ob_start();
</p> </p>
<p class="mb-0"> <p class="mb-0">
Les composants tiers (Bootstrap, PHPMailer, police Inter…) sont soumis à leurs licences respectives, Les composants tiers (Bootstrap, PHPMailer, police Inter…) sont soumis à leurs licences respectives,
détaillées sur la <a href="/?action=licenses">page des licences</a>. détaillées sur la <a href="/licenses">page des licences</a>.
</p> </p>
</div> </div>
</div> </div>
@@ -99,7 +99,7 @@ ob_start();
<p class="mb-0"> <p class="mb-0">
Conformément au RGPD (règlement UE 2016/679), vous disposez d'un droit d'accès, de rectification Conformément au RGPD (règlement UE 2016/679), vous disposez d'un droit d'accès, de rectification
et de suppression des données vous concernant. Pour exercer ces droits : et de suppression des données vous concernant. Pour exercer ces droits :
<a href="/?action=contact">formulaire de contact</a>. <a href="/contact">formulaire de contact</a>.
</p> </p>
</div> </div>
</div> </div>
+107 -96
View File
@@ -102,96 +102,6 @@ $dateValue = isset($published_at)
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if (!empty($existingFiles)): ?>
<?php $coverFile = $article['cover'] ?? ''; ?>
<?php $filesMeta = $article['files_meta'] ?? []; ?>
<div class="mb-3">
<p class="form-label fw-semibold">Fichiers existants</p>
<div class="list-group">
<?php foreach ($existingFiles as $i => $f): ?>
<?php
$fileUrl = '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($f['name']);
$fmeta = $filesMeta[$f['name']] ?? [];
$isCoverFile = ($f['name'] === $coverFile);
?>
<div class="list-group-item py-2">
<div class="d-flex align-items-center gap-3">
<!-- Miniature -->
<?php if ($f['is_image']): ?>
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener" class="flex-shrink-0">
<img src="<?= htmlspecialchars($fileUrl) ?>" alt=""
style="width:56px;height:56px;object-fit:cover;border-radius:4px;<?= $isCoverFile ? 'outline:2px solid #0d6efd' : '' ?>">
</a>
<?php else: ?>
<span style="width:56px;text-align:center;font-size:1.6rem;flex-shrink:0">
<?= match(true) {
str_starts_with($f['mime'], 'video/') => '🎬',
str_starts_with($f['mime'], 'audio/') => '🎵',
$f['mime'] === 'application/pdf' => '📑',
default => '📄',
} ?>
</span>
<?php endif; ?>
<!-- Infos + méta -->
<div class="flex-grow-1 overflow-hidden">
<div class="d-flex align-items-center gap-2 mb-1">
<code class="text-truncate small"><?= htmlspecialchars($f['name']) ?></code>
<small class="text-muted text-nowrap"><?= number_format($f['size'] / 1024, 1) ?> Ko</small>
<?php if ($isCoverFile): ?>
<span class="badge bg-primary">cover</span>
<?php endif; ?>
</div>
<?php if ($f['is_image']): ?>
<div class="d-flex gap-2 flex-wrap">
<input type="hidden" name="fmeta_name[]" value="<?= htmlspecialchars($f['name']) ?>">
<input type="text" name="fmeta_author[]"
class="form-control form-control-sm"
style="max-width:220px"
placeholder="Auteur / crédit"
value="<?= htmlspecialchars($fmeta['author'] ?? '') ?>">
<input type="url" name="fmeta_source[]"
class="form-control form-control-sm font-monospace"
style="max-width:280px"
placeholder="URL source"
value="<?= htmlspecialchars($fmeta['source_url'] ?? '') ?>">
</div>
<?php endif; ?>
</div>
<!-- Actions -->
<div class="d-flex flex-column gap-1 flex-shrink-0 align-items-end">
<?php if ($f['is_image'] && !$isCoverFile): ?>
<div class="form-check mb-0">
<input class="form-check-input" type="radio"
name="cover_file" id="cover_<?= $i ?>"
value="<?= htmlspecialchars($f['name']) ?>">
<label class="form-check-label small" for="cover_<?= $i ?>">Cover</label>
</div>
<?php elseif ($isCoverFile): ?>
<input type="hidden" name="cover_file" value="<?= htmlspecialchars($f['name']) ?>">
<small class="text-primary">✓ Cover</small>
<?php endif; ?>
<div class="d-flex gap-1">
<button type="button" class="btn btn-sm btn-outline-secondary"
data-copy-md-name="<?= htmlspecialchars($fmeta['title'] ?? $f['name']) ?>"
data-copy-md-is-image="<?= $f['is_image'] ? '1' : '0' ?>">
MD
</button>
<button type="submit" form="del-file-<?= $i ?>"
class="btn btn-sm btn-outline-danger"
data-confirm="Supprimer « <?= htmlspecialchars($f['name']) ?> » ?">
</button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if ($action === 'edit'): ?> <?php if ($action === 'edit'): ?>
</div><!-- /col-lg-8 --> </div><!-- /col-lg-8 -->
@@ -285,6 +195,91 @@ $dateValue = isset($published_at)
<hr class="my-3"> <hr class="my-3">
<?php if (!empty($existingFiles)): ?>
<?php $coverFile = $article['cover'] ?? ''; ?>
<?php $filesMeta = $article['files_meta'] ?? []; ?>
<div class="mb-3">
<p class="form-label fw-semibold small mb-2">Fichiers existants</p>
<div class="list-group">
<?php foreach ($existingFiles as $i => $f): ?>
<?php
$fileUrl = '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($f['name']);
$fmeta = $filesMeta[$f['name']] ?? [];
$isCoverFile = ($f['name'] === $coverFile);
?>
<div class="list-group-item py-2 px-2">
<div class="d-flex align-items-start gap-2">
<!-- Miniature -->
<?php if ($f['is_image']): ?>
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener" class="flex-shrink-0">
<img src="<?= htmlspecialchars($fileUrl) ?>" alt=""
style="width:48px;height:48px;object-fit:cover;border-radius:4px;<?= $isCoverFile ? 'outline:2px solid #0d6efd' : '' ?>">
</a>
<?php else: ?>
<span style="width:48px;text-align:center;font-size:1.4rem;flex-shrink:0;line-height:48px">
<?= match(true) {
str_starts_with($f['mime'], 'video/') => '🎬',
str_starts_with($f['mime'], 'audio/') => '🎵',
$f['mime'] === 'application/pdf' => '📑',
default => '📄',
} ?>
</span>
<?php endif; ?>
<!-- Infos + méta -->
<div class="flex-grow-1 overflow-hidden" style="min-width:0">
<div class="d-flex align-items-center gap-1 mb-1 flex-wrap">
<code class="text-truncate small" style="max-width:100%"><?= htmlspecialchars($f['name']) ?></code>
<small class="text-muted text-nowrap"><?= number_format($f['size'] / 1024, 1) ?> Ko</small>
<?php if ($isCoverFile): ?>
<span class="badge bg-primary">cover</span>
<?php endif; ?>
</div>
<div class="d-flex gap-1 mb-1">
<input type="hidden" name="fmeta_name[]" value="<?= htmlspecialchars($f['name']) ?>">
<input type="text" name="fmeta_author[]"
class="form-control form-control-sm"
placeholder="Auteur / crédit"
value="<?= htmlspecialchars($fmeta['author'] ?? '') ?>">
<input type="url" name="fmeta_source[]"
class="form-control form-control-sm font-monospace"
placeholder="URL source"
value="<?= htmlspecialchars($fmeta['source_url'] ?? '') ?>">
</div>
<div class="d-flex align-items-center gap-1 flex-wrap">
<?php if ($f['is_image'] && !$isCoverFile): ?>
<div class="form-check mb-0">
<input class="form-check-input" type="radio"
name="cover_file" id="cover_<?= $i ?>"
value="<?= htmlspecialchars($f['name']) ?>">
<label class="form-check-label small" for="cover_<?= $i ?>">Cover</label>
</div>
<?php elseif ($isCoverFile): ?>
<input type="hidden" name="cover_file" value="<?= htmlspecialchars($f['name']) ?>">
<small class="text-primary">✓ Cover</small>
<?php endif; ?>
<div class="d-flex gap-1 ms-auto">
<button type="button" class="btn btn-sm btn-outline-secondary"
data-copy-md-name="<?= htmlspecialchars($fmeta['title'] ?? $f['name']) ?>"
data-copy-md-is-image="<?= $f['is_image'] ? '1' : '0' ?>">
MD
</button>
<button type="submit" form="del-file-<?= $i ?>"
class="btn btn-sm btn-outline-danger"
data-confirm="Supprimer « <?= htmlspecialchars($f['name']) ?> » ?">
</button>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<hr class="my-3">
<?php endif; ?>
<?php $sidebarImages = array_filter($existingFiles ?? [], fn ($f) => $f['is_image']); ?> <?php $sidebarImages = array_filter($existingFiles ?? [], fn ($f) => $f['is_image']); ?>
<?php if ($sidebarImages): ?> <?php if ($sidebarImages): ?>
@@ -341,17 +336,17 @@ $dateValue = isset($published_at)
<?php endif; ?> <?php endif; ?>
<div class="d-flex flex-column gap-2"> <div class="d-flex flex-column gap-2">
<a href="/?action=add_files&uuid=<?= rawurlencode($uuid) ?>" class="btn btn-outline-secondary btn-sm"> <a href="/files/<?= rawurlencode($uuid) ?>/add" class="btn btn-outline-secondary btn-sm">
+ Ajouter des fichiers + Ajouter des fichiers
</a> </a>
<a href="/?action=import_image&uuid=<?= rawurlencode($uuid) ?>" class="btn btn-outline-secondary btn-sm"> <a href="/import/<?= rawurlencode($uuid) ?>" class="btn btn-outline-secondary btn-sm">
+ Importer depuis une URL + Importer depuis une URL
</a> </a>
<?php <?php
$hasSources = !empty($article['external_links']) || !empty($existingFiles); $hasSources = !empty($article['external_links']) || !empty($existingFiles);
if ($hasSources): if ($hasSources):
?> ?>
<a href="/?action=sources&uuid=<?= rawurlencode($uuid) ?>" class="btn btn-outline-secondary btn-sm"> <a href="/sources/<?= rawurlencode($uuid) ?>" class="btn btn-outline-secondary btn-sm">
Sources &amp; métadonnées Sources &amp; métadonnées
</a> </a>
<?php endif; ?> <?php endif; ?>
@@ -373,10 +368,18 @@ $dateValue = isset($published_at)
<?php if (!empty($article['revisions'])): ?> <?php if (!empty($article['revisions'])): ?>
<hr class="my-4"> <hr class="my-4">
<div> <div>
<div class="d-flex align-items-center gap-3 mb-1">
<button class="btn btn-sm btn-link text-secondary text-decoration-none p-0 fw-semibold" <button class="btn btn-sm btn-link text-secondary text-decoration-none p-0 fw-semibold"
type="button" data-bs-toggle="collapse" data-bs-target="#historyPanel"> type="button" data-bs-toggle="collapse" data-bs-target="#historyPanel">
▸ Historique des révisions (<?= count($article['revisions']) ?>) ▸ Historique des révisions (<?= count($article['revisions']) ?>)
</button> </button>
<form method="POST" action="/?action=delete_all_revisions&uuid=<?= rawurlencode($uuid) ?>" class="d-inline ms-auto">
<button type="submit" class="btn btn-sm btn-outline-danger"
data-confirm="Supprimer tout l'historique des révisions ?">
Tout supprimer
</button>
</form>
</div>
<div class="collapse mt-3" id="historyPanel"> <div class="collapse mt-3" id="historyPanel">
<table class="table table-sm table-hover align-middle"> <table class="table table-sm table-hover align-middle">
<thead> <thead>
@@ -384,8 +387,9 @@ $dateValue = isset($published_at)
</thead> </thead>
<tbody> <tbody>
<?php foreach (array_reverse($article['revisions']) as $rev): ?> <?php foreach (array_reverse($article['revisions']) as $rev): ?>
<?php $revN = (int)($rev['n'] ?? 0); ?>
<tr> <tr>
<td class="text-muted small"><?= (int)($rev['n'] ?? 0) ?></td> <td class="text-muted small"><?= $revN ?></td>
<td class="small text-nowrap"> <td class="small text-nowrap">
<?= htmlspecialchars(date('d/m/Y H:i', strtotime((string)($rev['date'] ?? '')))) ?> <?= htmlspecialchars(date('d/m/Y H:i', strtotime((string)($rev['date'] ?? '')))) ?>
</td> </td>
@@ -395,11 +399,18 @@ $dateValue = isset($published_at)
<td class="small text-muted"> <td class="small text-muted">
<?= htmlspecialchars($rev['comment'] ?? '') ?: '<span class="text-muted"></span>' ?> <?= htmlspecialchars($rev['comment'] ?? '') ?: '<span class="text-muted"></span>' ?>
</td> </td>
<td> <td class="text-nowrap">
<a href="/?action=diff&uuid=<?= rawurlencode($uuid) ?>&rev=<?= (int)($rev['n'] ?? 0) ?>" <a href="/diff/<?= rawurlencode($uuid) ?>/<?= $revN ?>"
class="btn btn-outline-secondary btn-sm" target="_blank"> class="btn btn-outline-secondary btn-sm" target="_blank">
Diff Diff
</a> </a>
<form method="POST" action="/?action=delete_revision&uuid=<?= rawurlencode($uuid) ?>" class="d-inline">
<input type="hidden" name="rev_n" value="<?= $revN ?>">
<button type="submit" class="btn btn-sm btn-outline-danger"
data-confirm="Supprimer la révision #<?= $revN ?> ?">
</button>
</form>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
+2 -2
View File
@@ -54,10 +54,10 @@ ob_start();
<?php if ($isAvantPremiere): ?> <?php if ($isAvantPremiere): ?>
<span class="text-muted">Disponible le <?= htmlspecialchars(date('d/m/Y \à H\hi', strtotime((string)($post['published_at'] ?? '')))) ?></span> <span class="text-muted">Disponible le <?= htmlspecialchars(date('d/m/Y \à H\hi', strtotime((string)($post['published_at'] ?? '')))) ?></span>
<?php else: ?> <?php else: ?>
<span><?= htmlspecialchars(date('d/m/Y', strtotime((string)($post['created_at'] ?? '')))) ?></span> <span><?= htmlspecialchars(date('d/m/Y', strtotime((string)($post['published_at'] ?? $post['created_at'] ?? '')))) ?></span>
<?php endif; ?> <?php endif; ?>
<?php if (function_exists('isAdmin') && isAdmin()): ?> <?php if (function_exists('isAdmin') && isAdmin()): ?>
<a href="/?action=edit&uuid=<?= htmlspecialchars($post['uuid']) ?>" class="post-entry-edit">modifier</a> <a href="/edit/<?= htmlspecialchars($post['uuid']) ?>" class="post-entry-edit">modifier</a>
<?php endif; ?> <?php endif; ?>
<?php if (!$isLocked): ?> <?php if (!$isLocked): ?>
<a href="<?= htmlspecialchars($postUrl) ?>" class="post-entry-read">→ lire</a> <a href="<?= htmlspecialchars($postUrl) ?>" class="post-entry-read">→ lire</a>
+17 -5
View File
@@ -32,7 +32,7 @@ if ($files) {
$externalLinks = $article['external_links'] ?? []; $externalLinks = $article['external_links'] ?? [];
$hasLeftSidebar = !empty($categorySidebar ?? []); $hasLeftSidebar = !empty($categorySidebar ?? []);
?> ?>
<div class="row g-4 align-items-start flex-nowrap-lg"> <div class="row g-4 align-items-start flex-lg-nowrap">
<?php if ($hasLeftSidebar): ?> <?php if ($hasLeftSidebar): ?>
<div class="post-sidebar-col order-2 order-lg-1"> <div class="post-sidebar-col order-2 order-lg-1">
@@ -87,8 +87,8 @@ $hasSources = (!empty($externalLinks) || !empty($files))
<div class="article-hero-top"> <div class="article-hero-top">
<a href="/" class="hero-btn">← Retour</a> <a href="/" class="hero-btn">← Retour</a>
<?php if (function_exists('isAdmin') && isAdmin()): ?> <?php if (function_exists('isAdmin') && isAdmin()): ?>
<a href="/?action=edit&uuid=<?= rawurlencode($article['uuid']) ?>" class="hero-btn ms-auto">✎ Modifier</a> <a href="/edit/<?= rawurlencode($article['uuid']) ?>" class="hero-btn ms-auto">✎ Modifier</a>
<a href="/?action=delete&uuid=<?= rawurlencode($article['uuid']) ?>" <a href="/delete/<?= rawurlencode($article['uuid']) ?>"
class="hero-btn hero-btn--danger" class="hero-btn hero-btn--danger"
data-confirm="Supprimer cet article définitivement ?">🗑 Supprimer</a> data-confirm="Supprimer cet article définitivement ?">🗑 Supprimer</a>
<?php endif; ?> <?php endif; ?>
@@ -111,7 +111,7 @@ $hasSources = (!empty($externalLinks) || !empty($files))
</div> </div>
<div class="article-hero-right"> <div class="article-hero-right">
<?php if ($hasSources): ?> <?php if ($hasSources): ?>
<a href="/?action=sources&uuid=<?= rawurlencode($article['uuid']) ?>" class="hero-btn"> Sources</a> <a href="/sources/<?= rawurlencode($article['uuid']) ?>" class="hero-btn"> Sources</a>
<?php endif; ?> <?php endif; ?>
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?> <?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
<form method="post" action="/?action=rate" class="d-flex align-items-center gap-2"> <form method="post" action="/?action=rate" class="d-flex align-items-center gap-2">
@@ -162,10 +162,14 @@ $hasSources = (!empty($externalLinks) || !empty($files))
<aside class="related-sidebar"> <aside class="related-sidebar">
<?php if (!empty($attachments)): ?> <?php if (!empty($attachments)): ?>
<?php $filesMeta = $article['files_meta'] ?? []; ?>
<h6 class="related-sidebar-title">Pièces jointes</h6> <h6 class="related-sidebar-title">Pièces jointes</h6>
<div class="d-flex flex-column gap-2 mb-4"> <div class="d-flex flex-column gap-2 mb-4">
<?php foreach ($attachments as $file): <?php foreach ($attachments as $file):
$fileUrl = '/file?uuid=' . rawurlencode($article['uuid']) . '&name=' . rawurlencode($file['name']); $fileUrl = '/file?uuid=' . rawurlencode($article['uuid']) . '&name=' . rawurlencode($file['name']);
$fmeta = $filesMeta[$file['name']] ?? [];
$fAuthor = trim($fmeta['author'] ?? '');
$fSource = trim($fmeta['source_url'] ?? '');
?> ?>
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener" class="source-card"> <a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener" class="source-card">
<?php if ($file['is_image']): ?> <?php if ($file['is_image']): ?>
@@ -179,7 +183,15 @@ $hasSources = (!empty($externalLinks) || !empty($files))
<?php endif; ?> <?php endif; ?>
<div class="source-card-body"> <div class="source-card-body">
<div class="source-card-title"><?= htmlspecialchars($file['name']) ?></div> <div class="source-card-title"><?= htmlspecialchars($file['name']) ?></div>
<div class="source-card-meta"><?= htmlspecialchars(number_format($file['size'] / 1024, 1)) ?> Ko</div> <div class="source-card-meta">
<?= htmlspecialchars(number_format($file['size'] / 1024, 1)) ?> Ko
<?php if ($fAuthor !== ''): ?>
· <?= htmlspecialchars($fAuthor) ?>
<?php endif; ?>
<?php if ($fSource !== ''): ?>
· <?= htmlspecialchars(parse_url($fSource, PHP_URL_HOST) ?: $fSource) ?>
<?php endif; ?>
</div>
</div> </div>
</a> </a>
<?php endforeach; ?> <?php endforeach; ?>
+1 -1
View File
@@ -15,7 +15,7 @@
<div class="alert alert-danger py-2 small mb-3"><?= htmlspecialchars($profileError) ?></div> <div class="alert alert-danger py-2 small mb-3"><?= htmlspecialchars($profileError) ?></div>
<?php endif; ?> <?php endif; ?>
<form method="post" action="/?action=profile"> <form method="post" action="/profile">
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-semibold" for="display_name">Nom affiché</label> <label class="form-label fw-semibold" for="display_name">Nom affiché</label>
<input type="text" id="display_name" name="display_name" <input type="text" id="display_name" name="display_name"
+1 -1
View File
@@ -38,7 +38,7 @@ function renderMetaCell(string $key, mixed $val, array $row = []): string
?> ?>
<div class="d-flex align-items-center gap-3 mb-1"> <div class="d-flex align-items-center gap-3 mb-1">
<a href="/?action=edit&uuid=<?= rawurlencode($article['uuid']) ?>" class="btn btn-secondary btn-sm">← Modifier</a> <a href="/edit/<?= rawurlencode($article['uuid']) ?>" class="btn btn-secondary btn-sm">← Modifier</a>
<h1 class="h4 mb-0">Sources &amp; médias</h1> <h1 class="h4 mb-0">Sources &amp; médias</h1>
</div> </div>
<p class="text-muted small mb-4"><?= htmlspecialchars($article['title']) ?></p> <p class="text-muted small mb-4"><?= htmlspecialchars($article['title']) ?></p>