mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
fix: TFE and annexes files not saved, plus keyword validation and file preview CSS
- ThesisCreateController::submit() was missing call to handleAnnexeFiles - ThesisEditController::save() was missing annexe upload handling - handleAnnexeFiles now applies ALLOWED_MIME_TYPES/ALLOWED_EXTENSIONS validation (same restrictions as TFE files, formerly only size was checked) - Use correct $_FILES key 'annexes' (matching the form input name) - Relax keyword minimum: admin create/edit require 1+, student (partage) requires 3 - Add CSS styles for file preview items (.fp-item, .fp-thumb, .fp-icon, .fp-meta, .fp-name, .fp-size) so multi-file previews (annexes, etc.) wrap correctly - Fix TFE file input accept attribute in fichiers-fragment.php to include video/audio/archive extensions
This commit is contained in:
7
TODO.md
7
TODO.md
@@ -27,3 +27,10 @@
|
|||||||
- [x] On validation error, append "envoyez un e-mail à xamxam@erg.be" to flash error message
|
- [x] On validation error, append "envoyez un e-mail à xamxam@erg.be" to flash error message
|
||||||
- [x] Preserve uploaded file names across validation redirects: store in session, display as warning on re-render so the student knows which files to re-select
|
- [x] Preserve uploaded file names across validation redirects: store in session, display as warning on re-render so the student knows which files to re-select
|
||||||
- [x] Obfuscate all email addresses and mailto: links as HTML decimal entities site-wide (EmailObfuscator class, applied in templates + Parsedown post-processing)
|
- [x] Obfuscate all email addresses and mailto: links as HTML decimal entities site-wide (EmailObfuscator class, applied in templates + Parsedown post-processing)
|
||||||
|
- [x] Fix TFE and annexes files not saved in ThesisCreateController::submit(): call handleAnnexeFiles, fix file input name mapping
|
||||||
|
- [x] Apply ALLOWED_MIME_TYPES/ALLOWED_EXTENSIONS validation in handleAnnexeFiles (same as handleTfeFiles)
|
||||||
|
- [x] Fix handleAnnexeFiles to use correct $_FILES key ('annexes' not 'files')
|
||||||
|
- [x] Add annexe handling in ThesisEditController::save()
|
||||||
|
- [x] Relax 3-keyword minimum: admin mode (create) requires 1+, edit requires 1+, student (partage) requires 3
|
||||||
|
- [x] Add CSS for file preview items (.fp-item, .fp-thumb, .fp-icon, .fp-meta, .fp-name, .fp-size) so annexes/cover/note-intention previews wrap and display correctly
|
||||||
|
- [x] Fix TFE file input accept attribute to include video/audio/archive extensions
|
||||||
|
|||||||
@@ -1219,3 +1219,63 @@ a.recap-file-name:hover {
|
|||||||
.tag-search-item--create.tag-search-item--highlight {
|
.tag-search-item--create.tag-search-item--highlight {
|
||||||
background: var(--accent-muted, rgba(149, 87, 181, 0.08));
|
background: var(--accent-muted, rgba(149, 87, 181, 0.08));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── File preview list (couverture, note d'intention, annexes) ───────────── */
|
||||||
|
|
||||||
|
.file-preview-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: var(--space-3xs) 0 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-3xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
padding: var(--space-3xs) var(--space-xs);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-thumb {
|
||||||
|
width: 48px;
|
||||||
|
height: 36px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-icon {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-name {
|
||||||
|
font-size: var(--step--1);
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-size {
|
||||||
|
font-size: var(--step--2);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
|||||||
<div class="admin-file-input">
|
<div class="admin-file-input">
|
||||||
<input type="file" id="tfe-files-input"
|
<input type="file" id="tfe-files-input"
|
||||||
name="files[]" multiple
|
name="files[]" multiple
|
||||||
accept=".pdf,.jpg,.jpeg,.png,.gif,.webp"
|
accept=".pdf,.jpg,.jpeg,.png,.gif,.webp,.mp4,.webm,.ogv,.mov,.mp3,.ogg,.oga,.wav,.flac,.aac,.m4a,.vtt,.zip,.tar,.gz,.tgz"
|
||||||
class="tfe-file-picker">
|
class="tfe-file-picker">
|
||||||
<small class="admin-file-hint">
|
<small class="admin-file-hint">
|
||||||
PDF (max 100 MB) · Images (JPG/PNG/GIF/WEBP).
|
PDF (max 100 MB) · Images (JPG/PNG/GIF/WEBP).
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ class ThesisCreateController
|
|||||||
$this->handleCoverUpload($thesisId, $files['couverture'] ?? null, $folderPath, $filePrefix);
|
$this->handleCoverUpload($thesisId, $files['couverture'] ?? null, $folderPath, $filePrefix);
|
||||||
$this->handleNoteIntentionUpload($thesisId, $files['note_intention'] ?? null, $folderPath, $filePrefix);
|
$this->handleNoteIntentionUpload($thesisId, $files['note_intention'] ?? null, $folderPath, $filePrefix);
|
||||||
$nextNum = $this->handleTfeFiles($thesisId, $files['files'] ?? null, $folderPath, $filePrefix, $post, 1);
|
$nextNum = $this->handleTfeFiles($thesisId, $files['files'] ?? null, $folderPath, $filePrefix, $post, 1);
|
||||||
|
$this->handleAnnexeFiles($thesisId, $files['annexes'] ?? null, $folderPath, $filePrefix, $post);
|
||||||
// PeerTube file rows don't go on disk, but the uploads themselves are processed separately
|
// PeerTube file rows don't go on disk, but the uploads themselves are processed separately
|
||||||
|
|
||||||
// ── 5b. PeerTube video / audio uploads ────────────────────────────────
|
// ── 5b. PeerTube video / audio uploads ────────────────────────────────
|
||||||
@@ -429,7 +430,7 @@ class ThesisCreateController
|
|||||||
if (count($keywords) > 10) {
|
if (count($keywords) > 10) {
|
||||||
throw new Exception('Maximum 10 mots-clés autorisés.');
|
throw new Exception('Maximum 10 mots-clés autorisés.');
|
||||||
}
|
}
|
||||||
if (count($keywords) < 3) {
|
if (!$adminMode && count($keywords) < 3) {
|
||||||
throw new Exception('Veuillez indiquer au moins 3 mots-clés.');
|
throw new Exception('Veuillez indiquer au moins 3 mots-clés.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -294,8 +294,8 @@ class ThesisEditController
|
|||||||
$keywords = array_values(array_unique($keywords));
|
$keywords = array_values(array_unique($keywords));
|
||||||
$keywords = array_filter($keywords, fn($t) => $t !== '');
|
$keywords = array_filter($keywords, fn($t) => $t !== '');
|
||||||
$keywords = array_slice($keywords, 0, 10);
|
$keywords = array_slice($keywords, 0, 10);
|
||||||
if (count($keywords) < 3) {
|
if (count($keywords) < 1) {
|
||||||
throw new Exception('Veuillez indiquer au moins 3 mots-clés.');
|
throw new Exception('Veuillez indiquer au moins 1 mot-clé.');
|
||||||
}
|
}
|
||||||
$this->db->setThesisTags($thesisId, $keywords);
|
$this->db->setThesisTags($thesisId, $keywords);
|
||||||
error_log('[ThesisEdit] Step 6 OK — tags=' . json_encode($keywords));
|
error_log('[ThesisEdit] Step 6 OK — tags=' . json_encode($keywords));
|
||||||
@@ -425,6 +425,11 @@ class ThesisEditController
|
|||||||
$this->handleTfeFiles($thesisId, $files['files'], $folderPath, $filePrefix, $post, $tfeCount + 1);
|
$this->handleTfeFiles($thesisId, $files['files'], $folderPath, $filePrefix, $post, $tfeCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── New annexe files upload ────────────────────────────────────────────
|
||||||
|
if (isset($files['annexes']) && is_array($files['annexes']['name'] ?? null)) {
|
||||||
|
$this->handleAnnexeFiles($thesisId, $files['annexes'], $folderPath, $filePrefix, $post);
|
||||||
|
}
|
||||||
|
|
||||||
// ── PeerTube video / audio uploads ────────────────────────────────────
|
// ── PeerTube video / audio uploads ────────────────────────────────────
|
||||||
$this->handlePeerTubeUpload($thesisId, trim($post['titre'] ?? ''), $files, 'peertube_video');
|
$this->handlePeerTubeUpload($thesisId, trim($post['titre'] ?? ''), $files, 'peertube_video');
|
||||||
$this->handlePeerTubeUpload($thesisId, trim($post['titre'] ?? ''), $files, 'peertube_audio');
|
$this->handlePeerTubeUpload($thesisId, trim($post['titre'] ?? ''), $files, 'peertube_audio');
|
||||||
|
|||||||
@@ -344,10 +344,20 @@ trait ThesisFileHandler
|
|||||||
if ($mimeType === 'text/plain' && $ext === 'vtt') {
|
if ($mimeType === 'text/plain' && $ext === 'vtt') {
|
||||||
$mimeType = 'text/vtt';
|
$mimeType = 'text/vtt';
|
||||||
}
|
}
|
||||||
|
if ($mimeType === 'application/octet-stream' && !in_array($ext, self::ALLOWED_EXTENSIONS, true)) {
|
||||||
|
error_log("ThesisFileHandler: annexe extension not allowed {$uploads['name'][$i]} ($ext), skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!in_array($mimeType, self::ALLOWED_MIME_TYPES, true)
|
||||||
|
&& !in_array($ext, self::ALLOWED_EXTENSIONS, true)) {
|
||||||
|
error_log("ThesisFileHandler: invalid annexe type {$uploads['name'][$i]} ($mimeType / $ext), skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$sizeLimit = (($mimeType === 'application/pdf' || $ext === 'pdf') ? self::MAX_PDF_SIZE : self::MAX_FILE_SIZE);
|
$isPdf = ($mimeType === 'application/pdf' || $ext === 'pdf');
|
||||||
|
$sizeLimit = $isPdf ? self::MAX_PDF_SIZE : self::MAX_FILE_SIZE;
|
||||||
if ($uploads['size'][$i] > $sizeLimit) {
|
if ($uploads['size'][$i] > $sizeLimit) {
|
||||||
error_log("ThesisFileHandler: annexe too large {$uploads['name'][$i]} (" . round($uploads['size'][$i] / 1024 / 1024) . ' MB), skipping');
|
error_log("ThesisFileHandler: annexe too large {$uploads['name'][$i]} (" . round($uploads['size'][$i] / 1024 / 1024) . " MB), skipping");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,19 @@
|
|||||||
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
+\\\\\\\ to: roqtyzln 34d91340 "feat: obfuscate all email addresses and mailto links as HTML entities" (rebased revision)
|
+\\\\\\\ to: roqtyzln 34d91340 "feat: obfuscate all email addresses and mailto links as HTML entities" (rebased revision)
|
||||||
++ $linkName = $link['name'] ?? '';
|
++ $linkName = $link['name'] ?? '';
|
||||||
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: roqtyzln 34d91340 "feat: obfuscate all email addresses and mailto links as HTML entities" (rebased revision)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
- $linkName = $link['name'] ?? '';
|
||||||
|
- $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: somsyvxz 14a3cd10 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebase destination)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: yztqkpzz f3b3686c "fix: TFE and annexes files not saved, plus keyword validation and file preview CSS" (rebased revision)
|
||||||
|
$linkName = $link['name'] ?? '';
|
||||||
|
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
$linkLockedYear = $link['locked_year'] ?? null;
|
||||||
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
+\\\\\\\ to: yztqkpzz 9c95f5ce "fix: TFE and annexes files not saved, plus keyword validation and file preview CSS" (rebased revision)
|
||||||
|
++ $linkName = $link['name'] ?? '';
|
||||||
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
?>
|
?>
|
||||||
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
|
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
|
||||||
|
|||||||
Reference in New Issue
Block a user