- Remove Mo option from duration, keep only pages and minutes
- Redesign duration fieldset: separate Pages input + Durée h:m inputs, both can be set together
- Fix minutes input visibility: wider inputs (6ch), proper CSS layout
- Add has_annexes checkbox to fichiers fragment + DB column + controllers
- Display duration on admin backoffice recap page
- Display duration on public partage recap page
- Update public TFE page for new combined duration format
- Migration 041: add duration_pages, duration_minutes, has_annexes columns; migrate data; recreate views
Also introduces $extraCssAdmin support in head.php for admin-only
stylesheets (form-admin.css, filepond CSS, system.css). Admin pages
now use $extraCssAdmin for admin-only assets and $extraCss for
shared stylesheets like form-base.css.
- New fragment endpoint POST/GET /partage/fragments/draft.php:
saves all form fields to PHP session, excludes file/csrf/slug fields
GET returns JSON for JS hydration on page load
rotates both global CSRF and share CSRF tokens in sync
- form.php accepts optional $formExtraAttrs and $showAutosaveStatus:
allows injecting HTMX attributes and 'Brouillon enregistré' indicator
- renderShareLinkForm adds hx-post with change/input debounce trigger,
loads autosave-handler.js, hydrate fields from draft on page load
- Draft cleared on successful form submission in handleShareLinkSubmission
- autosave-handler.js now also updates share_link_token hidden input
when rotating CSRF token (partage form uses both csrf_token and share_link_token)
- Added .autosave-status CSS to form.css (was admin.css-only)
- Updated fragment routing to accept GET requests (needed for draft hydration)
The partage/admin form had a hardcoded filepond_mode=1 hidden input,
so without JavaScript the server always entered the FilePond async
path — which found no hex IDs and silently dropped all files.
Three-layer fix:
1. HTML: filepond_mode input starts disabled with value=0; JS enables
it and sets value=1 on DOMContentLoaded (and after HTMX swaps).
Disabled inputs aren't submitted → server gets no filepond_mode
→ naturally falls to legacy path.
2. JS: enableFilepondMode() called on page load and hx:afterSwap so
FilePond-enhanced forms always send filepond_mode=1.
3. Server (defense-in-depth): ThesisFileHandler::hasFilePondQueueData()
scans POST['queue_file'] for 32-char hex IDs; ThesisCreateController
and ThesisEditController use it alongside filepond_mode, so even if
the flag somehow arrives without async upload IDs, the path
takes over.
ThesisEditController::save() previously only regenerated the identifier when
the year field changed during an edit. If a thesis had its year corrected in
a past edit (or via other means) and the identifier still carried the old
year prefix, subsequent edits that didn't touch the year field would leave
the mismatched identifier in place.
Now saves() also checks whether the existing identifier's 4-digit prefix
matches the thesis year, and regenerates if not — regardless of whether year
changed in the current edit.
The migration runner (run.php) only scanned for .sql files, so PHP migrations
(013, 016, 018, 038) were never auto-applied. Extended the runner to also
discover and execute .php migrations in a subprocess. If a PHP migration fails
with an idempotent error (no such column, already exists, duplicate column),
the runner treats it as already-applied and continues rather than aborting
— preventing a stale migration like 016 (banner_path already dropped by 028)
from blocking migrations that come after it alphabetically (e.g. 038).
Updated migrations 016 and 038 to accept an optional $argv[1] DB path.
Fixed 016 to gracefully handle the banner_path column already being gone
(exit 0 instead of fatal).
- Fix#1: Add is_published to getThesisRawFields() SELECT so the publish
checkbox stays checked when editing an already-published TFE.
- Fix#2: Rename 'Note contextuelle' → 'Note contextuelle relative à
soutenance' in all templates and StudentEmail.
- Fix#3: Update findOrCreateAuthor to also UPDATE the author name when
a record is found by name (fixes inability to capitalise names).
- Fix #4/#5: Decouple contact_interne (private author email) from
contact_visible (public contact on TFE page). Add migration 037 to
add contact_visible TEXT column to theses table and rebuild
v_theses_full view. Update all controllers, templates, and DB methods
to treat them independently.
- Fix#6: Investigated libre→interne restriction — no code barrier
found; likely resolved by is_published fix.
- Identifiant: mise à jour automatique quand l'année change en back-office (updateThesis + ThesisEditController)
- Contact: hint enrichi (1 seul contact, formatage Instagram/Mastodon)
- Fichiers: TFE rendu optionnel pour Site web/Performance/Installation (note d'intention reste obligatoire)
1. note_intention: Delete old file only when a genuinely new upload arrives
(32-char hex file_id), not when the FilePond pool preserves an existing
file by sending its DB integer ID. Previously the DB integer ID
triggered $hasNewNote=true, which deleted the existing note_intention
from disk+DB, then handleFilePondSingleFile couldn't re-process it
because the regex requires a hex pattern. Same fix applied to cover.
2. All file deletions now use deleteThesisFileToTrash() which renames
files to tmp/_trash/ instead of unlinking. The trash preserves
original filenames prefixed with DB id for traceability. Skips
website URLs and PeerTube refs (no disk file).
3. Storage prefix changed from theses/ to documents/ to reflect that
the folder holds all document types (determined by file_type in DB).
MediaController visibility gate supports both prefixes for backward
compat with existing files.
4. File browser + relink feature for orphaned files:
- /admin/fragments/file-browser.php — HTMX tree browser for
storage/documents/ and storage/theses/
- /admin/actions/filepond/relink.php — POST endpoint that inserts
a thesis_files row pointing to existing on-disk file
- Per-pool "📂 Relier" buttons (edit mode only)
- JS: XamxamOpenFileBrowser / XamxamRelinkFile with FilePond integration
- CSS: .relink-modal dialog + .file-browser tree styles
- Remove separate video/audio/peertube_video/peertube_audio pools from UI
- TFE pool now accepts all file types including video/audio
- When PeerTube is enabled, video/audio dropped into TFE pool auto-upload
to PeerTube (process.php detects MIME and uploads immediately)
- PeerTube return IDs now encode type: peertube:video:UUID or peertube:audio:UUID
- load.php returns placeholder SVG for PeerTube files so they appear in FilePond
- Edit mode: all existing files (including PeerTube) shown in TFE FilePond pool
- Remove legacy video/audio/peertube_* handling from both controllers
- Remove unused vide/audio/peertube_* entries from JS QUEUE_CONFIG
ErrorHandler::userMessage only handled RuntimeException, but all validation
throws in ThesisCreateController and ThesisEditController use plain Exception.
This caused user-friendly messages like 'Le champ Nom/Prénom/Pseudo est requis'
to fall through to the 'Une erreur inattendue est survenue…' generic message.
Fix: add Exception check (after PDOException, since PDOException extends it)
so all validation exceptions pass their message through.
Drops the session-backed HTMX incremental upload system in favour of a
single JS module that manages `File` objects client-side and injects
them into `FormData` on submit.
Key changes:
* `file-upload-queue.js`: client-side queues with validation, reorder
(SortableJS), removal, dirty-state tracking, and fetch-based submit
with manual redirect handling
* `fichiers-fragment.php`: empty queue containers for JS-managed queues;
HTMX format switching still works with queue rehydration after swap;
annexe uploads now support multiple files
* Form UI cleanup: moved existing files and cover preview into the
`Fichiers` fieldset (edit mode); removed redundant queue labels while
keeping labels for single-file inputs (`couverture`,
`note d'intention`); added delete buttons for existing files
* `ThesisFileHandler.php`: added
`handleTfeQueueFiles()`/`handleAnnexeQueueFiles()` reading from
`$_FILES['queue_file']`; introduced `extractFilesSubArray()` for
nested upload arrays; removed session-based queue handling
* `ThesisCreateController.php` &
`ThesisEditController.php`: switched to extracted
`['queue_file']` uploads
* `beforeunload-guard.js`: now also watches
`window.__xamxamDirty`
* Deleted obsolete PHP upload/remove/reorder queue endpoints for
`partage` and `admin`
* Cleaned up route dispatch in `partage/index.php`
* Misc form and styling updates in templates/CSS
* Added `docs/cms-migration-plan.html`
Replace the client-side FileArray + Sortable drag-to-reorder with a
server-side session-based upload flow:
- New endpoints: /partage/upload-tfe-file, /partage/remove-tfe-file
(and /admin/ variants) — single-file incremental upload via HTMX
multipart/form-data with progress bar support
- Session storage: uploaded files go to STORAGE_ROOT/uploads/{session_id}/
with metadata in $_SESSION['tfe_uploads']
- file-upload-queue.js reduced to single-file previews only (couverture,
note_intention, annexes thumbnails)
- ThesisFileHandler gains handleTfeFilesFromSession + writeTfeFileFromSrc
+ cleanupSessionUploads for final commit from session temp
- Sortable.min.js removed from all script tags; drag handles and ghost
CSS removed
- No file_orders[]/file_labels[] hidden field injection needed
- Upload queue survives page refresh (server-owned list)
This eliminates the SortableJS dependency entirely while keeping the
same UX: pick files, see them in a queue, remove individual files.
The share link (partage) form does not expose a license field and does
not send access_type_id (defaults to 2/Interne). Server-side validation
was unconditionally requiring a license for non-admin submissions,
causing all share link submissions to fail.
Now the license check is gated on adminMode=false AND accessTypeId=1
(Libre), matching the client-side HTMX fragment behaviour in
licence-fragment.php. Also fixed a use-before-definition where
accessTypeId was referenced before being assigned.
Student form improvements:
- Add xamxam@erg.be mailto link at top of form
- On validation error, append "Si le problème persiste, envoyez un
e-mail à xamxam@erg.be" to the flash message
- Preserve uploaded file names across validation redirects: store in
session (share_primed_files_<slug>), display as warning on form
re-render so the student knows which files to re-select
- License: only required for non-admin when access_type_id=1 (Libre),
not for Interne (2) or Interdit (3). Fixes share link submissions
failing with "Veuillez sélectionner une licence". Also fixed
use-before-definition of accessTypeId.
Mirrors the mots-clé tag-search system: dropdown suggestions from
existing languages via HTMX, pill display with bin-icon remove buttons,
'Créer' option for new languages. Replaces the plain text input.
- New partial: templates/partials/form/language-search.php
- New fragment: public/partage/language-search-fragment.php
- Admin wrapper: public/admin/language-search-fragment.php
- Updated language-autre-fragment to return just the required asterisk indicator
- Updated both controllers to handle language_autre as array (pill-based)
with backward-compatible string path
- Updated edit form to compute selectedOtherLanguages from DB
- Registered new route in partage/index.php
- Fix CSV importer: split comma-separated language column into individual entries
- Add htmx active search to admin index, title line-clamp, predefined languages only in checkboxes
- Admin index: filter form now uses htmx triggers (input delay:300ms on search,
change on selects) to actively search without page reload
- Sort links include hx-push-url for back-button support
- Added loading indicator bar (.admin-search-indicator)
- Title column: line-clamp at 2 lines with overflow hidden, native title attr
tooltip for full text
- Language checkboxes now show only 3 predefined languages (Français, Anglais,
Néerlandais); all others go via the Autre langue search component
- Added Database::getPredefinedLanguages() and excluded predefined from
language-search-fragment suggestions
- Included hidden sort/dir inputs in table-wrap so sort state preserved across
filter changes
- Fix language-search: block 'Créer' for predefined languages in dropdown
The 'Créer' option in the language-search dropdown now also checks against the
predefined set (français, anglais, néerlandais) to avoid offering creation of
languages that already exist as checkboxes.
- v_theses_full: author_email→contact_interne, author_show_contact→contact_public
- Updated schema.sql and live DB view
- Renamed all PHP variables: currentAuthorEmail→contactInterne, currentAuthorShowContact→contactPublic
- Restored contact_interne backoffice field with proper wiring (takes precedence over mail field)
- Updated admin/add.php, admin/edit.php, partage/index.php, public/tfe.php templates
- ErrorHandler tests: 77 assertions covering FK extraction, normalization, dedup, edge cases. Fix FK table map for child tables.
- Fix FK violation: (int)null → 0 in createThesis for orientation/ap/finality/license FK columns. Add FK value logging to updateThesis.
- Add CURRENT_ISSUES.md with summary of FK violation, dev debugging, and tag dedup status for next conversation
- 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.
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.
* **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.
- 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
- ThesisCreateController: comma-split auteurice, sort alphabetically,
use setThesisAuthors() instead of hardcoded createThesis() author_id
- Database::createThesis(): removed author_id param and hardcoded insert
- Database::findDuplicateThesis(): accepts array of author names, matches
any shared author via IN + DISTINCT
- ThesisEditController::save(): sort authors alphabetically on save
- File folder naming: slug from all authors alphabetically sorted
- v_theses_full GROUP_CONCAT: ORDER BY a.name ASC for deterministic display
- Migration 012_author_view_order.sql: rebuilds view with alphabetical order
- 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