- Backend PHP: architecture MVC avec API REST (upload, merge, preview, extract) - Frontend JavaScript: composants modulaires (arborescence, upload, themes, i18n) - Fonctionnalités: drag&drop, sélection exclusive, détection conflits, persistance état - Sécurité: validation stricte, isolation sessions, sanitization chemins - UI/UX: responsive, thèmes clair/sombre, multi-langue (FR/EN) - Documentation: README complet avec installation et utilisation
167 lines
4.9 KiB
PHP
167 lines
4.9 KiB
PHP
<?php
|
|
/**
|
|
* API Merge - Fusion de ZIP et téléchargement
|
|
*
|
|
* Endpoint : POST /api/merge.php
|
|
*
|
|
* Paramètres (JSON body) :
|
|
* {
|
|
* "selection": {
|
|
* "path/to/file": "left"|"right"|null,
|
|
* ...
|
|
* }
|
|
* }
|
|
*
|
|
* Réponse :
|
|
* - Stream direct du fichier ZIP fusionné (Content-Disposition: attachment)
|
|
* - OU JSON avec erreur si échec
|
|
*/
|
|
|
|
// Augmenter les limites PHP pour les gros fichiers
|
|
set_time_limit(300); // 5 minutes max
|
|
ini_set('memory_limit', '512M'); // 512 MB de mémoire
|
|
|
|
require_once __DIR__ . '/../core/Config.php';
|
|
require_once __DIR__ . '/../core/SessionManager.php';
|
|
require_once __DIR__ . '/../core/ZipHandler.php';
|
|
require_once __DIR__ . '/../core/FileTree.php';
|
|
|
|
function sendError(string $message, int $httpCode = 400): void {
|
|
http_response_code($httpCode);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
Config::log("Erreur API merge : {$message}", 'ERROR');
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => $message
|
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
// CORS
|
|
if (isset($_SERVER['HTTP_ORIGIN'])) {
|
|
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
|
|
header('Access-Control-Allow-Credentials: true');
|
|
header('Access-Control-Max-Age: 86400');
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
|
|
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
|
}
|
|
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
|
|
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
// Vérifier la méthode HTTP
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
sendError('Méthode non autorisée. Utilisez POST.', 405);
|
|
}
|
|
|
|
// Initialiser la session
|
|
try {
|
|
$sessionId = SessionManager::init();
|
|
Config::log("Merge API - Session : {$sessionId}");
|
|
} catch (Exception $e) {
|
|
sendError('Erreur initialisation session : ' . $e->getMessage(), 500);
|
|
}
|
|
|
|
// Récupérer le body JSON
|
|
$input = file_get_contents('php://input');
|
|
if (empty($input)) {
|
|
sendError('Body JSON manquant. Envoyez la sélection au format JSON.');
|
|
}
|
|
|
|
$data = json_decode($input, true);
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
sendError('JSON invalide : ' . json_last_error_msg());
|
|
}
|
|
|
|
// Vérifier la sélection
|
|
if (!isset($data['selection']) || !is_array($data['selection'])) {
|
|
sendError('Paramètre "selection" manquant ou invalide.');
|
|
}
|
|
|
|
$selection = $data['selection'];
|
|
Config::log("Sélection reçue : " . count($selection) . " entrées");
|
|
|
|
// Valider la sélection
|
|
$validation = FileTree::validateSelection($selection);
|
|
if (!$validation['valid']) {
|
|
$conflicts = implode(', ', $validation['conflicts']);
|
|
sendError("Sélection invalide. Conflits détectés : {$conflicts}");
|
|
}
|
|
|
|
$uploadDir = SessionManager::getUploadDir($sessionId);
|
|
$leftZipPath = $uploadDir . 'left.zip';
|
|
$rightZipPath = $uploadDir . 'right.zip';
|
|
|
|
// Vérifier que les 2 ZIP existent
|
|
if (!file_exists($leftZipPath)) {
|
|
sendError('ZIP gauche non uploadé.', 404);
|
|
}
|
|
|
|
if (!file_exists($rightZipPath)) {
|
|
sendError('ZIP droite non uploadé.', 404);
|
|
}
|
|
|
|
// Préparer le chemin du ZIP fusionné
|
|
$mergedZipPath = $uploadDir . 'merged.zip';
|
|
|
|
// Fusionner
|
|
$zipHandler = new ZipHandler();
|
|
|
|
try {
|
|
Config::log("Début fusion : " . count(array_filter($selection)) . " fichiers sélectionnés");
|
|
|
|
$result = $zipHandler->merge($leftZipPath, $rightZipPath, $selection, $mergedZipPath);
|
|
|
|
Config::log("Fusion réussie : {$result}");
|
|
|
|
// Vérifier que le fichier a été créé
|
|
if (!file_exists($mergedZipPath)) {
|
|
throw new Exception("Le fichier fusionné n'a pas été créé");
|
|
}
|
|
|
|
$fileSize = filesize($mergedZipPath);
|
|
Config::log("ZIP fusionné créé : " . Config::formatBytes($fileSize));
|
|
|
|
// Mettre à jour le timestamp
|
|
SessionManager::updateAccess($sessionId);
|
|
|
|
// Envoyer le fichier en téléchargement
|
|
// Nom de fichier avec timestamp
|
|
$timestamp = date('Y-m-d_His');
|
|
$filename = "fuzip_merged_{$timestamp}.zip";
|
|
|
|
// Headers pour téléchargement
|
|
header('Content-Type: application/zip');
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|
header('Content-Length: ' . $fileSize);
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
header('Pragma: no-cache');
|
|
header('Expires: 0');
|
|
|
|
// Stream le fichier
|
|
$handle = fopen($mergedZipPath, 'rb');
|
|
if ($handle === false) {
|
|
throw new Exception("Impossible d'ouvrir le fichier fusionné");
|
|
}
|
|
|
|
// Envoyer par chunks pour économiser la mémoire
|
|
while (!feof($handle)) {
|
|
echo fread($handle, Config::STREAM_BUFFER_SIZE);
|
|
flush();
|
|
}
|
|
|
|
fclose($handle);
|
|
|
|
Config::log("ZIP fusionné envoyé : {$filename} (" . Config::formatBytes($fileSize) . ")");
|
|
|
|
exit;
|
|
|
|
} catch (Exception $e) {
|
|
sendError('Erreur lors de la fusion : ' . $e->getMessage(), 500);
|
|
}
|