false, 'error' => 'Méthode non autorisée.']); exit; } if (!isset($_POST['csrf_token'], $_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { http_response_code(403); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'CSRF invalide.']); exit; } $trashFile = trim($_POST['trash_file'] ?? ''); if ($trashFile === '') { http_response_code(400); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'Nom de fichier invalide.']); exit; } $storageRoot = STORAGE_ROOT; $trashDir = $storageRoot . '/tmp/_trash'; $trashPath = $trashDir . '/' . basename($trashFile); $sidecarPath = $trashPath . '.json'; // Validate the trash file is inside the trash directory if (!is_file($trashPath) || !str_starts_with(realpath($trashPath), realpath($trashDir))) { http_response_code(404); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'Fichier introuvable dans la corbeille.']); exit; } // Read sidecar metadata if (!file_exists($sidecarPath)) { http_response_code(400); header('Content-Type: application/json; charset=utf-8'); echo json_encode([ 'ok' => false, 'error' => 'Métadonnées de restauration absentes (fichier trop ancien ?). Utilisez "Relier un fichier existant".', ]); exit; } $sidecar = json_decode(file_get_contents($sidecarPath), true); if (!is_array($sidecar) || empty($sidecar['file_path']) || empty($sidecar['thesis_id'])) { http_response_code(400); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'Métadonnées corrompues.']); exit; } $originalPath = $sidecar['file_path']; $thesisId = (int)$sidecar['thesis_id']; $fileType = $sidecar['file_type'] ?? 'other'; $fileName = $sidecar['file_name'] ?? basename($originalPath); $mimeType = $sidecar['mime_type'] ?? 'application/octet-stream'; $fileSize = (int)($sidecar['file_size'] ?? 0); $displayLabel = $sidecar['display_label'] ?? null; // Verify the thesis still exists require_once __DIR__ . '/../../../src/Database.php'; $db = new Database(); $thesis = $db->getThesis($thesisId); if (!$thesis) { http_response_code(404); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'Le TFE associé n\'existe plus.']); exit; } // Check no file already exists at the original path $absOriginal = $storageRoot . '/' . $originalPath; if (file_exists($absOriginal)) { // File already restored or a new file replaced it — just clean up the trash @unlink($trashPath); @unlink($sidecarPath); http_response_code(409); header('Content-Type: application/json; charset=utf-8'); echo json_encode([ 'ok' => false, 'error' => 'Un fichier existe déjà à l\'emplacement d\'origine. Fichier corbeille nettoyé.', ]); exit; } // Ensure parent directory exists $parentDir = dirname($absOriginal); if (!is_dir($parentDir)) { mkdir($parentDir, 0755, true); } // Move file back from trash to original location if (!rename($trashPath, $absOriginal)) { http_response_code(500); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'Échec du déplacement du fichier.']); exit; } chmod($absOriginal, 0644); // Delete the sidecar file @unlink($sidecarPath); // Re-insert the thesis_files DB row $db->insertThesisFile( $thesisId, $fileType, $originalPath, $fileName, $fileSize, $mimeType, $displayLabel, null ); $newId = $db->getConnection()->lastInsertId(); error_log("[restore-trash] thesis_id=$thesisId file_id=$newId restored from trash: $trashFile → $originalPath"); $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // HTMX request: re-render the fragment if (isset($_SERVER['HTTP_HX_REQUEST'])) { header('HX-Trigger: refreshStats'); require __DIR__ . '/cleanup-stats-fragment.php'; exit; } header('Content-Type: application/json; charset=utf-8'); echo json_encode([ 'ok' => true, 'id' => $newId, 'thesis_id' => $thesisId, 'message' => 'Fichier restauré avec succès.', ]); exit;