Files
fuzip/api/merge.php
Charles bd6d321ed7 Initial commit: FuZip - Application de fusion interactive de fichiers ZIP
- 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
2026-01-12 03:29:01 +01:00

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