Files
xamxam/TODO.md

205 lines
17 KiB
Markdown

# XAMXAM TODO
## Fix password-protected share links — form never loads after password entry
- [x] `partage/index.php` — main GET handler: check `$_SESSION['share_verified_' . $slug]` before showing password gate; skip to form if already verified
- [x] `partage/index.php` — add `error_log()` calls throughout password flow (gate entry, hash state, verification result, session check) for debugging
## Merge apropos editables into À propos page + remove charte + source code URL
- [x] `actions/apropos.php` — only `contacts`; removed credits, erg_url
- [x] `actions/page.php` — remove `charte` from allowed slugs
- [x] `contenus.php` (front controller) — filter pages to only show `about` + `licenses`
- [x] `templates/admin/contenus.php` — restored "Pages statiques" table
- [x] `contenus-edit.php` (front controller) — `about` slug loads contacts for unified edit
- [x] `templates/admin/contenus-edit.php``about_page` type: Markdown editor + contacts on one page
- [x] `templates/admin/apropos-groups-form.php` — reusable partial for contacts
- [x] `Database.php` — simplified getAproposContent/saveAproposContent (contacts-only JSON)
- [x] `storage/schema.sql` — removed credits, erg_url; only contacts remains
- [x] `AboutController.php` — removed credits, sourceCode DB loading
- [x] `templates/public/about.php` — hardcoded source code URL, hardcoded credits HTML
- [x] `apropos.css``.apropos-toc-source` styles
## Duplicate TFE submission prevention (fixes)
- [x] `DuplicateThesisException` — typed exception carrying existing thesis metadata
- [x] `Database::findDuplicateThesis()` — year + author + normalised-title matching (exact, prefix, Levenshtein ≤10%)
- [x] `ThesisCreateController::submit()` — calls duplicate check before any DB write, throws `DuplicateThesisException`
- [x] `AppLogger::logDuplicate()` — dedicated log action (`status: duplicate`) for audit trail
- [x] `App::flash/consumeFlash` — extended to support `warning` type alongside `error`/`success`
- [x] `admin/actions/formulaire.php` — catches `DuplicateThesisException` separately; logs it; flashes HTML warning with link to existing thesis; repopulates form
- [x] `partage/index.php` — same catch block; plain-text warning (no admin link) surfaced on the student form via `flash-warning` banner; form repopulated
- [x] `toast.php` — renders `toast--warning` block
- [x] `admin.css``.toast--warning` style + link colour
- [x] `form.css``.flash-warning` style (partage form)
## Admin audit logging
- [x] `AdminLogger` class — JSON-lines to `/var/log/xamxam.log` (prod) or `storage/logs/admin.log` (dev), mirrors to `admin_audit_log` DB table
- [x] `admin_audit_log` DB table — created in schema + migrated
- [x] `share_links.is_archived` column — archive replaces delete; stats preserved
- [x] `ShareLink::archive()` — new method; `toggleActive` returns new state; `listActive()` / `listArchived()` split; `validateLink` blocks archived slugs
- [x] `actions/acces-etudiante.php` — delete→archive, all actions logged (create, toggle, set_password, archive)
- [x] `actions/publish.php` — publish/unpublish logged
- [x] `actions/delete.php` — delete / bulk-delete / delete-all logged
- [x] `actions/visibility.php` — visibility changes logged
- [x] `actions/export-csv.php` — CSV export logged
- [x] `actions/export-db.php` — DB export logged
- [x] `actions/edit.php` — TFE edit logged
- [x] `actions/formulaire.php` — TFE add from admin logged
- [x] `actions/tag.php` — rename/merge/delete logged
- [x] `actions/page.php` — static page edits logged
- [x] `actions/apropos.php` — à-propos edits logged
- [x] `actions/form-help.php` — form structure edits logged
- [x] `actions/access-request.php` — approve/reject logged
- [x] `actions/maintenance.php` — maintenance on/off logged
- [x] `actions/settings.php` — formulaire toggles, objet types, SMTP update logged
- [x] `actions/smtp-test.php` — SMTP test logged
- [x] `templates/admin/acces.php` — archive button, archived links collapsible section
- [x] `scripts/setup-server.sh` — provision `/var/log/xamxam.log` with correct ownership
## Multi-author support
- [x] `ThesisCreateController::validateAndSanitise()` — comma-split `auteurice`, sort alphabetically, build author entries array
- [x] `Database::createThesis()` — removed hardcoded `author_id` insert; authors linked via `setThesisAuthors()` instead
- [x] `ThesisEditController::save()` — authors sorted alphabetically before `setThesisAuthors()`
- [x] `Database::findDuplicateThesis()` — accepts `array` of author names, matches any shared author via `IN` + `DISTINCT`
- [x] File folder naming — slug generated from all authors alphabetically sorted (both create and edit)
- [x] `v_theses_full` GROUP_CONCAT — `ORDER BY a.name ASC` for deterministic alphabetical display
- [x] Migration `012_author_view_order.sql` — rebuilds view with alphabetical author ordering
## Fix remote 500s and broken TFE pages (post-deploy)
- [x] `migrations/pending/008_share_links_is_archived.sql``ALTER TABLE share_links ADD COLUMN is_archived` (missing on remote; breaks `acces.php`)
- [x] `migrations/pending/009_admin_audit_log.sql``CREATE TABLE admin_audit_log` (missing on remote)
- [x] `migrations/pending/010_smtp_notify_email.sql``ALTER TABLE smtp_settings ADD COLUMN notify_email` (missing on remote; breaks `parametres.php` via `SmtpRelay::getSettings()`)
- [x] `migrations/pending/011_thesis_files_sort_and_label.sql``ALTER TABLE thesis_files ADD COLUMN sort_order / display_label` (missing on remote; breaks every public TFE detail page)
- [x] `justfile` — added `deploy-migrate` recipe: SSHes to remote and runs `php migrations/run.php`
## Replace browser dialogs with `<dialog>` modals
- [x] `admin/index.php``alert()` (no selection) → `<dialog id="no-selection-dialog">`; `confirm()` bulk publish/unpublish → `<dialog id="bulk-confirm-dialog">`; `confirm()` bulk delete → `<dialog id="bulk-delete-dialog">`; `confirm()` single delete → `<dialog id="delete-thesis-dialog">`; inline `confirm()` on Dépublier button removed (no confirmation needed for reversible action)
- [x] `admin/tags.php``confirm()` merge → `<dialog id="merge-tag-dialog">`; `confirm()` delete → `<dialog id="delete-tag-dialog">`
- [x] `admin/acces-etudiante.php``confirm()` delete link → `<dialog id="delete-link-dialog">`
- [x] `admin/acces.php``confirm()` archive link → `<dialog id="archive-link-dialog">`
- [x] `admin/parametres.php``confirm()` enable maintenance → `<dialog id="enable-maintenance-dialog">`; `confirm()` delete all TFE → `<dialog id="delete-all-tfe-dialog">`; admin password `confirm()` kept with `TODO` comment
- [x] `admin/account.php` — admin password `confirm()` kept with `TODO` comment
- [x] `admin.css` — added `.admin-dialog--sm`, `.admin-dialog__alert`, `.admin-dialog__footer` styles
## Fix 403 on HTMX tab requests in parametres.php
- [x] `AdminAuth::requireLogin()` — now sets `$_SESSION[SESSION_KEY]` when accepting nginx Basic Auth credentials (was returning early without marking the session)
- [x] `AdminAuth::isAuthenticated()` — now falls back to `PHP_AUTH_PW` verification (same logic as `requireLogin`) so HTMX requests to `system-fragment.php` authenticate even before a session exists
## Duplicate warning display fixes
- [x] `toast-fragment.php` — 204 guard now also checks `warning`; warning was silently discarded before
- [x] `partage/index.php` — warning stored as plain text (no pre-escaping); `htmlspecialchars()` applied once at render; was double-encoded before
- [x] `partage/index.php``flash-warning` div gets `id` + `tabindex=-1`; inline JS scrolls and focuses it on load
- [x] `admin/footer.php``htmx:afterSettle` listener focuses `.toast--warning` after HTMX injects the toast fragment
## Sticky save/cancel buttons on edit page
- [x] `templates/admin/edit.php` — moved `.admin-form-footer` from bottom to top-right, right after `<h1>`
- [x] `admin.css` — added `.admin-form-footer--sticky` variant with `position:sticky; top:0; justify-content:flex-end`
## Fix CSV importer column shift and data repair
- [x] Pad rows to expected column count to avoid offset warnings from short rows
- [x] Distinguish `$yearRaw !== ''` before `intval()` to handle empty-year rows correctly
- [x] Improve missing-field error message: lists which fields are missing, includes identifier/title snippet
- [x] Derive year from identifier when year column is empty
- [x] Auto-detect column-shifted CSV: when orientation/finality columns are empty but synopsis/context match known orientation/finality names, remap on import
- [x] Migration `013_fix_csv_column_shift.sql`: move orientation from synopsis→orientation_id, finality from context_note→finality_id for already-imported theses
- [x] Migration `013_fix_remarks_keywords.php`: move keywords from remarks→tags+thesis_tags for already-imported theses
## Support website-type TFE (URL instead of uploaded files)
- [x] Add `file_type = 'website'` support to `thesis_files` — URL stored in `file_path`, no filesystem upload (no schema change needed)
- [x] Admin add form: "Site web (URL)" field dynamically shown via HTMX when "Site web" format checked
- [x] Admin edit form: website URL field via HTMX toggle + recognize website (🌐 icon) in existing-files list
- [x] Student partage form: website URL field via HTMX toggle + HTMX script added
- [x] TFE detail page (`tfe.php`): render `website` type as iframe with sandbox in media section
- [x] `ThesisCreateController`: handle website URL in submit → `handleWebsiteUrl()` stores as thesis_files row
- [x] `ThesisEditController`: handle website URL in save → `handleWebsiteUrl()` replaces existing website row; delete-files skips unlink for URLs
- [x] Edit page: website rows deletable via same delete_files checkbox mechanism
- [x] File size 0 for website rows — hidden in edit list to avoid showing "0.00 MB"
- [x] HTMX fragment endpoint: `/admin/actions/format-website-fragment.php` + `/partage/format-website-fragment`
- [x] `checkbox-list.php` partial: optional `hxPost`/`hxTarget` for HTMX live update
- [x] Server-side initial render: pre-populate `#website-url-section` if "Site web" already checked
## Standardise répertoire filter column rendering
- [x] Centralise filter column rendering into a shared `repFilterEntry()` function
- [x] Define `$filterColumns` config array as single source of truth for the 5 filter columns
- [x] All columns (years, ap, or, fi, kw) now share identical fade/select/HTMX logic via the same code path
- [x] Fix single-valued FK columns (years, ap, or, fi): matched entries now use full intersection so clicking one entry correctly fades others with zero results
- [x] Fix column ordering: students between finalité and mots-clés
## Add spacing between form elements inside fieldsets
- [x] `common.css``fieldset > *:not(:last-child) { margin-bottom: var(--space-xs); }`
- [x] `admin.css``.param-form fieldset > * { margin-bottom: 0; }` to avoid double-spacing with flex gap
## Standardise buttons with .btn base class
- [x] Create `.btn` base class in common.css: `border-radius: 10px; padding: var(--space-xs)` + background + cursor
- [x] Add `.btn--primary` (accent bg), `.btn--secondary` (--bg bg + border), `.btn--sm`, `.btn--lg`, `.btn--danger`, `.btn--warning`, `.btn--success`, `.btn--ghost`, `.btn--muted`, `.btn--blue`, `.btn--yellow`, `.btn--green`, `.btn--red` modifiers
- [x] Replace old button definitions in admin.css, form.css, tfe.css, file-access.css, system.css with empty alias comments
- [x] Update all PHP templates to use new `.btn` classes (`btn btn--primary`, `btn btn--secondary`, `btn btn--danger`, etc.)
- [x] Update border-radius on pagination buttons to 10px for consistency
- [x] Exclude `storage/maintenance.flag` from rsync deploy and git
## Admin file export system
- [x] `ExportController`: add `getAllThesisFiles()`, `buildExportManifest()`, `createExportZip()` — gathers all thesis files, creates zip with files/ + manifest.json
- [x] `admin/actions/export-files.php` — thin dispatcher, streams zip, logs audit
- [x] `AdminLogger::logFilesExport()` — audit log entry for file exports
- [x] `templates/admin/index.php` — add "Exporter fichiers" button next to CSV export button
- [x] `Database::getAllThesisFilesForExport()` — query all thesis_files + identifier
- [x] `docs/export.md` — documentation en français pour administrateurs : fonctionnement, contenu du ZIP, procédure de restauration complète et partielle
## Validate & refactor TFE form fields (new spec)
- [x] Schema: add `exemplaire_baiu` and `exemplaire_erg` boolean columns to `theses`
- [x] Schema: add `jury_promoteur_ulb` boolean column to `thesis_supervisors`
- [x] Schema: persist `cc4r` checkbox in `theses` table
- [x] Schema: update `v_theses_full` view to include new columns + jury split (interne/externe/ULB)
- [x] Admin edit form: add `jury_points`, `remarks`, `exemplaire_baiu`, `exemplaire_erg` fields (Backoffice fieldset)
- [x] Admin edit form: add `promoteur_ulb` checkbox in jury fieldset
- [x] Admin edit form: reorder fields to match spec layout
- [x] Public TFE fiche: split lecteur·ice(s) into interne/externe
- [x] Public TFE fiche: add promoteur·ice ULB display
- [x] ThesisEditController: handle new fields in save()
- [x] ThesisCreateController: handle CC4r + is_ulb in jury methods
- [x] Database::updateThesis: include new columns (remarks, jury_points, exemplaire_baiu, exemplaire_erg, cc4r)
- [x] Database::setThesisJury: include is_ulb column
- [x] Database::getThesisJury: include is_ulb in SELECT
- [x] StudentEmail: use new jury_lecteurs_internes/externes and jury_promoteurs_ulb columns
- [x] Recapitulatif: show promoteur·ice ULB and lecteur·ices interne/externe
- [x] Migration: `014_tfe_form_fields.sql` — ALTER + view rebuild
- [x] Fix `Call to undefined function old()` in admin edit page — define `old()` in `app/public/admin/edit.php` (was only in `add.php`)
- [x] Add Note contextuelle and Backoffice fieldsets to admin add form (matching edit form)
- [x] `Database::createThesis()` — add `context_note`, `remarks`, `jury_points`, `exemplaire_baiu`, `exemplaire_erg`, `cc4r` columns
- [x] `ThesisCreateController::validateAndSanitise()` — handle new admin-only fields
- [x] `ThesisCreateController::submit()` — pass new fields to `createThesis()`
- [x] Replace admin E-mail de confirmation fieldset with Contact interne in Backoffice section (add + edit)
- [x] Remove confirmation email sending from add/edit (admin never sent; student partage unchanged)
## Refactor form structure per spec (student vs admin)
- [x] Remove `jury_president` field from student-facing forms (edit keeps it as optional)
- [x] Jury: split into promoteur·ice interne, promoteur·ice ULB, lecteur·ice interne, lecteur·ice externe — each with +add button
- [x] AP filtering: student form hides PACS; admin form shows all APs
- [x] Language: add "autre" text field alongside predefined checkboxes
- [x] Duration: split into pages field + minutes field + annexes checkbox
- [x] Licence: removed from métadonnées fieldset, now only in degrés d'ouverture section
- [x] Degrés d'ouverture: généralités FIRST, then radio choice (Libre/Interne/Interdit), then licence dropdown + custom text + CC2r
- [x] CC4r → CC2r rename in form UI + `cc2r` POST name
- [x] Licence: custom licence text field added (`license_custom` DB column)
- [x] Généralités text editable via form help block (`fieldset_generalites`)
- [x] Libre option: hidden in student form when disabled in settings; always shown in admin
- [x] Contact: `contact_visible` field always present in TFE info; admin gets `contact_public` checkbox separately
- [x] Email confirmation: mandatory in student form, optional in admin
- [x] Fichiers: cover image hint updated to 4:3 ratio
- [x] All three form pages (admin add, admin edit, partage) updated
- [x] Controllers updated: `collectJuryMembers`, `validateAndSanitise`, `buildFileSizeInfo`, `license_custom`, `cc2r``cc4r` mapping
## Fix form field required states & missing fields per spec
- [x] Admin add: add `contact_public` checkbox (matching edit form)
- [x] Admin add + partage + admin edit: formats checkbox-list `$required = true`
- [x] All forms: jury promoteur·ice interne `required` attribute
- [x] All forms: jury lecteur·ice interne `required` attribute (at least one)
- [x] All forms: jury lecteur·ice externe `required` attribute (at least one)
- [x] All forms: licence select `$required = true`
- [x] Admin edit: add "E-mail de confirmation" fieldset
- [x] Partage: contact always visible (POST handler defaults `showContact` to true when no `contact_public` key present)
- [x] Partage: filter PACS from AP programs dropdown
- [x] Verify no duplicate asterisks on any field
- [x] Admin add: `contact_public` POST handling in ThesisCreateController for admin submissions
- [x] Server-side validation: formats required, jury members required, licence required (ThesisCreateController + ThesisEditController)
- [x] Autofocus mappings for new validation errors (format, jury, licence)