admin edit.php: add cover image + thesis file management fields

- Database: add deleteThesisFile() and handleCoverUpload() methods
- ThesisEditController::load(): expose currentFiles + currentCover to view
- ThesisEditController::save(): handle couverture upload/removal,
  per-file deletion (delete_files[]), and new thesis file uploads
- edit.php template: new Fichiers fieldset with cover preview+remove,
  existing files list with delete checkboxes, new file upload input
  (mirrors add.php / partage.php)
This commit is contained in:
Pontoporeia
2026-04-27 20:33:21 +02:00
parent 27e1b6828d
commit 8e864fc624
4 changed files with 340 additions and 0 deletions

View File

@@ -1744,6 +1744,95 @@ class Database {
return $this->pdo->lastInsertId();
}
/**
* 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
* found), so the caller can clean up the filesystem.
*
* @param int $fileId Primary key of thesis_files row.
* @param int $thesisId Owning thesis ID (used as a safety guard).
* @return string|null The file_path that was stored, or null.
*/
public function deleteThesisFile(int $fileId, int $thesisId): ?string
{
$stmt = $this->pdo->prepare(
"SELECT file_path FROM thesis_files WHERE id = ? AND thesis_id = ? LIMIT 1"
);
$stmt->execute([$fileId, $thesisId]);
$row = $stmt->fetch();
if (!$row) {
return null;
}
$this->pdo->prepare("DELETE FROM thesis_files WHERE id = ?")->execute([$fileId]);
return $row['file_path'];
}
/**
* Replace the cover image for a thesis: removes any existing cover record
* (and its file from disk), then inserts the new one.
*
* @param int $thesisId
* @param array|null $upload Single-file $_FILES entry.
* @return string|null Relative path of the new cover, or null.
*/
public function handleCoverUpload(int $thesisId, ?array $upload): ?string
{
if (!$upload || ($upload['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
return null;
}
$allowedMimes = ['image/jpeg', 'image/png'];
$allowedExts = ['jpg', 'jpeg', 'png'];
$maxBytes = 10 * 1024 * 1024; // 10 MB
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($upload['tmp_name']);
$ext = strtolower(pathinfo($upload['name'], PATHINFO_EXTENSION));
if (!in_array($mimeType, $allowedMimes, true)
|| !in_array($ext, $allowedExts, true)
|| $upload['size'] > $maxBytes) {
error_log("handleCoverUpload: rejected {$upload['name']} ($mimeType, {$upload['size']} bytes)");
return null;
}
// Remove existing cover record + file
$existing = $this->pdo->prepare(
"SELECT id, file_path FROM thesis_files WHERE thesis_id = ? AND file_type = 'cover' LIMIT 1"
);
$existing->execute([$thesisId]);
if ($old = $existing->fetch()) {
$this->pdo->prepare("DELETE FROM thesis_files WHERE id = ?")->execute([$old['id']]);
if (!empty($old['file_path']) && defined('STORAGE_ROOT')) {
$abs = STORAGE_ROOT . '/' . $old['file_path'];
if (file_exists($abs)) @unlink($abs);
}
}
$coverDir = defined('STORAGE_ROOT') ? STORAGE_ROOT . '/covers/' : null;
if (!$coverDir) {
error_log('handleCoverUpload: STORAGE_ROOT not defined');
return null;
}
if (!is_dir($coverDir)) {
mkdir($coverDir, 0755, true);
}
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$targetPath = $coverDir . $safeName;
if (!move_uploaded_file($upload['tmp_name'], $targetPath)) {
error_log("handleCoverUpload: move_uploaded_file failed for {$upload['name']}");
return null;
}
chmod($targetPath, 0644);
$relPath = 'covers/' . $safeName;
$this->insertThesisFile($thesisId, 'cover', $relPath, basename($upload['name']), $upload['size'], $mimeType);
error_log("handleCoverUpload: saved $relPath");
return $relPath;
}
// ========================================================================
// EXPORT HELPERS — used by ExportController
// ========================================================================