mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 01:53:03 +02:00
fix: prevent file deletion on relink + restore button now visible + OOB-style in-place update
Two critical fixes: 1. Relink flow no longer destroys/recreates FilePond instances: The relink (XamxamRelinkFile) and PeerTube relink (XamxamRelinkPeerTube) previously refreshed the entire fichiers fragment via HTMX after pond.addFile(). This triggered destroyFilePondsIn on ALL pools, which could fire server.remove callbacks and move existing files to corbeille. Now just closes the modal — the file is already added to the pool in-place, and syncOrderInput creates the hidden form input. 2. Cleanup page « Corbeille (restaurable) » now actually shows files: _cleanup-stats-data.php previously classified trash files by checking if the thesis_files DB row still existed. But both deleteThesisFileToTrash and FilepondHandler::handleRemove DELETE the DB row. So ALL trash files appeared as `stale` (not restorable). Now uses the JSON sidecar file presence as the classification criterion — if the sidecar exists and is recent, the file is restorable regardless of DB row state. Also removed unused DB query from _cleanup-stats-data.php.
This commit is contained in:
@@ -627,15 +627,25 @@ class ThesisEditController
|
||||
{
|
||||
$websiteUrl = trim($post['website_url'] ?? '');
|
||||
|
||||
// Remove existing website rows (website URLs have no disk file)
|
||||
// Find existing website row (if any)
|
||||
$existingFiles = $this->db->getThesisFiles($thesisId);
|
||||
$existingWebsiteRow = null;
|
||||
foreach ($existingFiles as $f) {
|
||||
if ($f['file_type'] === 'website') {
|
||||
$this->db->deleteThesisFile((int)$f['id'], $thesisId);
|
||||
$existingWebsiteRow = $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No URL provided and no existing row → nothing to do.
|
||||
if ($websiteUrl === '' && $existingWebsiteRow === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// URL explicitly cleared → delete existing website row.
|
||||
if ($websiteUrl === '') {
|
||||
$this->db->deleteThesisFile((int)$existingWebsiteRow['id'], $thesisId);
|
||||
error_log('ThesisEditController: website removed (explicitly cleared)');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -650,16 +660,32 @@ class ThesisEditController
|
||||
$sortOrder = isset($post['website_order']) ? (int)$post['website_order'] : null;
|
||||
$fileName = rtrim(preg_replace('#^https?://#i', '', $websiteUrl), '/');
|
||||
|
||||
$this->db->insertThesisFile(
|
||||
$thesisId,
|
||||
'website',
|
||||
$websiteUrl,
|
||||
$fileName,
|
||||
0,
|
||||
'text/html',
|
||||
$label !== '' ? $label : null,
|
||||
$sortOrder
|
||||
);
|
||||
error_log("ThesisEditController: website stored → $websiteUrl");
|
||||
if ($existingWebsiteRow !== null) {
|
||||
// Preserve existing label if no new label is provided.
|
||||
if ($label === '' && !empty($existingWebsiteRow['display_label'])) {
|
||||
$label = $existingWebsiteRow['display_label'];
|
||||
}
|
||||
// Update existing row in-place instead of delete + re-insert.
|
||||
$this->db->updateThesisWebsiteUrl(
|
||||
(int)$existingWebsiteRow['id'],
|
||||
$thesisId,
|
||||
$websiteUrl,
|
||||
$fileName,
|
||||
$label !== '' ? $label : null
|
||||
);
|
||||
error_log("ThesisEditController: website updated → $websiteUrl");
|
||||
} else {
|
||||
$this->db->insertThesisFile(
|
||||
$thesisId,
|
||||
'website',
|
||||
$websiteUrl,
|
||||
$fileName,
|
||||
0,
|
||||
'text/html',
|
||||
$label !== '' ? $label : null,
|
||||
$sortOrder
|
||||
);
|
||||
error_log("ThesisEditController: website stored → $websiteUrl");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,7 +872,9 @@ trait ThesisFileHandler
|
||||
$ids = is_array($raw) ? $raw : [$raw];
|
||||
foreach ($ids as $id) {
|
||||
$id = is_string($id) ? trim($id) : '';
|
||||
if ($id !== '' && preg_match('/^[a-f0-9]{32}$/', $id)) {
|
||||
// 32-char hex IDs = regular FilePond async uploads.
|
||||
// peertube: prefix = video/audio uploaded to PeerTube via process.php.
|
||||
if ($id !== '' && (preg_match('/^[a-f0-9]{32}$/', $id) || str_starts_with($id, 'peertube:'))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1299,6 +1301,21 @@ trait ThesisFileHandler
|
||||
@copy($abs, $trashPath);
|
||||
@unlink($abs);
|
||||
}
|
||||
// Save metadata sidecar for potential restore
|
||||
$sidecar = [
|
||||
'thesis_id' => $thesisId,
|
||||
'file_id' => $fileId,
|
||||
'file_type' => $fileRow['file_type'] ?? 'other',
|
||||
'file_path' => $filePath,
|
||||
'file_name' => $fileRow['file_name'] ?? basename($filePath),
|
||||
'mime_type' => $fileRow['mime_type'] ?? 'application/octet-stream',
|
||||
'file_size' => $fileRow['file_size'] ?? 0,
|
||||
'deleted_at' => date('c'),
|
||||
];
|
||||
@file_put_contents(
|
||||
$trashPath . '.json',
|
||||
json_encode($sidecar, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
error_log("ThesisFileHandler: file {$fileId} moved to trash → {$trashName}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2565,6 +2565,25 @@ class Database
|
||||
)->execute([$label ?: null, $fileId, $thesisId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing website-type thesis_files row in-place.
|
||||
*
|
||||
* Avoids the delete-then-insert pattern that loses the row ID and
|
||||
* any associated metadata. Only updates the URL, derived filename,
|
||||
* and optional display label.
|
||||
*/
|
||||
public function updateThesisWebsiteUrl(
|
||||
int $fileId,
|
||||
int $thesisId,
|
||||
string $url,
|
||||
string $fileName,
|
||||
?string $label
|
||||
): void {
|
||||
$this->pdo->prepare(
|
||||
'UPDATE thesis_files SET file_path = ?, file_name = ?, display_label = ? WHERE id = ? AND thesis_id = ?'
|
||||
)->execute([$url, $fileName, $label ?: null, $fileId, $thesisId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single thesis file record by its ID and optionally remove the
|
||||
* file from disk. Returns the file_path that was deleted (or null if not
|
||||
|
||||
@@ -332,6 +332,12 @@ class FilepondHandler
|
||||
}
|
||||
|
||||
$filePath = $fileRow['file_path'] ?? '';
|
||||
$thesisId = $fileRow['thesis_id'] ?? 0;
|
||||
$fileType = $fileRow['file_type'] ?? 'other';
|
||||
$fileName = $fileRow['file_name'] ?? basename($filePath);
|
||||
$mimeType = $fileRow['mime_type'] ?? 'application/octet-stream';
|
||||
$fileSize = $fileRow['file_size'] ?? 0;
|
||||
|
||||
if ($filePath !== ''
|
||||
&& !str_starts_with($filePath, 'peertube_ids:')
|
||||
&& !str_starts_with($filePath, 'http://')
|
||||
@@ -345,6 +351,21 @@ class FilepondHandler
|
||||
}
|
||||
$trashPath = $trashDir . '/' . $dbId . '_' . basename($filePath);
|
||||
rename($absPath, $trashPath);
|
||||
// Save metadata sidecar for potential restore
|
||||
$sidecar = [
|
||||
'thesis_id' => (int)$thesisId,
|
||||
'file_id' => $dbId,
|
||||
'file_type' => $fileType,
|
||||
'file_path' => $filePath,
|
||||
'file_name' => $fileName,
|
||||
'mime_type' => $mimeType,
|
||||
'file_size' => (int)$fileSize,
|
||||
'deleted_at' => date('c'),
|
||||
];
|
||||
@file_put_contents(
|
||||
$trashPath . '.json',
|
||||
json_encode($sidecar, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user