Commit Graph

29 Commits

Author SHA1 Message Date
Pontoporeia
e6829994b6 Refactor + feat: unify format/fichiers HTMX fragment, reorder format types, add file constraints, fix admin auth
* **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.
2026-05-13 17:59:13 +02:00
Pontoporeia
5735ccbc38 Fix issues with nginx access to pages
- 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.
2026-05-13 17:58:29 +02:00
Pontoporeia
6ba13e00ea test: add ShareLinkTest + PureLogicTest (TDD), fix coverMap undefined in SearchController 2026-05-08 22:58:25 +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
9dc7ea98f2 fix: password-protected share links never load form after password entry
The main GET handler in partage/index.php always showed the password gate
for links with password_hash set, even after successful verification. The
session flag share_verified_<slug> was being set by requirePasswordGate()
but never checked when deciding whether to re-show the gate.

Added a check: if the session flag is already set, skip the gate and
render the form directly.

Also added error_log() calls throughout the password flow to help
diagnose future issues.
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
24d68dda59 refactor form structure per new spec + fix
- 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
2026-05-07 19:43:43 +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
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
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
0960afb731 fix: add missing favicon tags to partage/recapitulatif.php 2026-05-05 11:04:52 +02:00
Pontoporeia
9ba60084bf fix: require SmtpRelay.php before StudentEmail.php in partage/index.php 2026-05-05 11:04:52 +02:00
Pontoporeia
c27ffafa7e fix: add missing favicon tags to partage/index.php (error, password gate, form) 2026-04-29 21:58:49 +02:00
Pontoporeia
992f74b31c fix: prevent jury-fieldset partial from calling old() with wrong arity in partage context
Drop '?: null' coercions on juryPresident/juryPromoteur seeding in partage/index.php
so they are '' (not null), making the partial's $addMode guard false and skipping the
single-arg old() call that clashes with partage's 3-arg old() signature.
2026-04-29 21:56:42 +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
d665cb502d centralise form fieldsets into shared partials; add TODO stubs in partage form 2026-04-29 20:59:35 +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
aca7e7eef8 rename thanks.php to recapitulatif.php in admin and partage 2026-04-27 20:41:43 +02:00
Pontoporeia
4986fa74f4 add structured logging for admin/partage form submissions + migration system
- AppLogger: JSON-line logger in storage/logs/form-submissions.log
- Logs submissions (admin + partage) with IP, UA, thesis ID, author
- Logs errors with context (post keys, share slug)
- Migration runner (app/migrations/run.php) handles schema drift
- 001_add_objet_column.sql fixes production DB missing 'objet' column
- ThesisCreateController::getIdentifier() helper for logging
2026-04-24 23:03:49 +02:00
Pontoporeia
d961f9533c feat: add objet field (tfe/thèse/frart) with share-link restriction and site-settings toggles 2026-04-24 23:03:49 +02:00
Pontoporeia
38031374c1 fix(partage): smtp view missing in schema + thanks redirect broken
- Add v_smtp_active VIEW to schema.sql (was only in migration 012,
  causing SmtpRelay::isConfigured() to always return false on fresh installs)
- Change thanks redirect from /partage/thanks.php to /partage/thanks
  (nginx 'location ~ \.php$ { deny all }' blocked the .php URL)
- Route /partage/thanks in index.php before slug validation
- Guard App::boot() in thanks.php to avoid double-boot when included
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
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