feat: métadonnées étendues (author, revisions), migration BDD → fichiers
This commit is contained in:
+39
-68
@@ -23,13 +23,12 @@ class ArticleManager
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
$dir = $this->dataDir . '/' . $entry;
|
||||
$file = $dir . '/index.md';
|
||||
if (!is_dir($dir) || !file_exists($file)) {
|
||||
$dir = $this->dataDir . '/' . $entry;
|
||||
if (!is_dir($dir) || !file_exists($dir . '/meta.json')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$article = $this->parseFile($file);
|
||||
$article = $this->loadArticle($dir);
|
||||
if (!$article) {
|
||||
continue;
|
||||
}
|
||||
@@ -59,18 +58,18 @@ class ArticleManager
|
||||
if (!$this->isValidUuid($uuid)) {
|
||||
return null;
|
||||
}
|
||||
$file = $this->dataDir . '/' . $uuid . '/index.md';
|
||||
if (!file_exists($file)) {
|
||||
$dir = $this->dataDir . '/' . $uuid;
|
||||
if (!is_dir($dir) || !file_exists($dir . '/meta.json')) {
|
||||
return null;
|
||||
}
|
||||
return $this->parseFile($file);
|
||||
return $this->loadArticle($dir);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Écriture
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
public function create(string $title, string $content, bool $published, string $slug = '', string $publishedAt = ''): string
|
||||
public function create(string $title, string $content, bool $published, string $slug = '', string $publishedAt = '', string $author = ''): string
|
||||
{
|
||||
$uuid = $this->generateUuid();
|
||||
$slug = $slug !== '' ? $this->sanitizeSlug($slug) : $this->generateSlug($title);
|
||||
@@ -86,36 +85,47 @@ class ArticleManager
|
||||
'uuid' => $uuid,
|
||||
'slug' => $slug,
|
||||
'title' => $title,
|
||||
'author' => $author,
|
||||
'published' => $published,
|
||||
'published_at' => $publishedAt,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
'revisions' => [],
|
||||
];
|
||||
file_put_contents($dir . '/index.md', $this->writeFrontmatter($meta, $content));
|
||||
$this->writeMeta($dir, $meta);
|
||||
file_put_contents($dir . '/index.md', ltrim($content));
|
||||
|
||||
return $uuid;
|
||||
}
|
||||
|
||||
public function update(string $uuid, string $title, string $content, bool $published, string $slug, string $publishedAt): void
|
||||
public function update(string $uuid, string $title, string $content, bool $published, string $slug, string $publishedAt, string $revisionComment = ''): void
|
||||
{
|
||||
$article = $this->getByUuid($uuid);
|
||||
if (!$article) {
|
||||
return;
|
||||
}
|
||||
|
||||
$slug = $slug !== '' ? $this->sanitizeSlug($slug) : $this->generateSlug($title);
|
||||
$slug = $this->uniqueSlug($slug, $uuid);
|
||||
$slug = $slug !== '' ? $this->sanitizeSlug($slug) : $this->generateSlug($title);
|
||||
$slug = $this->uniqueSlug($slug, $uuid);
|
||||
$revisions = $article['revisions'] ?? [];
|
||||
if ($revisionComment !== '') {
|
||||
$revisions[] = ['date' => date('Y-m-d H:i:s'), 'comment' => $revisionComment];
|
||||
}
|
||||
|
||||
$meta = [
|
||||
'uuid' => $uuid,
|
||||
'slug' => $slug,
|
||||
'title' => $title,
|
||||
'author' => $article['author'] ?? '',
|
||||
'published' => $published,
|
||||
'published_at' => $publishedAt !== '' ? $publishedAt : ($article['published_at'] ?? date('Y-m-d H:i:s')),
|
||||
'created_at' => $article['created_at'] ?? date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'revisions' => $revisions,
|
||||
];
|
||||
file_put_contents($this->dataDir . '/' . $uuid . '/index.md', $this->writeFrontmatter($meta, $content));
|
||||
$dir = $this->dataDir . '/' . $uuid;
|
||||
$this->writeMeta($dir, $meta);
|
||||
file_put_contents($dir . '/index.md', ltrim($content));
|
||||
}
|
||||
|
||||
public function delete(string $uuid): void
|
||||
@@ -149,7 +159,7 @@ class ArticleManager
|
||||
if (!is_file($path)) {
|
||||
continue;
|
||||
}
|
||||
$mime = mime_content_type($path) ?: 'application/octet-stream';
|
||||
$mime = mime_content_type($path) ?: 'application/octet-stream';
|
||||
$files[] = [
|
||||
'name' => $name,
|
||||
'size' => filesize($path),
|
||||
@@ -196,7 +206,6 @@ class ArticleManager
|
||||
{
|
||||
$base = '/file?uuid=' . rawurlencode($uuid) . '&name=';
|
||||
|
||||
//  et [texte](fichier.ext) sans http/https ni /
|
||||
return preg_replace_callback(
|
||||
'/(!?\[([^\]]*)\])\((?!https?:\/\/)(?!\/)([^)]+)\)/',
|
||||
static function (array $m) use ($base): string {
|
||||
@@ -210,70 +219,32 @@ class ArticleManager
|
||||
// Helpers privés
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
private function parseFile(string $path): ?array
|
||||
private function loadArticle(string $dir): ?array
|
||||
{
|
||||
$raw = file_get_contents($path);
|
||||
$metaPath = $dir . '/meta.json';
|
||||
$contentPath = $dir . '/index.md';
|
||||
|
||||
$raw = file_get_contents($metaPath);
|
||||
if ($raw === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
['meta' => $meta, 'content' => $content] = $this->parseFrontmatter($raw);
|
||||
if (empty($meta['uuid'])) {
|
||||
$meta = json_decode($raw, true);
|
||||
if (!is_array($meta) || empty($meta['uuid'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$meta['content'] = $content;
|
||||
$meta['published'] = filter_var($meta['published'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$meta['content'] = file_exists($contentPath) ? (string)file_get_contents($contentPath) : '';
|
||||
$meta['published'] = (bool)($meta['published'] ?? false);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function parseFrontmatter(string $raw): array
|
||||
private function writeMeta(string $dir, array $meta): void
|
||||
{
|
||||
if (!str_starts_with($raw, '---')) {
|
||||
return ['meta' => [], 'content' => $raw];
|
||||
}
|
||||
$end = strpos($raw, "\n---", 3);
|
||||
if ($end === false) {
|
||||
return ['meta' => [], 'content' => $raw];
|
||||
}
|
||||
|
||||
$yaml = substr($raw, 4, $end - 4);
|
||||
$content = ltrim(substr($raw, $end + 4));
|
||||
$meta = [];
|
||||
|
||||
foreach (explode("\n", $yaml) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
$colon = strpos($line, ':');
|
||||
if ($colon === false) {
|
||||
continue;
|
||||
}
|
||||
$key = trim(substr($line, 0, $colon));
|
||||
$val = trim(substr($line, $colon + 1));
|
||||
if ($val === 'true') {
|
||||
$val = true;
|
||||
} elseif ($val === 'false') {
|
||||
$val = false;
|
||||
}
|
||||
$meta[$key] = $val;
|
||||
}
|
||||
|
||||
return ['meta' => $meta, 'content' => $content];
|
||||
}
|
||||
|
||||
private function writeFrontmatter(array $meta, string $content): string
|
||||
{
|
||||
$yaml = '';
|
||||
foreach ($meta as $key => $val) {
|
||||
if (is_bool($val)) {
|
||||
$val = $val ? 'true' : 'false';
|
||||
}
|
||||
$yaml .= $key . ': ' . $val . "\n";
|
||||
}
|
||||
return "---\n" . $yaml . "---\n\n" . ltrim($content);
|
||||
file_put_contents(
|
||||
$dir . '/meta.json',
|
||||
json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"
|
||||
);
|
||||
}
|
||||
|
||||
private function generateSlug(string $title): string
|
||||
@@ -289,7 +260,7 @@ class ArticleManager
|
||||
$slug = mb_strtolower($title, 'UTF-8');
|
||||
$slug = strtr($slug, $map);
|
||||
$slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
|
||||
return trim((string)$slug, '-');
|
||||
return trim((string)$slug, '-') ?: 'article';
|
||||
}
|
||||
|
||||
private function sanitizeSlug(string $slug): string
|
||||
|
||||
Reference in New Issue
Block a user