mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-08-10 07:11:18 +02:00
fix: merge corbeille lists + bulk restore button in cleanup
- Single unified 'Corbeille' table replaces the two separate lists (stale/orphelin + restorable). Each row has a Statut column showing '↺ Restaurable' or 'Orphelin', plus a checkbox for bulk ops. - Checkboxes on restorable rows carry data-restorable='1' attribute so the bulk JS can distinguish them. - Bulk actions bar now has both 'Supprimer la sélection' and '↺ Restaurer la sélection' buttons. The restore button only appears when at least one restorable item is checked. - New cleanupBulkRestore() JS function populates a dedicated hidden form and confirms before submitting. Only restorable items are sent. - restore-trash.php refactored to handle both single (trash_file) and bulk (trash_files[]) inputs via a loop, accumulating restored/skipped counts and re-rendering the fragment on HTMX requests.
This commit is contained in:
@@ -30,117 +30,105 @@ if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|
||||
exit;
|
||||
}
|
||||
|
||||
$trashFile = trim($_POST['trash_file'] ?? '');
|
||||
if ($trashFile === '') {
|
||||
$trashFile = trim($_POST['trash_file'] ?? '');
|
||||
$trashFiles = $_POST['trash_files'] ?? [];
|
||||
if (!is_array($trashFiles)) $trashFiles = [];
|
||||
|
||||
// Normalise: accept both single (trash_file) and bulk (trash_files[])
|
||||
$allTrashFiles = $trashFile !== '' ? [$trashFile] : $trashFiles;
|
||||
$allTrashFiles = array_values(array_filter(array_map('trim', $allTrashFiles), fn($n) => $n !== ''));
|
||||
|
||||
if (empty($allTrashFiles)) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => false, 'error' => 'Nom de fichier invalide.']);
|
||||
echo json_encode(['ok' => false, 'error' => 'Aucun fichier spécifié.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../../src/Database.php';
|
||||
$db = new Database();
|
||||
|
||||
$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;
|
||||
}
|
||||
$restored = 0;
|
||||
$skipped = 0;
|
||||
$errors = [];
|
||||
|
||||
// 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;
|
||||
}
|
||||
foreach ($allTrashFiles as $trashFile) {
|
||||
$trashFile = basename($trashFile);
|
||||
$trashPath = $trashDir . '/' . $trashFile;
|
||||
$sidecarPath = $trashPath . '.json';
|
||||
|
||||
$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;
|
||||
}
|
||||
if (!is_file($trashPath) || !str_starts_with(realpath($trashPath), realpath($trashDir))) {
|
||||
$skipped++;
|
||||
$errors[] = "$trashFile : introuvable dans la corbeille.";
|
||||
continue;
|
||||
}
|
||||
|
||||
$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;
|
||||
if (!file_exists($sidecarPath)) {
|
||||
$skipped++;
|
||||
$errors[] = "$trashFile : métadonnées absentes (trop ancien ?).";
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
$sidecar = json_decode(file_get_contents($sidecarPath), true);
|
||||
if (!is_array($sidecar) || empty($sidecar['file_path']) || empty($sidecar['thesis_id'])) {
|
||||
$skipped++;
|
||||
$errors[] = "$trashFile : métadonnées corrompues.";
|
||||
continue;
|
||||
}
|
||||
|
||||
$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;
|
||||
|
||||
$thesis = $db->getThesis($thesisId);
|
||||
if (!$thesis) {
|
||||
$skipped++;
|
||||
$errors[] = "$trashFile : le TFE #$thesisId n'existe plus.";
|
||||
continue;
|
||||
}
|
||||
|
||||
$absOriginal = $storageRoot . '/' . $originalPath;
|
||||
if (file_exists($absOriginal)) {
|
||||
@unlink($trashPath);
|
||||
@unlink($sidecarPath);
|
||||
$skipped++;
|
||||
$errors[] = "$trashFile : un fichier existe déjà à l'emplacement d'origine.";
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentDir = dirname($absOriginal);
|
||||
if (!is_dir($parentDir)) {
|
||||
mkdir($parentDir, 0755, true);
|
||||
}
|
||||
|
||||
if (!rename($trashPath, $absOriginal)) {
|
||||
$skipped++;
|
||||
$errors[] = "$trashFile : échec du déplacement.";
|
||||
continue;
|
||||
}
|
||||
chmod($absOriginal, 0644);
|
||||
|
||||
// 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;
|
||||
|
||||
$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: $trashFile → $originalPath");
|
||||
$restored++;
|
||||
}
|
||||
|
||||
// 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';
|
||||
@@ -149,9 +137,10 @@ if (isset($_SERVER['HTTP_HX_REQUEST'])) {
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'id' => $newId,
|
||||
'thesis_id' => $thesisId,
|
||||
'message' => 'Fichier restauré avec succès.',
|
||||
'ok' => $restored > 0,
|
||||
'restored' => $restored,
|
||||
'skipped' => $skipped,
|
||||
'errors' => $errors,
|
||||
'message' => $restored . ' fichier(s) restauré(s).' . ($skipped > 0 ? ' ' . $skipped . ' ignoré(s).' : ''),
|
||||
]);
|
||||
exit;
|
||||
|
||||
Reference in New Issue
Block a user