readImageBlob($image_data); $imagick->stripImage(); // nettoyage métadonnées //$imagick->setImageFormat($format); // inutile, writeImage force la conversion // compression switch($format){ case 'jpeg': // particularité du switch, si 'jpeg' le test de 'jpg' est ignoré et on va jusqu'au break case 'jpg': $imagick->setImageCompression(Imagick::COMPRESSION_JPEG); $imagick->setImageCompressionQuality(85); break; case 'webp': $imagick->setImageCompression(Imagick::COMPRESSION_WEBP); $imagick->setImageCompressionQuality(85); break; case 'png': $imagick->setImageCompression(Imagick::COMPRESSION_ZIP); $imagick->setImageCompressionQuality(7); // 9 est sans perte break; case 'tiff': $imagick->setImageCompression(Imagick::COMPRESSION_LZW); // LZW est sans perte break; } // enregistrement // writeImage utilise l'extension du fichier et ignore le format détecté // imagemagick est à l'origine une appli console, elle considère que l'extension montre l'intention de l'utilisateur $imagick->writeImage($local_path); $imagick->clear(); $imagick->destroy(); return true; } catch(Exception $e){ return false; } } function curlDownloadImage(string $url, int $maxRetries = 3, int $timeout = 10): string|false { $attempt = 0; $imageData = false; while($attempt < $maxRetries){ $ch = curl_init($url); // instance de CurlHandle curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_USERAGENT, 'TinyMCE-Image-Downloader'); $imageData = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); //$curlError = curl_error($ch); curl_close($ch); if($imageData !== false && $httpCode >= 200 && $httpCode < 300){ return $imageData; } $attempt++; sleep(1); } return false; // échec après trois tentatives } function sanitizeFileName(string $filename): string { $filename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $filename); // ne garder que les lettres, chiffres, tirets et underscores $filename = preg_replace('/_+/', '_', $filename); // doublons d'underscores return trim($filename, '_'); } function checkFileDownload(array $file): bool { $extensions_white_list = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp']; $mime_type_white_list = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation']; // 1/ extension $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if(!in_array($extension, $extensions_white_list, true)){ return false; } // 2/ fichier obtenu par HTTP POST (théoriquement inutile si le routeur est solide, mais ça ne mange pas de pain) if(!is_uploaded_file($file['tmp_name'])){ return false; } $finfo = finfo_open(FILEINFO_MIME_TYPE); // 3/ objet $finfo valide (dépend du paramètre FILEINFO_MIME_TYPE) if($finfo === false){ return false; } $real_type = finfo_file($finfo, $file['tmp_name']); finfo_close($finfo); // 4/ contrôle du "vrai" type mime (finfo_file lit les 1ers octets des fichiers pour y trouver des "signatures", très fiable sauf avec les conteneurs: doc, zip...) return in_array($real_type, $mime_type_white_list, true); } if(isset($_GET['action']) && $_GET['action'] == 'editor_submit'){ // récupération des données $data = file_get_contents('php://input'); $json = json_decode($data, true); if(json_last_error() === JSON_ERROR_NONE) { // Traitement côté serveur $articleId = $json['id']; $content = $json['content']; // retour au client echo json_encode(['success' => true]); } else{ echo json_encode(['success' => false, 'message' => 'Erreur de décodage JSON']); } die; } elseif(isset($_GET['action']) && $_GET['action'] == 'delete_article'){ // récupération des données $post = json_decode(file_get_contents('php://input'), true); if(json_last_error() === JSON_ERROR_NONE){ // Traitement côté serveur $success = true; // retour au client if($success) { echo json_encode(['success' => true]); } else { http_response_code(500); echo json_encode(['success' => false, 'message' => 'Erreur lors de la suppression de l\'article.']); } } die; } elseif(isset($_GET['action']) && in_array($_GET['action'], ['upload_image', 'upload_image_url', 'upload_image_base64'])){ $dest = 'images/'; if(!is_dir($dest)){ mkdir($dest, 0755, true); } $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; if($_GET['action'] == 'upload_image'){ if(!isset($_FILES['file'])){ http_response_code(400); echo json_encode(['message' => 'Erreur 400: Bad Request']); die; } if(!is_uploaded_file($_FILES['file']['tmp_name'])) { http_response_code(500); echo json_encode(['message' => "Le fichier n'a pas été téléchargé correctement."]); die; } $name = sanitizeFileName(pathinfo($_FILES['file']['name'], PATHINFO_FILENAME)); $extension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)); $image_data = file_get_contents($_FILES['file']['tmp_name']); } elseif($_GET['action'] == 'upload_image_url'){ $json = json_decode(file_get_contents('php://input'), true); if(!isset($json['image_url'])){ http_response_code(400); echo json_encode(['message' => 'Erreur 400: Bad Request']); die; } $image_data = curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents if($image_data === false){ http_response_code(400); echo json_encode(['message' => "Erreur, le serveur n'a pas réussi à télécharger l'image."]); die; } $url_path = parse_url($json['image_url'], PHP_URL_PATH); $name = sanitizeFileName(pathinfo($url_path, PATHINFO_FILENAME)); $extension = strtolower(pathinfo($url_path, PATHINFO_EXTENSION)); } elseif($_GET['action'] == 'upload_image_base64'){ $json = json_decode(file_get_contents('php://input'), true); // détection de data:image/ et de ;base64, et capture du format dans $type if(!isset($json['image_base64']) || !preg_match('/^data:image\/(\w+);base64,/', $json['image_base64'], $type)){ // $type est déclaré et passé par référence http_response_code(400); echo json_encode(['message' => 'Données image base64 manquantes ou invalides']); die; } $extension = strtolower($type[1]); // dans (\w+) $name = 'pasted_image'; $image_data = base64_decode(substr($json['image_base64'], strpos($json['image_base64'], ',') + 1)); // découpe la chaine à la virgule puis convertit en binaire if($image_data === false){ http_response_code(400); echo json_encode(['message' => 'Décodage base64 invalide']); die; } } if(!in_array($extension, $allowed_extensions)){ $extension = 'jpeg'; } $local_path = $dest . $name . '_' . uniqid() . '.' . $extension; if(imagickCleanAndWriteImage($image_data, $local_path)){ echo json_encode(['location' => $local_path]); } else{ http_response_code(500); echo json_encode(['message' => 'Erreur image non valide']); } die; } elseif(isset($_GET['action']) && $_GET['action'] == 'upload_file'){ if(isset($_FILES['file'])){ $dest = 'media/'; if(!is_dir($dest)){ // Vérifier si le répertoire existe, sinon le créer mkdir($dest, 0755, true); } $name = sanitizeFileName(pathinfo($_FILES['file']['name'], PATHINFO_FILENAME)); // retirer caractères spéciaux et changer espaces en underscores $extension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)); $file_path = $dest . $name . '_' . uniqid() . '.' . $extension; // nom unique if(checkFileDownload($_FILES['file'])){ if(move_uploaded_file($_FILES['file']['tmp_name'], $file_path)){ echo json_encode(['location' => $file_path]); } else{ http_response_code(500); echo json_encode(['message' => 'Erreur enregistrement du fichier.']); } } else{ http_response_code(400); echo json_encode(['message' => 'Erreur 400: fichier non valide.']); } } else{ http_response_code(400); echo json_encode(['message' => 'Erreur 400: Bad Request']); } die; } ?>
Contenu de l'article 1.
Contenu de l'article 2.