# XAMXAM TODO
## Completed
- [x] PeerTube integration — two parallel systems (backup direct upload + PeerTube API)
- [x] `PeerTubeService.php` — credentials CRUD + OAuth2 password grant + multipart upload to `/api/v1/videos/upload`
- [x] Migration `021_peertube_settings.sql` — `peertube_settings` table (singleton) + `peertube_upload_enabled` feature flag (default 0 = disabled)
- [x] `actions/settings.php` — `peertube` section handler (toggle + credential save)
- [x] `admin/parametres.php` — PeerTube section UI (instance URL, username, password, channel ID, privacy)
- [x] `templates/admin/parametres.php` — PeerTube settings form between SMTP and admin account sections
- [x] `admin/partage/fichiers-fragment.php` — shows `` for video/audio when enabled, keeps TODO notice when disabled
- [x] `ThesisCreateController` — `handlePeerTubeUpload()` uploads video/audio to PeerTube, stores watch URL as `thesis_files` row
- [x] `ThesisEditController` — same `handlePeerTubeUpload()` method for edit workflow
- [x] `templates/public/tfe.php` — renders PeerTube iframe embed for files whose path contains `/videos/watch/`
- [x] `AdminLogger` — `logPeerTubeUpdate()` audit method
- [x] Direct file upload fallback: when `peertube_upload_enabled = 0`, standard `` + local storage works unchanged
- [x] Fix `just serve` — justfile shebang recipes (`deploy-env`, `reencrypt-password`) used space indentation instead of tabs, causing "extra leading whitespace" parse error
- [x] PDF 100 MB limit + bentopdf mention
- [x] `ThesisCreateController`: `MAX_PDF_SIZE = 100 MB`; PDFs checked against it, other files still 500 MB
- [x] `ThesisEditController`: same per-PDF limit applied
- [x] `fichiers-fragment.php`: note d'intention and TFE hints mention 100 MB PDF limit + bentopdf.com link
- [x] `form.php` edit-mode new-files hint updated
- [x] `file-field.php`: added `$hintRaw` flag to allow HTML in hints
- [x] Format types: reorder, rename, add Image/Écriture
- [x] Migration 019: add Écriture
- [x] Migration 020: add `sort_order` column, rename Autre → Etc. / Autre, add Image, set display order (Écriture · Image · Audio · Vidéo · Site web · Performance · Objet éditorial · Installation · Etc. / Autre)
- [x] `Database.php` format_types query uses `ORDER BY sort_order, id`
- [x] `fichiers-fragment.php` uses `ORDER BY sort_order, id`; Image/Vidéo/Audio IDs resolved via name map
- [x] TODO: Vidéo + Audio — PeerTube API upload (notice shown in form for now)
- [x] Combined Format + Fichiers into HTMX-swappable block
- [x] `partage/fichiers-fragment.php` — new combined fragment: format checkboxes + fichiers fieldset that adapts based on selected formats (upload inputs / URL fields / both)
- [x] Route `/partage/fichiers-fragment` added to `partage/index.php`
- [x] `admin/fichiers-fragment.php` — admin-gated wrapper for the same fragment (sets `admin_mode=1`)
- [x] `admin/format-website-fragment.php` — admin-gated fragment for edit-mode website URL fieldset toggle
- [x] `form.php` — add/partage mode: replaced separate Format + Fichiers + website-url-fieldset with single `#format-fichiers-block` server-rendered via shared fragment
- [x] `form.php` — edit mode: Format checkboxes wire to `admin/format-website-fragment.php` → `#edit-website-url-fieldset` (existing-file management untouched)
- [x] `checkbox-list.php` — added `$hxInclude` variable (defaults to `'this, #website-url-fieldset'`) so callers can customise included fields
- [x] TDD analysis + new test suites
- [x] **Bug fixed**: `SearchController::handleSearch()` — `$coverMap` undefined variable + never populated for search results
- [x] `ShareLinkTest` (13 tests) — `generateSlug`, all `validateLink` branches, `verifyPassword`, `incrementUsage`, `objet_restriction`
- [x] `PureLogicTest` (31 tests) — `TfeController` helpers (meta, OG image, jury split, captions), `ThesisCreateController` helpers (autofocus, detectFileType, authorSlug), `ThesisEditController::buildFileSizeInfo`, `ExportController` CSV column consistency, `SearchController` coverMap regression
- [x] Private helpers promoted to `protected` in `TfeController`, `ThesisCreateController`, `ThesisEditController` to enable subclass-based testing without reflection
- [x] Form save audit + TDD
- [x] `createThesis()` missing `duration_pages`/`duration_minutes` columns — fixed
- [x] `ThesisCreateController` not passing raw page/minute values to `createThesis()` — fixed (`durationPages`, `durationMinutes` extracted and passed)
- [x] `FormSaveTest.php` — 14 red-green tests covering create+edit round-trips for all fields
- [x] Language form improvements
- [x] Add Néerlandais as default language option (schema + migration 017)
- [x] `language_autre` conditionally required via HTMX fragment (replaced custom JS)
- [x] `language_autre` saved via `getOrCreateLanguage()` in both create and edit controllers
- [x] `formData['languages']` wired in edit.php so checkboxes are pre-checked
- [x] `duration_pages`/`duration_minutes` saved in `updateThesis()` and read back in `getThesisRawFields()`
- [x] `beforeunload-guard` applied to add and partage forms too
- [x] Audit + fix direct PHP URL references blocked by nginx catch-all `deny all`
- [x] `/request-access.php` fetch in `tfe.php` → `/request-access`
- [x] `/media.php?path=` in `form.php` (×2) and `admin/recapitulatif.php` → `/media?path=`
- [x] Fix 403 on `/language-autre-fragment.php` from `edit.php`
- [x] Root cause: standalone root-level PHP file blocked by nginx catch-all `deny all`
- [x] Moved logic to `partage/language-autre-fragment.php` (shared include)
- [x] Added route `/partage/language-autre-fragment` in `partage/index.php`
- [x] Added `admin/language-autre-fragment.php` (AdminAuth gated, includes shared logic)
- [x] `form.php` picks URL based on `$mode` (`partage` vs admin)
- [x] Deleted `public/language-autre-fragment.php`; nginx unchanged
- [x] Merge banner images into cover images
- [x] Migration 016: copy `storage/banners/*` → `storage/covers/`, insert `thesis_files` cover records, clear `banner_path`, remove banners dir
- [x] Remove banner fieldset from edit form (`form.php`)
- [x] Remove banner fieldset from student submission form (`fieldset-files.php`: rename to couverture)
- [x] Update `ThesisEditController::save()` — remove banner upload/removal logic
- [x] Update `ThesisCreateController::submit()` — remove `handleBannerUpload` call
- [x] Update `Database::handleCoverUpload()` — add webp support, raise limit to 20 MB
- [x] Remove `Database::setBannerPath()`, `handleBannerUpload()`, `getThesisBannerPath()`
- [x] Update `Database::deleteThesis()` / `bulkDeleteTheses()` — remove banner file cleanup
- [x] `HomeController`: batch-load covers for all items, remove banner_path fallback
- [x] `SearchController::handleSearch()`: batch-load covers, pass `$coverMap` to view
- [x] `SearchController::handleStudentPreview()`: load covers, pass `$coverMap` to partial
- [x] `TfeController::resolveOgImage()`: use cover file_type instead of banner_path
- [x] `home.php`: use only `$coverMap` (no banner_path fallback)
- [x] `search.php`: show cover thumbnail on result cards
- [x] `student-preview.php`: use `$coverMap` instead of `banner_path`
- [x] Migration applied and file moved to `applied/`
- [x] Remove `required` from all form inputs in admin add/edit
- [x] Introduced `$adminMode` flag in `form.php` (true when `$mode` is `'add'` or `'edit'`)
- [x] Hidden "champs obligatoires" note in admin mode
- [x] All `$required = true` callers in `form.php`, `fieldset-tfe-info.php`, `fieldset-academic.php`, `fieldset-licence-explanation.php`, `fieldset-files.php` changed to `!$adminMode`
- [x] Hardcoded `required` HTML attributes in `fieldset-tfe-info.php` (synopsis, objet radios), `fieldset-licence-explanation.php` (access type radios), `jury-fieldset.php` (promoteur, lecteurs interne/externe) gated on `!$adminMode`
- [x] Dynamic JS `ulbInput.required` in jury fieldset also gated