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, '-'); } }