diff --git a/admin/index.php b/admin/index.php index 154e949..59105cd 100644 --- a/admin/index.php +++ b/admin/index.php @@ -36,7 +36,7 @@ $stories = Stories::getAll(); <div class="stories-list"> <?php foreach ($stories as $story): ?> <div class="story-item"> - <img src="<?= htmlspecialchars($story['cover']) ?>" alt="" class="story-cover"> + <img src="<?= htmlspecialchars('../' . $story['cover']) ?>" alt="" class="story-cover"> <div class="story-info"> <h2><?= htmlspecialchars($story['title']) ?></h2> <p>Dernière modification : <?= htmlspecialchars($story['updated']) ?></p> diff --git a/admin/story-edit.php b/admin/story-edit.php index df9f88d..f1670c6 100644 --- a/admin/story-edit.php +++ b/admin/story-edit.php @@ -2,6 +2,7 @@ require_once '../includes/config.php'; require_once '../includes/auth.php'; require_once '../includes/stories.php'; +require_once 'upload-handler.php'; if (!Auth::check()) { header('Location: login.php'); @@ -28,21 +29,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 'id' => $_POST['id'] ?? generateSlug($_POST['title']), 'title' => $_POST['title'], 'description' => $_POST['description'], - 'cover' => $story['cover'] ?? '', // Géré séparément par l'upload + 'cover' => $story['cover'] ?? '', 'created' => $story['created'] ?? date('Y-m-d'), 'updated' => date('Y-m-d'), 'chapters' => $story['chapters'] ?? [] ]; // Gestion de l'upload de couverture - if (isset($_FILES['cover']) && $_FILES['cover']['error'] === UPLOAD_ERR_OK) { - $uploadDir = '../assets/images/covers/'; - $extension = strtolower(pathinfo($_FILES['cover']['name'], PATHINFO_EXTENSION)); - $filename = $storyData['id'] . '.' . $extension; - - if (move_uploaded_file($_FILES['cover']['tmp_name'], $uploadDir . $filename)) { - $storyData['cover'] = 'assets/images/covers/' . $filename; - } + if (isset($_FILES['cover']) && $_FILES['cover']['error'] !== UPLOAD_ERR_NO_FILE) { + $uploadHandler = new CoverUploadHandler(); + $storyData['cover'] = $uploadHandler->handleUpload($_FILES['cover'], $storyData['id']); } Stories::save($storyData); diff --git a/admin/upload-handler.php b/admin/upload-handler.php new file mode 100644 index 0000000..451ed10 --- /dev/null +++ b/admin/upload-handler.php @@ -0,0 +1,92 @@ +<?php +class CoverUploadHandler { + private $uploadDir; + private $allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; + private $maxFileSize = 5242880; // 5MB + + public function __construct() { + $this->uploadDir = __DIR__ . '/../assets/images/covers/'; + $this->ensureUploadDirectory(); + } + + public function handleUpload($file, $storyId) { + try { + // Vérifications basiques + if ($file['error'] !== UPLOAD_ERR_OK) { + throw new Exception($this->getUploadErrorMessage($file['error'])); + } + + // Vérifier le type MIME + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mimeType = $finfo->file($file['tmp_name']); + if (!in_array($mimeType, $this->allowedTypes)) { + throw new Exception('Type de fichier non autorisé. Types acceptés : JPG, PNG, GIF'); + } + + // Vérifier la taille + if ($file['size'] > $this->maxFileSize) { + throw new Exception('Fichier trop volumineux. Taille maximum : 5MB'); + } + + // Générer le nom de fichier + $extension = $this->getExtensionFromMimeType($mimeType); + $filename = $storyId . '.' . $extension; + $targetPath = $this->uploadDir . $filename; + + // Supprimer l'ancienne image si elle existe + $this->removeOldCover($storyId); + + // Déplacer le fichier + if (!move_uploaded_file($file['tmp_name'], $targetPath)) { + throw new Exception('Erreur lors du déplacement du fichier uploadé'); + } + + // Retourner le chemin relatif pour stockage en BDD + return 'assets/images/covers/' . $filename; + + } catch (Exception $e) { + error_log('Erreur upload couverture: ' . $e->getMessage()); + throw $e; + } + } + + private function ensureUploadDirectory() { + if (!file_exists($this->uploadDir)) { + if (!mkdir($this->uploadDir, 0755, true)) { + throw new Exception('Impossible de créer le dossier d\'upload'); + } + } + + if (!is_writable($this->uploadDir)) { + throw new Exception('Le dossier d\'upload n\'est pas accessible en écriture'); + } + } + + private function removeOldCover($storyId) { + foreach (glob($this->uploadDir . $storyId . '.*') as $file) { + unlink($file); + } + } + + private function getExtensionFromMimeType($mimeType) { + $map = [ + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif' + ]; + return $map[$mimeType] ?? 'jpg'; + } + + private function getUploadErrorMessage($error) { + $errors = [ + UPLOAD_ERR_INI_SIZE => 'Le fichier dépasse la taille maximale autorisée par PHP', + UPLOAD_ERR_FORM_SIZE => 'Le fichier dépasse la taille maximale autorisée par le formulaire', + UPLOAD_ERR_PARTIAL => 'Le fichier n\'a été que partiellement uploadé', + UPLOAD_ERR_NO_FILE => 'Aucun fichier n\'a été uploadé', + UPLOAD_ERR_NO_TMP_DIR => 'Dossier temporaire manquant', + UPLOAD_ERR_CANT_WRITE => 'Échec de l\'écriture du fichier sur le disque', + UPLOAD_ERR_EXTENSION => 'Une extension PHP a arrêté l\'upload' + ]; + return $errors[$error] ?? 'Erreur inconnue lors de l\'upload'; + } +} \ No newline at end of file