Move the is_published checkbox from its own separate Publication fieldset
into the Backoffice fieldset (as item #8). This means the publish control
is now present in both add and edit admin forms (previously it was only
shown in edit mode via $showPublish).
- Replace mb_strlen/mb_substr/mb_strtolower with strlen/substr/strtolower
(mbstring extension missing on server, causing fatal error)
- Scope annexes checkbox HTMX swap to #annexes-input-block with hx-select
(prevents duplicating entire page inside Fichiers fieldset)
- Split format+fichiers response: #format-fichiers-block (stable) and
#format-extras-block (swappable, inside Fichiers fieldset). Format
checkboxes use hx-select to extract only the extras, preserving file queue.
- Keep format extras inline in Fichiers fieldset (no sub-fieldsets). Remove
website legend input (URL only).
- When PeerTube upload disabled, show direct file upload inputs for
video/audio (name=files[]).
- Add "Glissez-déposez" sort hint below TFE file queue.
- Fix .fq-name overflow with width:0;min-width:100% chain.
- Remove legend placeholder from .fq-item.
- Merge "Récits et expérimentation" AP into "Narration Spéculative".
Rename PACS to "Pratique de lart - outils critiques, arts et contexte
simultanés".
- Remove président·e field from jury fieldset, form templates, and
controller validation. Keep DB column and display logic for existing data.
* **Unified Format + Fichiers into a single HTMX fragment**
* Introduced `app/public/partage/fichiers-fragment.php` as shared dynamic block returning both format checkboxes and adaptive “Fichiers” fieldset
* Logic adapts inputs based on selected formats:
* no selection / upload formats → standard file inputs
* “Site web” → URL fields only
* “Site web + upload” → file inputs + URL sub-fieldset
* Added admin wrapper: `app/public/admin/fichiers-fragment.php` (gated via `admin_mode=1`)
* Added `app/public/admin/format-website-fragment.php` for edit-mode website URL toggling
* Wired route `/partage/fichiers-fragment` in `app/public/partage/index.php`
* Refactored `form.php` (add/edit partage) to use single `#format-fichiers-block` instead of separate fragments
* Edit mode format checkboxes now target `format-website-fragment.php` → `#edit-website-url-fieldset`
* Added `$hxInclude` support in `checkbox-list.php` for configurable HTMX includes
* **Format system migration + ordering**
* Migration `020_format_types_sort_and_rename.sql`:
* added `sort_order` column to `format_types`
* inserted new format **Image**
* defined ordering: Écriture · Image · Audio · Vidéo · Site web · Performance · Objet éditorial · Installation · Autre
* `Database.php`: format queries now use `ORDER BY sort_order, id`
* `fichiers-fragment.php`:
* uses ordered format list
* resolves Image/Vidéo/Audio by name
* introduces `$hasImage` flag
* preserves `admin_mode` across HTMX requests
* **File constraints and UX updates**
* Enforced **100 MB PDF limit**
* `ThesisCreateController`: `MAX_PDF_SIZE = 100MB` for PDFs only
* `ThesisEditController`: same PDF-specific constraint applied
* Other file types remain capped at 500 MB
* Updated UI hints in `fichiers-fragment.php` and edit form:
* explicitly mention 100 MB PDF limit
* added reference to `bentopdf.com` for compression guidance
* `file-field.php`: added `$hintRaw` to allow HTML rendering in hints
* **Admin authentication fix**
* Fixed missing auth in admin fragments
* Added `require_once AdminAuth.php`
* Replaced direct usage with `AdminAuth::requireLogin()`
* Applied consistent pattern with existing fragment authentication approach
* **Migrations included**
* `019_add_ecriture_format.sql`
* `020_format_types_sort_and_rename.sql`
* **Files affected**
* Controllers: `ThesisCreateController`, `ThesisEditController`
* DB layer: `Database.php`
* Public fragments: `partage/fichiers-fragment.php`, `admin/fichiers-fragment.php`, `admin/format-website-fragment.php`
* Templates: `form.php`, `checkbox-list.php`, `file-field.php`
* Routing: `partage/index.php`
* Misc: `TODO.md`
This consolidates format normalization, HTMX UI simplification, file validation rules, and admin stability fixes into a single coherent system update.
- 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
- pad rows, distinguish empty year, better error diagnostics
- derive year from identifier when year column is empty
- fix remaining 18 theses: Installation/Performance (slash→dash) orientation alias
- csv importer: use column-name-based header detection instead of hardcoded positions
- 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
- 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)
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
- migration 014: adds Récits et expérimentation (RE), PACS, sets code NS
on Narration Spéculative; applied to both posterg.db and test.db
- importer (admin/index.php): replaced the code-only ap_programs lookup
(SELECT WHERE code=?) and the orientationMap short-code translation with
two resolver closures that handle the real CSV format (full names):
resolveAP(): alias map for L.I.E.N.S., case variants → exact name
match → code match (legacy) → case-insensitive name match
resolveOrientation(): legacy 2-letter code map → alias map for
Installation/Performance, Arts numériques, Design numérique →
exact name match → case-insensitive name match
All 5 AP values and 13 orientation values from the real CSV now
resolve to correct DB IDs. Legacy short-code CSVs (test.db format)
continue to work unchanged.
- checkbox-list.php: support $required prop → adds required + aria-required on fieldset
- add.php: languages checkbox now marked required (matches server-side validation)
- partage/index.php: same for student form
- admin.css: dashed border on required inputs, bold labels, red asterisk via :has(), "Champs obligatoires" note
- Both forms now show "* Champs obligatoires" note at top
Server-side required fields = titre, auteurice, synopsis, année, orientation, ap, finality, languages (≥1), access_type_id, confirmation_email. All now have required attribute + visual asterisk.