- Edit mode now uses the same fichiers-fragment.php as add and partage,
instead of duplicating the format checkboxes + new-file upload + website
URL fieldsets.
- Edit-only elements (existing files list, cover replace) stay in
a separate #edit-existing-files-block below the shared fragment.
- Removed .zip/.tar/.gz from the main TFE upload accept in both
fichiers-fragment.php and fieldset-files.php. Archives go only
in the Annexes file input.
- Removed admin/format-website-fragment.php dependency from edit
(no longer needed — the shared fragment handles website too).
fix: jury repop crash + hx-preserve on file inputs, remove zip/tar from tfe accept
- Jury fieldset add-mode repopulation now handles both scalar (legacy)
and array (new dynamic multi-row) values for jury_promoteur and
jury_promoteur_ulb_name. htmlspecialchars() was choking on array value.
- All file inputs in fichiers-fragment.php wrapped in hx-preserve
containers so HTMX swaps don't wipe user-selected files when toggling
formats or the annexes checkbox.
- Removed .zip/.tar/.gz from main TFE file accept — archives only via
annexes input (which already had multiple + correct accept).
- Edit mode now reuses the same fichiers-fragment.php fragment.
fix: file inputs re-initialize after HTMX swap via inline script
- Exposed window.XamxamInitFileUploads from file-upload-queue.js IIFE
so HTMX fragments can trigger re-binding without a global listener.
- fichiers-fragment.php emits <script>XamxamInitFileUploads()</script>
at the end of the #format-fichiers-block fragment.
- Removed hx-preserve wrappers — they prevented re-render after
format/annexes toggles changed visible inputs.
- This also fixes .zip removal from TFE accept and jury repopulation
array crash from the previous commit.
refactor: simplify file-upload-queue.js, remove file-preview.js
- file-upload-queue.js rewritten from ~250 lines to ~120 lines:
no more DataTransfer machinery, no IIFE wrapper, uses .onchange
instead of addEventListener for simpler HTMX re-init.
- window.XamxamInitFileUploads is the function itself (not an IIFE export).
- Merged file-preview.js functionality into file-upload-queue.js
(single-file .data-preview handling). Deleted file-preview.js.
- fichiers-fragment.php inline script calls XamxamInitFileUploads()
after every HTMX swap (same as before).
debug: add console.log to file-upload-queue.js for file input behavior
Adds logging at key points to diagnose why only one file is displayed:
- XamxamInitFileUploads called
- TFE queue picker init (id, multiple attribute state)
- onchange event (files count, names)
- fileArray post-concat length
- Single-file preview bindings (id, multiple attribute)
Remove after debug session.
1. fix: form improvements — multiple promoteurices, asterisks, contact dedup, bentopdf
- Multiple promoteurice (interne + ULB): both fieldsets now support dynamic
add/remove rows (same pattern as lecteurs). field names changed to arrays
(jury_promoteur[], jury_promoteur_ulb_name[]). Controllers accept both
scalar and array forms for backwards compat.
- ULB promoteurice: when finality=Approfondi, asterisk appears on legend
and first ULB input is marked required (JS toggle). Non-Approfondi hides
the fieldset and clears values.
- Contact visibility duplication: removed redundant contact_public checkbox
from admin add/edit forms (showContact=false). The 'mail' field in
fieldset-tfe-info already serves this purpose.
- Asterisk fixes: website URL field now has asterisk+required when Site web
format selected. Video/audio already had correct required handling.
- bentopdf link: clearer full URL 'https://bentopdf.com/' in both
fichiers-fragment.php and form.php (edit mode)
2. refactor: merge Note contextuelle into Backoffice, add Lien BAIU, reorder fields
Backoffice fieldset now contains in order:
1. Note contextuelle (was standalone fieldset)
2. Points du jury
3. Remarques
4. Lien BAIU (moved from Métadonnées complémentaires)
5. Exemplaire physique BAIU
6. Exemplaire physique ERG
7. Contact interne
Métadonnées complémentaires now only has: pages, minutes, annexes checkbox.
Removed dead showContextNote variable from form.php, add.php, edit.php.
Controller baiu_link still mapped to input name "lien" (no migration needed).
3. refactor: move annexes checkbox from Métadonnées into Fichiers fieldset
- Removed 'Ce TFE comporte des annexes' checkbox from
fieldset-metadata.php.
- Added annexes checkbox + conditional file input to
fichiers-fragment.php. When checked, an HTMX swap reveals
the 'annexes' file input (multiple, PDF or ZIP/TAR, max 500 MB).
- form.php seeds ['has_annexes'] for initial fragment render.
- Métadonnées complémentaires now only contains pages + minutes.
- fix: 403 on /language-autre-fragment.php — add explicit nginx location block
The nginx catch-all blocked direct access
to all PHP files except /index.php and files inside /admin/.
language-autre-fragment.php lives at the public root and is POSTed to by
HTMX from both the admin edit form and the partage form. Added an explicit
fastcgi block so it is executed
rather than denied.
- fix: replace .php-suffixed public URLs blocked by nginx catch-all
Audit of all client-facing PHP URL references against nginx routing:
- fetch('/request-access.php') in tfe.php -> '/request-access'
(clean URL already routed by Dispatcher)
- /media.php?path= in form.php (x2) and admin/recapitulatif.php -> /media?path=
(nginx only has location = /media, no location for /media.php)
All these .php-suffixed URLs hit the nginx catch-all
location ~ \.php$ { deny all; }
which takes precedence over location / { try_files ... } for regex matches.
Created templates/partials/form/form.php as the unified form template driven by
$mode ('add'|'edit'|'partage') and boolean flags for optional sections.
The three calling templates (templates/admin/add.php, templates/admin/edit.php,
partage/index.php renderShareLinkForm) now only set variables then include the
shared partial. ~200 lines of duplicated fieldset HTML eliminated.
- Hardcode source code URL and credits in about template, remove from DB/admin interface; only contacts remains editable
- Merge apropos editables into one À propos section, remove charte, add editable source code URL
- split jury into interne/externe/ULB,
- remove president from student form,
- add language_autre,
- split duration into pages+minutes+annexes,
- move licence to degrés d'ouverture with CC2r,
- add license_custom,
- filter PACS from student AP list,
- editable généralités help block,
- Libre toggle per settings
Fix:
- missing comma after cc4r column in schema.sql
- remove duplicate form footer from partage template
- remove couverture from student files fieldset; add promoteur ULB conditional disable via JS on Approfondi
- promoteur ULB: remove 'si applicable', make required when visible
- add exemplaire_baiu, exemplaire_erg, cc4r, remarks;
- add is_ulb to jury;
- split jury_lecteurs into interne/externe in view;
- refactor admin edit form with backoffice fields;
- update public fiche to show promoteur ULB and split lecteurs
- toast-fragment.php: 204 early-exit now also checks flash['warning'];
previously the warning was consumed by consumeFlash() then silently dropped
- partage/index.php: store warning as plain text; htmlspecialchars() applied
once at render time — previously htmlspecialchars() was called inside the
stored string then again at output, producing ' entities etc.
- partage/index.php: flash-warning div gets id + tabindex=-1; inline JS
scrolls it into view and focuses it on DOMContentLoaded
- admin/footer.php: htmx:afterSettle listener focuses .toast--warning after
HTMX injects the toast fragment into #toast-region
- Add DuplicateThesisException (typed, carries existing thesis metadata)
- Add Database::findDuplicateThesis(): matches on year + author + normalised
title (exact, prefix, Levenshtein ≤10% of longer string)
- ThesisCreateController::submit() runs duplicate check before any DB write
and throws DuplicateThesisException on match
- AppLogger::logDuplicate() writes status=duplicate entries to the JSON-lines
log for audit purposes
- App::flash/consumeFlash extended to support 'warning' flash type
- admin/actions/formulaire.php: catches DuplicateThesisException, logs it,
flashes an HTML warning toast with a clickable link to the existing thesis,
and repopulates the form fields
- partage/index.php: same catch block; surfaces a plain-text flash-warning
banner on the student form with identifier, title, and year of the match;
form is repopulated via session
- toast.php: renders toast--warning variant
- admin.css: .toast--warning + link colour rules
- form.css: .flash-warning style for the partage form
- Live file preview on all file inputs (file-field partial, edit template):
thumbnails for images, emoji icons for PDF/video/zip/vtt, filename + size
- New file-preview.js wired via $extraJs in add.php / edit.php and direct
<script> in partage/index.php; $extraJs support added to head.php
- admin/recapitulatif.php: replace plain table with rich file list — image
thumbnails linked to media.php, type badges, human-readable size, date
- partage/recapitulatif.php: full rewrite — shows thesis metadata + files
list with same rich display (no media links for student privacy)
- form.css: new sections for .file-preview-list (live preview) and
.recap-file-list / .recap-dl / .partage-recap (recap pages)
- Drop file-field.php partial for cover/banner (it added a second label)
- Inline all three file inputs with admin-file-input wrapper
- Move banner inside the Fichiers fieldset
- Each entry: one label, one input, one small hint — no duplicate labels
- Context-aware hints: 'Laisser vide pour conserver...' when file already exists
Requirements:
- parametres.php toggle: 'restricted_files_enabled' enables/disables the feature
- Public TFE page: when enabled + access_type=Interne, hides files, shows French
restriction message + access request form (metadata/synopsis still visible)
- ERG emails (@erg.school / @erg.be): auto-approve, send 24h access link immediately
- External emails: show justification textarea, create pending request, notify admin
- Admin panel /admin/file-access.php: approve/reject requests with optional notes,
sends access email on approval (linked from admin nav with pending count badge)
Security:
- One-time 24h email tokens (used_at + is_valid=0 on first click)
- Token redeemed via POST /validate-access (GET shows confirmation page only)
- Long-lived 30-day browser session in file_access_sessions table
- Cookie: HttpOnly + Secure + SameSite=Strict
- CSRF on all mutations, rate limiting on request submission
- Audit trail: IP, UA, event, timestamp in file_access_audit
Bug fixes:
- admin/file-access.php: $vars never extract()ed → page was blank
- Template had self-contained head/footer includes (double-include)
- Admin approval URL used $requestId instead of $request['thesis_id']
- App::boot() now starts session so CSRF token works on public pages
- Dispatcher routes /validate-access and /request-access through front controller