- 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
- shared repFilterEntry() and config array
- shared repFilterEntry() and $filterColumns config array
- fix single-valued FK fading via full intersection
- 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
fieldset-academic.php, fieldset-metadata.php and fieldset-licence-explanation.php
were each calling unset($formData) (or wrong variable) in their cleanup block,
destroying the variable in the parent renderShareLinkForm() scope. This caused
an Undefined variable / TypeError on old($formData, ...) for any field rendered
after those partials (e.g. confirmation_email at line 328).
Fix: remove $formData from the unset() calls; fieldset-licence-explanation.php
was also unsetting the wrong name — corrected to unset($n) which is the variable
it actually declares.
- 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