dbd76556fb
Ajoute un concept de "livre" (série de pages ordonnées) avec : - BookManager : CRUD JSON dans data/books/<slug>.json - Route /book/<slug> → page de sommaire (table des matières) - Navigation chapitre ← → en bas de chaque article membre du livre - Bandeau "Chapitre X/N — Nom du livre" en haut de l'article - Admin → onglet Livres : créer, éditer, supprimer un livre, ajouter/ordonner les pages via textarea slug Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
132 lines
4.0 KiB
PHP
132 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
class BookManager
|
|
{
|
|
public function __construct(private string $booksDir)
|
|
{
|
|
}
|
|
|
|
// ------------------------------------------------------------------ //
|
|
// Lecture
|
|
// ------------------------------------------------------------------ //
|
|
|
|
public function getAll(): array
|
|
{
|
|
$books = [];
|
|
if (!is_dir($this->booksDir)) {
|
|
return $books;
|
|
}
|
|
foreach (scandir($this->booksDir) as $file) {
|
|
if (!str_ends_with($file, '.json')) {
|
|
continue;
|
|
}
|
|
$raw = file_get_contents($this->booksDir . '/' . $file);
|
|
if ($raw === false) {
|
|
continue;
|
|
}
|
|
$book = json_decode($raw, true);
|
|
if (!is_array($book) || empty($book['slug'])) {
|
|
continue;
|
|
}
|
|
$books[] = $book;
|
|
}
|
|
usort($books, static fn ($a, $b) => strcmp($a['title'] ?? '', $b['title'] ?? ''));
|
|
return $books;
|
|
}
|
|
|
|
public function getBySlug(string $slug): ?array
|
|
{
|
|
$path = $this->bookPath($slug);
|
|
if (!file_exists($path)) {
|
|
return null;
|
|
}
|
|
$raw = file_get_contents($path);
|
|
if ($raw === false) {
|
|
return null;
|
|
}
|
|
$book = json_decode($raw, true);
|
|
return is_array($book) && !empty($book['slug']) ? $book : null;
|
|
}
|
|
|
|
/**
|
|
* Cherche dans quel livre se trouve un article (par son slug).
|
|
* Retourne le contexte complet ou null si l'article n'appartient à aucun livre.
|
|
*
|
|
* @return array{book: array, position: int, total: int, prev: ?string, next: ?string}|null
|
|
*/
|
|
public function findForArticle(string $articleSlug): ?array
|
|
{
|
|
foreach ($this->getAll() as $book) {
|
|
$arts = $book['articles'] ?? [];
|
|
$pos = array_search($articleSlug, $arts, true);
|
|
if ($pos === false) {
|
|
continue;
|
|
}
|
|
$pos = (int) $pos;
|
|
return [
|
|
'book' => $book,
|
|
'position' => $pos + 1,
|
|
'total' => count($arts),
|
|
'prev' => $pos > 0 ? $arts[$pos - 1] : null,
|
|
'next' => $pos < count($arts) - 1 ? $arts[$pos + 1] : null,
|
|
];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ------------------------------------------------------------------ //
|
|
// Écriture
|
|
// ------------------------------------------------------------------ //
|
|
|
|
public function save(array $book): void
|
|
{
|
|
$slug = $this->sanitizeSlug($book['slug'] ?? '');
|
|
if ($slug === '') {
|
|
return;
|
|
}
|
|
$book['slug'] = $slug;
|
|
$book['articles'] = array_values(array_filter(array_map('strval', $book['articles'] ?? [])));
|
|
if (!is_dir($this->booksDir)) {
|
|
mkdir($this->booksDir, 0755, true);
|
|
}
|
|
file_put_contents(
|
|
$this->bookPath($slug),
|
|
json_encode($book, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"
|
|
);
|
|
}
|
|
|
|
public function delete(string $slug): void
|
|
{
|
|
$path = $this->bookPath($slug);
|
|
if (file_exists($path)) {
|
|
@unlink($path);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------ //
|
|
// Helpers
|
|
// ------------------------------------------------------------------ //
|
|
|
|
private function bookPath(string $slug): string
|
|
{
|
|
return $this->booksDir . '/' . $slug . '.json';
|
|
}
|
|
|
|
private function sanitizeSlug(string $slug): string
|
|
{
|
|
$map = [
|
|
'à' => 'a', 'â' => 'a', 'ä' => 'a',
|
|
'é' => 'e', 'è' => 'e', 'ê' => 'e', 'ë' => 'e',
|
|
'î' => 'i', 'ï' => 'i',
|
|
'ô' => 'o', 'ö' => 'o',
|
|
'ù' => 'u', 'û' => 'u', 'ü' => 'u',
|
|
'ç' => 'c', 'æ' => 'ae', 'œ' => 'oe',
|
|
];
|
|
$slug = mb_strtolower(strtr(trim($slug), $map), 'UTF-8');
|
|
$slug = (string) preg_replace('/[^a-z0-9]+/', '-', $slug);
|
|
return trim($slug, '-');
|
|
}
|
|
}
|