Commit Graph

100 Commits

Author SHA1 Message Date
Pontoporeia
cc0ae32df0 fix: resolve partage form submission issues
- 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.
2026-05-19 00:08:05 +02:00
Pontoporeia
59bbcf4642 css: moved + tweaked styles to common.css
- Add baseline input[type="checkbox"] and input[type="radio"] styling
  in common.css (accent-color, size, cursor, flex-shrink)
- Give select a solid background (var(--bg-primary)) and its own focus rule
- Remove now-redundant checkbox accent-color/size from
  .admin-checkbox-label (form.css) and .param-checkbox (admin.css)
- Simplify .search-filter-select (repertoire.css) to inherit common
  select defaults (border, background, arrow icon)
- Keep all layout-specific classes in form.css and admin.css intact
- Add baseline input[type="checkbox"] and input[type="radio"] styling
  in common.css (accent-color, size, cursor, flex-shrink)
- Give select its own rule block with same shape as text inputs
  (transparent background, same padding/border/radius/focus)
- Remove now-redundant checkbox accent-color/size from
  .admin-checkbox-label (form.css) and .param-checkbox (admin.css)
- Simplify .search-filter-select (repertoire.css) to inherit common
  select defaults
- Keep all layout-specific classes in form.css and admin.css intact
- Remove bottom-border/border-radius:0 overrides from .admin-form,
  .admin-inline-form, .param-form, and .param-grid inputs/selects
- Change required-field indicator from border-bottom-style to
  border-style: dashed to work with full-border approach
- Update param-grid aria-invalid from border-bottom-color to border-color
- All text inputs, selects, and textareas now inherit the full-border
  style from common.css (border, border-radius, padding, focus ring)
- .password-gate input[password]: remove redundant padding override
- .retry-email-form input[email]: remove redundant border/border-radius/
  padding/box-sizing, keep only font-size (larger) and width
- .tfe-access-request-form input/textarea: remove broken references to
  undefined vars (--border, --background, --accent), now inherit from
  common.css. Remove redundant focus rule.
- .fhb-name-input: strip redundant padding/border/radius/font-size/font
- .admin-inline-form input/select: strip redundant font-size
- .param-checkbox: remove font-size (inherits from body)
- .param-checkbox small: remove redundant color + font-size (common.css small already sets both)
- .param-note: remove font-size
- .param-account-status: remove font-size
- .param-smtp-test-row label: remove display:block + font-size (common.css label)
- .param-smtp-status: remove font-size
- .param-grid label: remove font-size
- Remove .param-form legend padding override (now inherits common.css legend)
- Remove .param-danger-zone legend padding override
- Remove .param-export-zone legend padding override
- Remove .param-fieldset-inline legend entirely (only rule was padding)
- Remove .licence-explanation legend entirely (all properties identical to common.css legend)
- All fieldsets now consistently use common.css fieldset padding
  (0 var(--space-m) var(--space-m) var(--space-m))
- The common.css fieldset has padding-top: 0, which leaves checkboxes
  and other content tight against the legend. Add var(--space-s) top
  padding so the first content row has proper spacing from the legend.
2026-05-19 00:08:05 +02:00
Pontoporeia
21c2b55bfb style: normalize headers, overtype editor rounded corners, remove duplicate cover preview, thesis-add-header grid layout, subtitle below header with top gradient 2026-05-19 00:08:05 +02:00
Pontoporeia
7ccadbb224 refactor public search bar
- one big input with positioned magnifying glass icon
- fix search input left padding to prevent placeholder overlapping magnifying glass
- add !important to search input styles to override base form element rules
- reduce search input vertical padding
- bump search input vertical padding to space-2xs
2026-05-19 00:08:05 +02:00
Pontoporeia
862ed02136 style: unify form element styles in common.css, redesign focus rings, refactor public search bar, tweak admin section 2026-05-19 00:08:05 +02:00
Pontoporeia
77fd282e29 refactor: unify edit mode Format+Fichiers with add/partage HTMX fragment
- Edit mode now uses the same fichiers-fragment.php as add and partage,
  instead of duplicating the format checkboxes + new-file upload + website
  URL fieldsets.
- Edit-only elements (existing files list, cover replace) stay in
  a separate #edit-existing-files-block below the shared fragment.
- Removed .zip/.tar/.gz from the main TFE upload accept in both
  fichiers-fragment.php and fieldset-files.php. Archives go only
  in the Annexes file input.
- Removed admin/format-website-fragment.php dependency from edit
  (no longer needed — the shared fragment handles website too).

fix: jury repop crash + hx-preserve on file inputs, remove zip/tar from tfe accept

- Jury fieldset add-mode repopulation now handles both scalar (legacy)
  and array (new dynamic multi-row) values for jury_promoteur and
  jury_promoteur_ulb_name. htmlspecialchars() was choking on array value.
- All file inputs in fichiers-fragment.php wrapped in hx-preserve
  containers so HTMX swaps don't wipe user-selected files when toggling
  formats or the annexes checkbox.
- Removed .zip/.tar/.gz from main TFE file accept — archives only via
  annexes input (which already had multiple + correct accept).
- Edit mode now reuses the same fichiers-fragment.php fragment.

fix: file inputs re-initialize after HTMX swap via inline script

- Exposed window.XamxamInitFileUploads from file-upload-queue.js IIFE
  so HTMX fragments can trigger re-binding without a global listener.
- fichiers-fragment.php emits <script>XamxamInitFileUploads()</script>
  at the end of the #format-fichiers-block fragment.
- Removed hx-preserve wrappers — they prevented re-render after
  format/annexes toggles changed visible inputs.
- This also fixes .zip removal from TFE accept and jury repopulation
  array crash from the previous commit.

refactor: simplify file-upload-queue.js, remove file-preview.js

- file-upload-queue.js rewritten from ~250 lines to ~120 lines:
  no more DataTransfer machinery, no IIFE wrapper, uses .onchange
  instead of addEventListener for simpler HTMX re-init.
- window.XamxamInitFileUploads is the function itself (not an IIFE export).
- Merged file-preview.js functionality into file-upload-queue.js
  (single-file .data-preview handling). Deleted file-preview.js.
- fichiers-fragment.php inline script calls XamxamInitFileUploads()
  after every HTMX swap (same as before).

debug: add console.log to file-upload-queue.js for file input behavior

Adds logging at key points to diagnose why only one file is displayed:
- XamxamInitFileUploads called
- TFE queue picker init (id, multiple attribute state)
- onchange event (files count, names)
- fileArray post-concat length
- Single-file preview bindings (id, multiple attribute)

Remove after debug session.
2026-05-13 18:03:33 +02:00
Pontoporeia
e3896811c4 Fix migrations and deploy issues + errors + linting
- scan both pending/ and applied/ dirs so remote catch-up works
- fix remote 500s: run.php handles per-statement errors so VIEW rebuilds run after duplicate columns; replace mb_strimwidth with substr (no mbstring extension on server)
- add missing migration: 015_license_custom.sql (column existed in schema.sql but was never migrated)
- remote: fgetcsv enclosure single-char + AdminLogger permission-denied
guard + deploy always migrates
- fix admin-filters wrapping: restore flex-wrap, flex-basis on
inputs/selects, shrink-protect buttons
- fix phpstan: remove redundant ?? [] after isset guard in
ThesisEditController
- biome: exclude vendored min.js via includes patterns;
lint whole js dir; modernise beforeunload-guard.js
2026-05-08 22:58:05 +02:00
Pontoporeia
bdd95341b0 Extract shared TFE form partial — single source of truth for add/edit/partage
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.
2026-05-07 23:39:41 +02:00
Pontoporeia
ac0008df6c Add website-type TFE support: URLs stored as thesis_files rows, HTMX-toggle on Site web format 2026-05-07 23:39:41 +02:00
Pontoporeia
03121d6b7e form: add spacing between elements inside fieldsets
- common.css: fieldset > *:not(:last-child) gets margin-bottom: var(--space-xs)
- admin.css: .param-form fieldset > * zeroes margin to avoid double-spacing with flex gap
2026-05-07 23:39:41 +02:00
Pontoporeia
696259afae Fix form field required states & missing fields per spec
- Admin add: add contact_public checkbox (matching edit form)
- All forms: formats checkbox-list now required
- All forms: jury promoteur·ice interne required, lecteur·ice interne/externe required
- All forms: licence select now required
- Admin edit: add E-mail de confirmation fieldset
- Partage: contact always visible when provided (no contact_public field)
- Partage: filter PACS from AP programs dropdown
- Server-side validation: formats, jury, licence required (create + edit controllers)
- Autofocus mappings for new validation errors
- No duplicate asterisks — verified across all rendered fields
- fix: add missing old() function in admin edit controller
- refactor: move admin email field to Backoffice as Contact interne, never send email
- Untrack admin.log (covered by .gitignore)
2026-05-07 23:39:41 +02:00
Pontoporeia
e0c748d8e7 Refactor about.php
- 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
2026-05-07 19:44:18 +02:00
Pontoporeia
b063312642 centralise repertoire filter column rendering
- shared repFilterEntry() and  config array
- shared repFilterEntry() and $filterColumns config array
- fix single-valued FK fading via full intersection
2026-05-07 12:35:25 +02:00
Pontoporeia
bca707ee96 standardise buttons: .btn base class (border-radius 10px, padding var(--space-xs)) 2026-05-05 11:59:43 +02:00
Pontoporeia
b58445f71c sticky save/cancel buttons at top-right of admin edit page 2026-05-05 11:22:19 +02:00
Pontoporeia
ae6d9b86b3 Replace browser alert/confirm dialogs with <dialog> modals
- admin/index.php: alert() → no-selection dialog; confirm() bulk actions → bulk-confirm/bulk-delete dialogs; confirm() single delete → delete-thesis dialog; removed redundant confirm on Dépublier (reversible action)
- admin/tags.php: confirm() merge/delete → merge-tag/delete-tag dialogs
- admin/acces-etudiante.php: confirm() delete link → delete-link dialog
- admin/acces.php: confirm() archive link → archive-link dialog
- admin/parametres.php: confirm() maintenance/delete-all → enable-maintenance/delete-all-tfe dialogs; admin password confirm() kept with TODO comment
- admin/account.php: admin password confirm() kept with TODO comment
- admin.css: add .admin-dialog--sm, .admin-dialog__alert, .admin-dialog__footer styles
2026-05-05 11:04:52 +02:00
Pontoporeia
5f24dcae7e fix: duplicate warning not shown in admin, double-encoded in partage, no focus
- 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 &#039; 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
2026-05-05 11:04:52 +02:00
Pontoporeia
a2cba6d3c0 feat: prevent duplicate TFE submissions with logging and user feedback
- 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
2026-05-05 11:04:52 +02:00
Pontoporeia
34b2d073ee style(toast): reposition to bottom-center, solid bg, larger text, longer duration 2026-05-05 11:04:52 +02:00
Pontoporeia
e8bf89d184 admin header: replace déconnexion text with SVG sign-out icon 2026-05-05 11:04:52 +02:00
Pontoporeia
0f849468f7 feat: inline email retry on 550 rejection in tfe access request form 2026-05-05 11:04:52 +02:00
Pontoporeia
da53bf5d7a feat: email retry page on 550 rejection; confirmation_email optional in admin form 2026-05-05 11:04:52 +02:00
Pontoporeia
a83dc1c74e feat: multi-type file upload with sort order, labels, and expanded MIME support
- DB migration 007: add sort_order + display_label to thesis_files
- Database: getThesisFiles ordered by sort_order; insertThesisFile accepts label/order;
  new reorderThesisFiles() and updateThesisFileLabel() methods
- ThesisCreateController + ThesisEditController: expand allowed MIME/exts to include
  audio (mp3/ogg/wav/flac/aac/m4a), video (webm/mov/ogv), image (gif/webp),
  archives (tar/gz), any-ext via octet-stream; max size raised to 500 MB;
  accept file_labels[] and file_orders[] POST fields; detectFileType() helper
- MediaController: expanded MIME allowlist; HTTP Range support for audio/video;
  force-download for unknown types; inline for known displayable types
- fieldset-files.php: sortable queue UI with SortableJS, per-file labels, 500 MB hint
- templates/admin/edit.php: existing files as sortable list with drag handles,
  type icons, label inputs, delete checkboxes, hidden sort-order fields
- file-upload-queue.js: new JS replacing file-preview.js — sortable new-file queue,
  per-file labels, hidden order fields on submit, backward-compat legacy preview
- tfe.php: renders audio (<audio>), all video formats, images, PDF, and
  download-only 'other' files; reads display_label; sorted by sort_order
- tfe.css + form.css: styles for audio player, download files, sortable queue,
  drag handles, file type badges, label inputs
- .htaccess + .user.ini: upload_max_filesize=512M / post_max_size=520M
2026-05-05 11:04:52 +02:00
Pontoporeia
bdb68479d5 smtp: typed probe errors with per-field UI highlighting on save 2026-05-05 11:04:52 +02:00
Pontoporeia
56c8d54435 repertoire: align all column headings to shared baseline row 2026-05-05 11:04:52 +02:00
Pontoporeia
a9e03c4b1c repertoire: fixed-header columns, remove main/index padding, minimal column padding 2026-05-05 11:04:52 +02:00
Pontoporeia
3e35bbc40f style: align mobile nav dropdown links left 2026-05-05 11:04:52 +02:00
Pontoporeia
471c892638 style: larger mobile nav dropdown links 2026-05-05 11:04:52 +02:00
Pontoporeia
42286b1b71 Header link modification 2026-05-05 11:04:44 +02:00
Pontoporeia
671cfb6d83 fix: hamburger dropdown not showing — reset display:none at mobile breakpoint 2026-04-30 00:02:44 +02:00
Pontoporeia
11f429eb72 feat: pure-CSS hamburger menu for public nav (≤640px) 2026-04-29 22:13:19 +02:00
Pontoporeia
43702542eb feat(admin): sortable form-help blocks with two-panel UI
- Migration 005: add sort_order column to form_help_blocks
- Database: getAllFormHelpBlocks orders by sort_order; new reorderFormHelpBlocks()
- actions/form-help-reorder.php: HTMX POST handler, CSRF-validated, 204 response
- templates/admin/contenus.php: replace flat table with two-panel layout
  - Left: SortableJS 1.15.2 + htmx drag-and-drop ordered block cards
  - Right: static form structure reference showing fieldsets and their inputs
- admin.css: .fhb-* styles for layout, cards, ghost/chosen/drag states, anchors
- schema.sql: updated form_help_blocks DDL with sort_order column
2026-04-29 21:45:55 +02:00
Pontoporeia
885150ea45 css: centralise semantic element baseline styles in common.css 2026-04-29 21:33:55 +02:00
Pontoporeia
670a38f30d add form help blocks: DB table, admin editor, live rendering in partage form 2026-04-29 21:08:09 +02:00
Pontoporeia
32a7509598 feat: add file display to forms and recap pages
- 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)
2026-04-27 20:52:27 +02:00
Pontoporeia
27e1b6828d Implement TFE file access restriction feature (complete)
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
2026-04-27 20:20:52 +02:00
Théophile Gervreau-Mercier
7e26351f4b refactor: remove test.db, use only posterg.db for all environments
- Simplified Database.php determineDatabasePath to always use posterg.db
- Removed test.db auto-detection based on php_sapi_name
- Removed test.db targets from justfile (migrate-test removed)
- Removed CreateTestDatabase.php fixture script
- Updated migrate.sh to only init posterg.db
- Updated setup-dev.sh to init posterg.db
- Updated run-tests.php (removed DB_ENV=test env var)
- Updated deploy-db to use posterg.db
- Removed test.db file

refactor: remove empty fixtures directory
2026-04-27 18:07:20 +02:00
Pontoporeia
53c3127140 feat: student name popover preview on /repertoire via htmx 2026-04-24 23:03:49 +02:00
Pontoporeia
ede53746ba feat: student name popover preview on /repertoire via htmx 2026-04-24 23:03:49 +02:00
Pontoporeia
95bce2bbad Extract form CSS into form.css; scope system.css to system.php only 2026-04-24 23:03:49 +02:00
Pontoporeia
d82556c596 Unify form CSS: move licence/share-badge styles to admin.css, remove inline style from partage form 2026-04-24 23:03:49 +02:00
Pontoporeia
b448d0d40c Lock body scroll: only main/inner elements scroll on admin and public pages 2026-04-24 23:03:49 +02:00
Pontoporeia
5a58eefe66 feat(admin): add SMTP test email button on parametres page 2026-04-24 23:03:49 +02:00
Pontoporeia
4839b568de Separate admin views from controllers — move HTML to templates/admin/
All admin pages refactored to thin controllers + pure view templates, mirroring
the public-page pattern:

Controllers (public/admin/*.php): auth, data loading, include template
Views (templates/admin/*.php): pure HTML/PHP output
Fragment partials (templates/admin/partials/): toast, system-log-panel, system-nginx-config-panel

Pages migrated: login, tags, contenus, contenus-edit, account, acces-etudiante,
thanks, add, edit, parametres, system, index

Fragment endpoints refactored: system-fragment.php, toast-fragment.php
Skipped (pure redirects): logout, logs, status, import
2026-04-24 23:03:49 +02:00
Pontoporeia
362688c0fa fix: remove broken flash-messages include from admin footer; make repertoire columns scrollable 2026-04-24 23:03:49 +02:00
Pontoporeia
73fdda4a7f fix repertoire AP/OR/FI columns and main scroll containment
- repertoire-index.php: add $colHasMatches per-column guard.
  Entries in a column are only faded when that column has at least one
  matched entry in the current result set. When a dimension has no
  matched entries (e.g. no thesis has orientation_id set yet), the
  entire column stays fully interactive — all values remain clickable.
  This fixes: empty columns, forced single-select, cascade fading.

- Database.php: revert allAp/allOr/allFi to full lookup-table queries
  so all known values are always shown (not just ones linked to theses).

- common.css: body is now a flex column; main gets flex:1 + min-height:0;
  header-search-wrap gets flex-shrink:0; duplicate html/body blocks merged.
- public.css: removed redundant top-level main block; home-main gets min-height:0.
- repertoire.css: search-main gets min-height:0 for proper flex scroll.
2026-04-24 23:03:49 +02:00
Pontoporeia
78449afe64 some css changes 2026-04-24 23:01:25 +02:00
Pontoporeia
1b02ccb1d5 fix: mark languages as required, add required-field visual indicators on both forms
- 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.
2026-04-20 16:19:55 +02:00
Pontoporeia
e21a4d81a2 refine: required confirmation_email field on both student forms, StudentEmail uses it directly
- Add dedicated 'confirmation_email' (type=email, required) field
  to student form at end of submission (partage + admin).
- ThesisCreateController now validates it is present and a valid
  email; form is rejected if missing/invalid.
- Autofocus mapping for confirmation_email errors.
- StudentEmail uses confirmation_email directly (removed extractEmail
  hack that mined email from free-form contact field).
2026-04-20 15:47:55 +02:00
Pontoporeia
75f808bee4 feat: extract MediaController, wire into Dispatcher, delete media.php 2026-04-20 12:32:00 +02:00