- 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
182 lines
5.6 KiB
PHP
182 lines
5.6 KiB
PHP
<?php
|
|
/**
|
|
* API Structure - Récupération de structure et détection de conflits
|
|
*
|
|
* Endpoint : GET /api/structure.php
|
|
*
|
|
* Paramètres :
|
|
* - action : 'get' (structure d'un ZIP) ou 'conflicts' (conflits entre 2 ZIP)
|
|
* - side : 'left' ou 'right' (pour action=get uniquement)
|
|
*
|
|
* Réponse JSON :
|
|
* Pour action=get :
|
|
* {
|
|
* "success": true,
|
|
* "structure": {...},
|
|
* "stats": {...}
|
|
* }
|
|
*
|
|
* Pour action=conflicts :
|
|
* {
|
|
* "success": true,
|
|
* "conflicts": [...]
|
|
* }
|
|
*/
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
// 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);
|
|
}
|
|
|
|
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 sendResponse(array $data, int $httpCode = 200): void {
|
|
http_response_code($httpCode);
|
|
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
function sendError(string $message, int $httpCode = 400): void {
|
|
Config::log("Erreur API structure : {$message}", 'ERROR');
|
|
sendResponse([
|
|
'success' => false,
|
|
'error' => $message
|
|
], $httpCode);
|
|
}
|
|
|
|
// Vérifier la méthode HTTP
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
|
sendError('Méthode non autorisée. Utilisez GET.', 405);
|
|
}
|
|
|
|
// Initialiser la session
|
|
try {
|
|
$sessionId = SessionManager::init();
|
|
Config::log("Structure API - Session : {$sessionId}");
|
|
} catch (Exception $e) {
|
|
sendError('Erreur initialisation session : ' . $e->getMessage(), 500);
|
|
}
|
|
|
|
// Récupérer le paramètre action
|
|
$action = $_GET['action'] ?? 'get';
|
|
|
|
if (!in_array($action, ['get', 'conflicts'])) {
|
|
sendError('Paramètre "action" invalide. Valeurs acceptées : "get", "conflicts".');
|
|
}
|
|
|
|
Config::log("Action demandée : {$action}");
|
|
|
|
$uploadDir = SessionManager::getUploadDir($sessionId);
|
|
$zipHandler = new ZipHandler();
|
|
|
|
// ACTION : get - Récupérer la structure d'un ZIP
|
|
if ($action === 'get') {
|
|
// Vérifier le paramètre side
|
|
if (!isset($_GET['side']) || !in_array($_GET['side'], ['left', 'right'])) {
|
|
sendError('Paramètre "side" manquant ou invalide pour action=get.');
|
|
}
|
|
|
|
$side = $_GET['side'];
|
|
$zipPath = $uploadDir . $side . '.zip';
|
|
|
|
// Vérifier que le fichier existe
|
|
if (!file_exists($zipPath)) {
|
|
sendError("Aucun fichier ZIP uploadé pour le côté '{$side}'. Uploadez d'abord un fichier.", 404);
|
|
}
|
|
|
|
Config::log("Récupération structure : {$side}");
|
|
|
|
try {
|
|
// Extraire la structure
|
|
$structure = $zipHandler->getStructure($zipPath);
|
|
|
|
// Construire l'arborescence
|
|
$tree = FileTree::buildTree($structure['files']);
|
|
$stats = FileTree::getTreeStats($tree);
|
|
|
|
// Mettre à jour le timestamp
|
|
SessionManager::updateAccess($sessionId);
|
|
|
|
sendResponse([
|
|
'success' => true,
|
|
'side' => $side,
|
|
'structure' => [
|
|
'tree' => $tree,
|
|
'files_list' => $structure['files']
|
|
],
|
|
'stats' => [
|
|
'total_files' => $stats['total_files'],
|
|
'total_folders' => $stats['total_folders'],
|
|
'total_size' => $stats['total_size'],
|
|
'total_size_formatted' => Config::formatBytes($stats['total_size']),
|
|
'max_depth' => $stats['max_depth']
|
|
]
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
sendError('Erreur lors de l\'extraction : ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
// ACTION : conflicts - Détecter les conflits entre les 2 ZIP
|
|
if ($action === 'conflicts') {
|
|
$leftZipPath = $uploadDir . 'left.zip';
|
|
$rightZipPath = $uploadDir . 'right.zip';
|
|
|
|
// Vérifier que les 2 fichiers existent
|
|
if (!file_exists($leftZipPath)) {
|
|
sendError('ZIP gauche non uploadé. Uploadez les 2 fichiers avant de détecter les conflits.', 404);
|
|
}
|
|
|
|
if (!file_exists($rightZipPath)) {
|
|
sendError('ZIP droite non uploadé. Uploadez les 2 fichiers avant de détecter les conflits.', 404);
|
|
}
|
|
|
|
Config::log("Détection conflits entre left.zip et right.zip");
|
|
|
|
try {
|
|
// Extraire les structures
|
|
$leftStructure = $zipHandler->getStructure($leftZipPath);
|
|
$rightStructure = $zipHandler->getStructure($rightZipPath);
|
|
|
|
// Détecter les conflits
|
|
$conflicts = FileTree::detectConflicts($leftStructure['files'], $rightStructure['files']);
|
|
|
|
Config::log("Conflits détectés : " . count($conflicts));
|
|
|
|
// Mettre à jour le timestamp
|
|
SessionManager::updateAccess($sessionId);
|
|
|
|
sendResponse([
|
|
'success' => true,
|
|
'conflicts' => $conflicts,
|
|
'total_conflicts' => count($conflicts),
|
|
'summary' => [
|
|
'left_total_files' => $leftStructure['total_files'],
|
|
'right_total_files' => $rightStructure['total_files'],
|
|
'conflicts_count' => count($conflicts)
|
|
]
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
sendError('Erreur lors de la détection des conflits : ' . $e->getMessage(), 500);
|
|
}
|
|
}
|