uploadDir = __DIR__ . '/../../assets/images/chapters/' . $storyId . '/'; $this->ensureUploadDirectory(); } public function handleUpload($file) { try { // Vérifications de base if ($file['error'] !== UPLOAD_ERR_OK) { throw new Exception($this->getUploadErrorMessage($file['error'])); } // Vérification du 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, WEBP'); } // Vérification de la taille if ($file['size'] > $this->maxFileSize) { throw new Exception('Fichier trop volumineux. Taille maximum : 5MB'); } // Vérification et redimensionnement de l'image [$width, $height, $type] = getimagesize($file['tmp_name']); $needsResize = $width > $this->maxWidth || $height > $this->maxHeight; // Génération d'un nom de fichier unique $extension = $this->getExtensionFromMimeType($mimeType); $filename = uniqid() . '.' . $extension; $targetPath = $this->uploadDir . $filename; if ($needsResize) { // Calcul des nouvelles dimensions en conservant le ratio $ratio = min($this->maxWidth / $width, $this->maxHeight / $height); $newWidth = round($width * $ratio); $newHeight = round($height * $ratio); // Création de la nouvelle image $sourceImage = $this->createImageFromFile($file['tmp_name'], $mimeType); $newImage = imagecreatetruecolor($newWidth, $newHeight); // Préservation de la transparence pour PNG if ($mimeType === 'image/png') { imagealphablending($newImage, false); imagesavealpha($newImage, true); } // Redimensionnement imagecopyresampled( $newImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height ); // Sauvegarde de l'image redimensionnée $this->saveImage($newImage, $targetPath, $mimeType); // Libération de la mémoire imagedestroy($sourceImage); imagedestroy($newImage); } else { // Déplacement du fichier original si pas besoin de redimensionnement if (!move_uploaded_file($file['tmp_name'], $targetPath)) { throw new Exception('Erreur lors du déplacement du fichier uploadé'); } } // Retourner le chemin relatif pour l'éditeur return [ 'success' => true, 'url' => $this->getRelativePath($targetPath), 'width' => $needsResize ? $newWidth : $width, 'height' => $needsResize ? $newHeight : $height ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } 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 createImageFromFile($file, $mimeType) { switch ($mimeType) { case 'image/jpeg': return imagecreatefromjpeg($file); case 'image/png': return imagecreatefrompng($file); case 'image/gif': return imagecreatefromgif($file); case 'image/webp': return imagecreatefromwebp($file); default: throw new Exception('Type d\'image non supporté'); } } private function saveImage($image, $path, $mimeType) { switch ($mimeType) { case 'image/jpeg': return imagejpeg($image, $path, 85); // Qualité 85% case 'image/png': return imagepng($image, $path, 8); // Compression 8 case 'image/gif': return imagegif($image, $path); case 'image/webp': return imagewebp($image, $path, 85); // Qualité 85% default: throw new Exception('Type d\'image non supporté pour la sauvegarde'); } } private function getExtensionFromMimeType($mimeType) { $map = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp' ]; return $map[$mimeType] ?? 'jpg'; } private function getRelativePath($absolutePath) { $relativePath = str_replace(__DIR__ . '/../../', '', $absolutePath); // Ajout de '../' car on est dans admin/api/ return '../' . str_replace('\\', '/', $relativePath); // Pour la compatibilité Windows } 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'; } } // Point d'entrée du script if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); exit(json_encode(['error' => 'Méthode non autorisée'])); } // Vérification de l'authentification if (!Auth::check()) { http_response_code(401); exit(json_encode(['error' => 'Non autorisé'])); } // Récupération de l'ID du roman $storyId = $_POST['storyId'] ?? null; if (!$storyId) { http_response_code(400); exit(json_encode(['error' => 'ID du roman manquant'])); } // Traitement de l'upload try { $handler = new ImageUploadHandler($storyId); $result = $handler->handleUpload($_FILES['image']); if (!$result['success']) { http_response_code(400); } header('Content-Type: application/json'); echo json_encode($result); } catch (Exception $e) { http_response_code(500); echo json_encode([ 'success' => false, 'error' => 'Erreur serveur : ' . $e->getMessage() ]); }