Commit Graph

235 Commits

Author SHA1 Message Date
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 ' 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
0a05f3911c Replace Psalm with PHPStan + PHP‑CS‑Fixer + Biome, add linting configs & cleanup
- Removed the `vimeo/psalm` dependency and all related files
(`psalm.xml`, `psalm‑baseline.xml`, suppress annotations).  
- Added **PHPStan** (v2.1.54) and **PHP‑CS‑Fixer** (v3.95.1) to
`vendor/bin/`.  
- Created `phpstan.neon` (level 5, bootstraps `app/bootstrap.php`,
scans `Parsedown.php`).  
- Created `phpstan‑baseline.neon` with 10 pre‑existing errors.  
- Added `.php‑cs‑fixer.dist.php` (PSR‑12 + PHP80Migration, targets
 `app/src` & `app/tests`).  
- Added `biome.json` and updated `justfile` to replace the old Psalm
recipes with `phpstan`, `cs‑check`, and `cs‑fix`.  
- Updated `.gitignore` to exclude PHPStan and PHP‑CS‑Fixer cache files.  
- Updated several JS files (`file‑preview.js`, `file‑upload‑queue.js`)
eand PHP controllers (`MediaController.php`, `SearchController.php`,
`SystemController.php`).  
- Minor adjustments to `TODO.md`, `app/src/Database.php`,
`app/src/Parsedown.php`, `app/src/ShareLink.php`, and
`app/src/SmtpRelay.php`.
2026-05-05 11:04:52 +02:00
Pontoporeia
d6e30ec9cd Enhance serve recipe to automatically open the browser
- use xdg-open firefox
- keep serve recipe in foreground
2026-05-05 11:04:52 +02:00
Pontoporeia
8a38708fc8 Refactor justfile to reduce redundancy and merge similar recipes
- Merge deploy-* recipes into a single deploy-script recipe
- Remove rarely used recipes (show id, setup-dirs)
- Simplify test-* recipes
- Remove redundant default recipe
- Preserve all critical functionality
2026-05-05 11:04:52 +02:00
Pontoporeia
d09f1942f0 Fix Mistral provider: change api from openai-completions to mistral-conversations 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
898a87789b fix(smtp-test): catch SmtpSendException to surface delivery errors as flash messages 2026-05-05 11:04:52 +02:00
Pontoporeia
19784090a3 fix: pass PHP upload limits via -d flags in justfile serve recipe
php -S (built-in dev server) ignores .htaccess and .user.ini entirely.
The POST Content-Length limit was still 8M from /etc/php/php.ini.
Pass upload_max_filesize=512M, post_max_size=520M, memory_limit=256M,
max_execution_time=300, max_input_time=300 directly on the CLI.
2026-05-05 11:04:52 +02:00
Pontoporeia
6a37d21f3f docs: add file-uploads.md — accepted types, limits, storage, ordering, security 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
2188ff5479 docs: add SMTP 550 postfix fix report for mail admin 2026-05-05 11:04:52 +02:00
Pontoporeia
89b7ab476e Handle SMTP 550 recipient-rejected errors with structured SmtpSendException
- Add SmtpSendException with smtpCode/smtpResponse/isRecipientRejected()
- smtpSend() $expect closure throws SmtpSendException (with code) instead of RuntimeException
- SmtpRelay::send() re-throws SmtpSendException so callers can inspect it
- request-access.php (new): catch 550 → roll back token+approval, return HTTP 422 with FR user message
- request-access.php (resend): catch 550 → HTTP 422 instead of silently claiming success
- StudentEmail::sendConfirmation(): catch SmtpSendException → log+false (submission not aborted)
- admin/actions/access-request.php: catch SmtpSendException post-approval → flash warning (recipient-rejected vs transient)
2026-05-05 11:04:52 +02:00
Pontoporeia
8d115dc965 smtp: enable TLS peer verification, fix envelope injection, fix dot-stuffing 2026-05-05 11:04:52 +02:00
Pontoporeia
33987c9b15 smtp: add notify_email field; fix admin notification sent to no-reply sender 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
b750aca2f5 smtp: probe credentials on save (connect+auth+quit, no message sent) 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
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
cb883ab33f fix: deploy-server.sh migrates posterg.db → xamxam.db and cleans legacy nginx configs 2026-05-05 11:04:52 +02:00
Pontoporeia
ab51bf3a66 fix: deploy-server.sh cleans up legacy posterg configs and prunes old xamxam backups 2026-05-05 11:04:52 +02:00
Pontoporeia
68e30abb56 fix: remove Post-ERG branding → XAMXAM; drop legacy posterg nginx symlink in deploy script; rename posterg.db → xamxam.db 2026-05-05 11:04:52 +02:00
Pontoporeia
c949cf9481 rename posterg → xamxam throughout: nginx conf, scripts, PHP source, docs 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
80b7fddea4 fix: partials must not unset caller-owned $formData
fieldset-academic.php, fieldset-metadata.php and fieldset-licence-explanation.php
were each calling unset($formData) (or wrong variable) in their cleanup block,
destroying the variable in the parent renderShareLinkForm() scope.  This caused
an Undefined variable / TypeError on old($formData, ...) for any field rendered
after those partials (e.g. confirmation_email at line 328).

Fix: remove $formData from the unset() calls; fieldset-licence-explanation.php
was also unsetting the wrong name — corrected to unset($n) which is the variable
it actually declares.
2026-04-29 21:56:42 +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
5c39e856a3 fix: pass enabledAccessTypes from ThesisEditController to edit view 2026-04-29 21:34:47 +02:00
Pontoporeia
885150ea45 css: centralise semantic element baseline styles in common.css 2026-04-29 21:33:55 +02:00
Pontoporeia
b5189c0d08 admin: merge acces-etudiante+file-access into acces.php, absorb system.php into parametres.php 2026-04-29 21:18:25 +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
0437ec8d15 fix: escape apostrophe in FORM_HELP_LABELS string (Database.php:2005) 2026-04-29 21:05:53 +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
18a02a0018 deploy: rename deploy path from /var/www/posterg to /var/www/xamxam 2026-04-28 22:21:09 +02:00
Pontoporeia
cd68e6e9d7 deploy: exclude posterg.db, theses/, covers/ from rsync to avoid overwriting remote data 2026-04-28 22:15:06 +02:00
Pontoporeia
59c4cf055f smtp-test: bypass DB, use POST fields directly for credentials 2026-04-27 21:44:10 +02:00
Pontoporeia
e09b056115 fix: iframe for PDF display, exclude cover files from public loop, no session on media requests 2026-04-27 21:11:58 +02:00
Pontoporeia
46a3c360ec fix: use local storage/ in dev, create upload dirs, gitignore uploads 2026-04-27 21:08:48 +02:00
Pontoporeia
48059c2317 fix: serve logs, formulaire.php error_log path, CSRF debug, undefined $redirect 2026-04-27 21:04:21 +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
4d88bd8cc5 edit.php: rework Fichiers fieldset layout
- Drop file-field.php partial for cover/banner (it added a second label)
- Inline all three file inputs with admin-file-input wrapper
- Move banner inside the Fichiers fieldset
- Each entry: one label, one input, one small hint — no duplicate labels
- Context-aware hints: 'Laisser vide pour conserver...' when file already exists
2026-04-27 20:38:45 +02:00
Pontoporeia
8e864fc624 admin edit.php: add cover image + thesis file management fields
- Database: add deleteThesisFile() and handleCoverUpload() methods
- ThesisEditController::load(): expose currentFiles + currentCover to view
- ThesisEditController::save(): handle couverture upload/removal,
  per-file deletion (delete_files[]), and new thesis file uploads
- edit.php template: new Fichiers fieldset with cover preview+remove,
  existing files list with delete checkboxes, new file upload input
  (mirrors add.php / partage.php)
2026-04-27 20:33:21 +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
88b9f341cd Replace Posterg branding with XAMXAM in all user-facing content 2026-04-27 19:30:54 +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