Commit Graph

92 Commits

Author SHA1 Message Date
Pontoporeia
3df1456781 fix: author name casing not updating — use ID lookup priority
Root cause: SQLite uses BINARY collation, so WHERE name = ? is
case-sensitive. When changing 'john doe' to 'John Doe', the name
lookup failed and fell through to the email path which didn't update
the name. The previous fix only added UPDATE in the name-match branch.

Fixes in findOrCreateAuthor:
1. Accept optional $idHint parameter — when known (edit flow), update
   directly by ID (fastest, zero ambiguity)
2. Add COLLATE NOCASE to the name lookup (fallback path)
3. Add UPDATE in the email fallback path too

setThesisAuthors now fetches existing author_ids before deletion and
passes them as position-based hints, so identity is always preserved.
2026-06-10 00:17:00 +02:00
Pontoporeia
3016c199bd Fix edit form: is_published reset, contact decoupling, note label, author name case
- Fix #1: Add is_published to getThesisRawFields() SELECT so the publish
  checkbox stays checked when editing an already-published TFE.
- Fix #2: Rename 'Note contextuelle' → 'Note contextuelle relative à
  soutenance' in all templates and StudentEmail.
- Fix #3: Update findOrCreateAuthor to also UPDATE the author name when
  a record is found by name (fixes inability to capitalise names).
- Fix #4/#5: Decouple contact_interne (private author email) from
  contact_visible (public contact on TFE page). Add migration 037 to
  add contact_visible TEXT column to theses table and rebuild
  v_theses_full view. Update all controllers, templates, and DB methods
  to treat them independently.
- Fix #6: Investigated libre→interne restriction — no code barrier
  found; likely resolved by is_published fix.
2026-06-10 00:17:00 +02:00
Pontoporeia
3d524226a1 formulaire: correctifs identifiant/année, contact, fichiers optionnels
- Identifiant: mise à jour automatique quand l'année change en back-office (updateThesis + ThesisEditController)
- Contact: hint enrichi (1 seul contact, formatage Instagram/Mastodon)
- Fichiers: TFE rendu optionnel pour Site web/Performance/Installation (note d'intention reste obligatoire)
2026-06-10 00:17:00 +02:00
Pontoporeia
312d9eab0e À propos: contacts flexibles, liens sidebar éditables, grille contacts admin, et bouton supprimer
- Contacts: on peut laisser vide le nom OU le rôle (plus besoin des deux)
- Sidebar: les liens « site de l'erg » et « code source » sont éditables depuis /admin/contenus-edit.php?slug=about
- Admin: les champs Nom/Email/Lien des contacts s'affichent en grille 3 colonnes
- Admin: icône corbeille (admin-icon-btn--delete) pour supprimer un contact, avec réindexation automatique
- Database::getAproposContent() gère maintenant les valeurs string (URLs) en plus des arrays
- Database::saveAproposContent() accepte array|string
2026-06-10 00:16:22 +02:00
Pontoporeia
3588f22d7b style: consolidate aria-current nav styles — remove border-radius from base header links, keep global :focus-visible ring, move border-bottom/padding to shared header.css 2026-06-10 00:15:41 +02:00
Pontoporeia
cee3345ea3 tfe.php: afficher CC2r + licence, formater contact court, supprimer download PDF 2026-06-10 00:15:41 +02:00
Pontoporeia
df70fba5d4 feat: convert all file inputs to FilePond for standardized uploading
- Add csv_import queue type (storeAsFile, no async upload) for CSV import dialog
- Convert file-field.php partial to FilePond with field-name→queue-type mapping
- Conditionally skip server config for storeAsFile queues in buildFilePondOptions
- Skip FilePond init for inputs inside closed <dialog> elements
- Trigger FilePond init when import dialog opens
- Load FilePond CSS/JS assets on admin index page
2026-06-09 13:12:22 +02:00
Pontoporeia
f4cb06656e Improve .gitignore 2026-06-08 10:14:17 +02:00
Pontoporeia
a047062d87 Phase 4 cleanup: migrate old tests to PHPUnit, add ErrorHandler/PureLogic/SearchController tests, remove app/tests/, update justfile test target 2026-05-20 01:55:58 +02:00
Pontoporeia
d9e4541749 Remove unused Parsedown.php (replaced by league/commonmark in Phase 1); update phpstan baseline 2026-05-20 01:21:47 +02:00
Pontoporeia
a0cda5b55d Phase 3: Replace SmtpRelay SMTP socket with PHPMailer 2026-05-20 01:17:44 +02:00
Pontoporeia
ba57820016 Phase 2: Replace PeerTubeService HTTP client with Guzzle 2026-05-20 01:07:41 +02:00
Pontoporeia
5e75cacad7 Phase 1: Replace Parsedown with league/commonmark (4 call sites) 2026-05-20 01:02:09 +02:00
Pontoporeia
728f05502c Combine phpstan, cs-check, cs-fix into lint-php recipe; fix lint issues + test failures + duplicate detection bug 2026-05-20 00:31:19 +02:00
Pontoporeia
defc919cd0 cleanup modal: list stale files to remove; storage restructure: documents/ → {objet}/ 2026-05-19 23:58:51 +02:00
Pontoporeia
5bbf633295 Contenus: add Mots-clés fieldset mirroring Langues, keep dedicated page button as backup, add Annuler cancel button to both bulk action bars, limit both table wraps to max-height:50vh with overflow scroll 2026-05-19 23:58:51 +02:00
Pontoporeia
2cb8d71fe9 Fix dialog margins, add admin-dialog__body/styles, give trash page horizontal margins 2026-05-19 23:58:51 +02:00
Pontoporeia
7cf020c7bd Refactor CSS architecture per css-methodology-spec.md
Split CSS into named layers: reset → colors → typography → base →
components → utilities. Each component has one unique root class in
its own file. No cross-component overrides.

New files:
- reset.css (modern-normalize base — matches project's prior reset)
- colors.css (all colour variables)
- typography.css (font faces, size/space scale, font-family vars)
- base.css (≤ 5 site-wide rules: layout, headings)
- utilities.css (sr-only, skip-link, reduced-motion)
- style.css (root @import file loading all layers)
- components/{links,focus,forms,tables,dialog,details,media,
  buttons,badges,toasts,pagination,header,search}.css

Existing files:
- variables.css → backward-compat wrapper (imports colors + typography)
- common.css → backward-compat wrapper (imports style.css)
- Page files (admin, public, form, tfe, apropos, repertoire, system,
  file-access) → removed redundant @import url(./variables.css)
- head.php → loads style.css instead of modern-normalize + common.css
- partage pages → load style.css

Fixes vs initial refactoring:
- reset.css: use modern-normalize base (not Tailwind Preflight) to
  avoid border/list/heading regressions from aggressive defaults
- components/search.css: restore !important flags on input styles
  (needed to override forms.css base input selectors)
- acces.php: add toast feedback on password copy button

Cleaned up duplicate status-badge/toast definitions from admin.css
(now live in components/badges.css and components/toast.css).
2026-05-19 16:55:32 +02:00
Pontoporeia
79eddf5d5a feat: fix file deletion on save + trash policy + documents/ prefix + relink browser
1. note_intention: Delete old file only when a genuinely new upload arrives
   (32-char hex file_id), not when the FilePond pool preserves an existing
   file by sending its DB integer ID.  Previously the DB integer ID
   triggered $hasNewNote=true, which deleted the existing note_intention
   from disk+DB, then handleFilePondSingleFile couldn't re-process it
   because the regex requires a hex pattern.  Same fix applied to cover.

2. All file deletions now use deleteThesisFileToTrash() which renames
   files to tmp/_trash/ instead of unlinking.  The trash preserves
   original filenames prefixed with DB id for traceability.  Skips
   website URLs and PeerTube refs (no disk file).

3. Storage prefix changed from theses/ to documents/ to reflect that
   the folder holds all document types (determined by file_type in DB).
   MediaController visibility gate supports both prefixes for backward
   compat with existing files.

4. File browser + relink feature for orphaned files:
   - /admin/fragments/file-browser.php — HTMX tree browser for
     storage/documents/ and storage/theses/
   - /admin/actions/filepond/relink.php — POST endpoint that inserts
     a thesis_files row pointing to existing on-disk file
   - Per-pool "📂 Relier" buttons (edit mode only)
   - JS: XamxamOpenFileBrowser / XamxamRelinkFile with FilePond integration
   - CSS: .relink-modal dialog + .file-browser tree styles
2026-05-19 00:08:06 +02:00
Pontoporeia
6f7a02244f maintenance: allow /partage through gate, fix fragment routing, add visibility table in admin
Extract shared filepond logic into src/FilepondHandler.php class.
Admin filepond endpoints delegate to the handler after AdminAuth check.
New partage filepond endpoints at /partage/actions/filepond/ verify
share_active session flag + CSRF token, no admin auth required.

JS reads filepond-base meta tag to determine endpoint path:
- Admin pages: /admin/actions/filepond (via head.php isAdmin check)
- Partage form: /partage/actions/filepond (explicit meta)

partage/index.php sets share_active = true on form render, cleans up on
successful submit. Partage process endpoint rate-limited to 30/5min per
session. No nginx changes needed — /partage/ location already handles
PHP without auth_basic.
2026-05-19 00:08:06 +02:00
Pontoporeia
da153fc604 Refactor HTMX fragment architecture: DRY split into auth endpoints + shared templates
- Created templates/partials/form/_licence.php (shared HTML, no auth logic)
- Created templates/partials/form/_format-website.php (shared HTML, no auth logic)
- Created src/FragmentRenderer.php helper for clean fragment rendering
- Created public/{admin,partage}/fragments/ subdirectories
- Created thin fragment endpoint files: auth guard + data fetch + render template
- Updated all hx-post references in templates to new fragments/ paths
- Updated partage/index.php routing for new fragments subdirectory
- Kept old fragment files as thin delegates for backward compat
- Updated nginx config: added PHP handler in /partage/ location block
2026-05-19 00:08:06 +02:00
Pontoporeia
2632730fa0 .gitignore ignore rate_limit and theses and logs.
Done. The .gitignore now ignores all files in app/storage/cache/rate_limit/* and app/storage/theses/* while   
 preserving their .gitkeep files via ! negation rules.
2026-05-19 00:08:06 +02:00
Pontoporeia
2f4ac22bcb Fix Interdit Info text 2026-05-19 00:08:06 +02:00
Pontoporeia
9152b120e8 feat: mandatory auto-generated passwords for share links + admin password copy/regeneration + password gate rate limiting 2026-05-19 00:08:06 +02:00
Pontoporeia
8bb0b3a1f2 refactor: unify FilePond edit previews + clean upload UI and shared fragments
* Move shared `fichiers-fragment.php` from `partage/` to `templates/partials/form/`
  and update all include/require references
* `.gitignore`: exclude SQLite WAL/SHM journal files
* FilePond UI:

  * change uploaded file block border state from yellow to green
  * restyle image previews to use site light-theme colors
* Edit mode:

  * remove custom existing-file preview list implementation
  * preload existing files directly into FilePond pools
  * include `cover` and `note_intention` assets in FilePond-managed state
* Remove obsolete upload progress bar UI and related JS includes
* Remove deprecated `Écriture` + `Image` format types from upload flow/configuration
2026-05-19 00:08:06 +02:00
Pontoporeia
2e9ebfc684 filepond: implement async server-ID upload architecture with nested queue support + PeerTube integration
Replace `storeAsFile:true` with a full async FilePond round-trip pipeline using opaque server-side file IDs.

* Added 4 new PHP endpoints under `/admin/actions/filepond/`:

  * `process.php` — upload/process single file and return opaque `file_id`
  * `revert.php` — delete pending tmp uploads before form submit
  * `load.php` — stream existing files by DB ID for FilePond preload
  * `remove.php` — soft-delete `thesis_files` rows
* `process.php` improvements:

  * accept arbitrary FilePond field names instead of hardcoded `file`
  * support PHP-nested multi-file queue inputs (`queue_file[tfe][]`)
  * explicit unwrapping of nested `$_FILES` structures
  * add `audio/mp3` to audio + `peertube_audio` MIME whitelists
  * immediate upload of `peertube_*` files to PeerTube, returning `peertube:{uuid}` IDs
  * extensive `error_log()` instrumentation for request, CSRF, MIME, upload, and save stages
* `revert.php` now accepts `peertube:` IDs without local cleanup
* `ThesisFileHandler`:

  * add `handleFilePondQueueFiles()` + `handleFilePondSingleFile()`
  * process async uploads from `storage/tmp/filepond/` via opaque `file_id`
  * inline handling of `peertube:{uuid}` IDs with direct `thesis_files` insertion
  * remove obsolete deferred PeerTube queue-processing flow
* `ThesisCreateController` + `ThesisEditController`:

  * gate async path behind `filepond_mode=1`
  * preserve legacy multipart flow as fallback
* `file-upload-filepond.js`:

  * remove `storeAsFile:true`
  * add `buildServerConfig()` for async endpoint wiring
  * fix `syncOrderInput()` to use `serverId`
  * add `onprocessfile` hook
  * add `fileValidateSizeFilterItem` for per-extension size caps
  * preload existing uploads via `data-existing-files` + `server.load`
  * replace static `INPUT_ID_TO_TYPE` map with `data-queue-type`
  * add extensive `console.log()` debugging across upload pipeline stages
* `upload-progress.js`:

  * block form submission while uploads are pending
  * update `collectFileNames()` to read processed FilePond items
* Templates/layout:

  * add `data-queue-type`
  * add `data-existing-files`
  * add global CSRF meta tag outside admin-only context
  * add `filepond_mode` hidden input
  * add CSRF token/meta support for partage pages
  * move website URL field below file upload block
* `.gitignore`: exclude `storage/tmp/` from version control
2026-05-19 00:08:06 +02:00
Pontoporeia
d873a7f09e fix: add upload-progress.js to partage form (progress bar was missing on public submissions) 2026-05-19 00:08:06 +02:00
Pontoporeia
83a5a508ea feat: PeerTube integration — alternate audio/video labels, FilePond pools, shared SMTP credentials, channel by name, test button, resumable upload, embed improvements, fix alt labels/curl_close/deprecation 2026-05-19 00:08:06 +02:00
Pontoporeia
28ef35dce5 fix: make schema.sql fully idempotent — add IF NOT EXISTS to all CREATE INDEX, CREATE TRIGGER, and CREATE VIEW statements 2026-05-19 00:08:06 +02:00
Pontoporeia
be50ac5eb0 fix(production): fix multiple remote server errors from nginx logs
- Fix 413 Request Entity Too Large: bump client_max_body_size to 256M,
  PHP post_max_size/upload_max_filesize to 256M, fastcgi timeouts to 300s
- Fix missing v_smtp_active view: add IF NOT EXISTS to all CREATE VIEW
  statements in schema.sql for idempotent migrates
- Fix bars.svg 404: create animated SVG spinner in app/public/assets/img/
- Fix nginx rate limiting: increase admin zone from 60r/m (1 r/s) to
  300r/m (5 r/s) with burst=30 to handle ~11 concurrent HTMX fragment
  GETs on contenus.php page load
- Add deploy-nginx recipe to justfile for uploading nginx config to server
- Database readonly issue mitigated by existing --chown + deploy-server.sh
  permissions fix
- Add comprehensive PHP/JS debugging logs for settings checkboxes:
  per-field raw POST values in error_log, console.log on htmx:beforeSend,
  htmx:sendError, htmx:afterRequest, toast lifecycle
- Fix toast auto-remove script: use getElementById with unique ID instead
  of querySelector which could remove wrong toast on rapid clicks
2026-05-19 00:08:06 +02:00
Pontoporeia
72f7192156 feat(deploy): add deploy-verify-permissions recipe + upload/run deploy-server.sh before verification + run migrations in deploy 2026-05-19 00:08:06 +02:00
Pontoporeia
926659087f feat: implement SQLite backup & data integrity plan (Phases 2-4) 2026-05-19 00:08:06 +02:00
Pontoporeia
f28a20d642 fix: spurious HTMX console warnings from checkbox-list default hx-include
The checkbox-list partial defaulted hx-include to 'this, #website-url-fieldset',
but #website-url-fieldset only exists when `Site web` is checked in the
format list.  Every language checkbox click triggered a no-match warning
and a cascade triggering the known HTMX internal-data crash.
2026-05-19 00:08:05 +02:00
Pontoporeia
38dc8de9d8 feat: obfuscate all email addresses and mailto links as HTML entities
Added EmailObfuscator class (src/EmailObfuscator.php) that converts
email addresses to HTML decimal entities (e.g. &#102;&#111;&#111;@...)
so browsers render them correctly but bots and scrapers see gibberish.

Methods:
- email($addr): obfuscate for display in HTML content
- mailto($addr): return obfuscated mailto: href
- obfuscateHtml($html): post-process rendered HTML to obfuscate all
  mailto: links (used after Parsedown/Markdown rendering)

Applied to:
- partage/index.php: mailto link at top + error scenarios via _flash_contact
  flag rendered in form.php (outside htmlspecialchars to avoid double-escape)
- admin/acces.php: request email mailto links
- admin/file-access.php: request email mailto links
- public/about.php: contact email mailto links
- public/tfe.php: author contact mailto links
- AboutController: Parsedown output post-processing
- LicenceController: Parsedown output post-processing
- Dispatcher::render(): require_once EmailObfuscator for all public views

Also fixed _flash_contact session flag in form.php partial to show
contact email line on share link validation errors (separate from
flash_error/warning to bypass htmlspecialchars double-escaping).
2026-05-19 00:08:05 +02:00
Pontoporeia
ab6e266807 fix: add help email, preserve file names on validation error, license fix
The share link (partage) form does not expose a license field and does
not send access_type_id (defaults to 2/Interne). Server-side validation
was unconditionally requiring a license for non-admin submissions,
causing all share link submissions to fail.

Now the license check is gated on adminMode=false AND accessTypeId=1
(Libre), matching the client-side HTMX fragment behaviour in
licence-fragment.php. Also fixed a use-before-definition where
accessTypeId was referenced before being assigned.

Student form improvements:
- Add xamxam@erg.be mailto link at top of form
- On validation error, append "Si le problème persiste, envoyez un
  e-mail à xamxam@erg.be" to the flash message
- Preserve uploaded file names across validation redirects: store in
  session (share_primed_files_<slug>), display as warning on form
  re-render so the student knows which files to re-select

- License: only required for non-admin when access_type_id=1 (Libre),
  not for Interne (2) or Interdit (3). Fixes share link submissions
  failing with "Veuillez sélectionner une licence". Also fixed
  use-before-definition of accessTypeId.
2026-05-19 00:08:05 +02:00
Pontoporeia
96fa8ee266 CSV importer: boolean and ap variants/typos
- add AP aliases for:
  - Design & politique du multiple → DPM,
  - Pratiques artistiques & complexité scientifique → PACS,
  - Narraion Speculative typo → NS
- Fix: OUI/NON CSV artefacts in contact_interne — clean DB, guard in findOrCreateAuthor and CSV import
- Cleaned 141 authors.email = 'NON' rows → NULL in dev DB
- findOrCreateAuthor: treat OUI/NON as null (CSV boolean artefact in email column)
- CSV import: sanitize contact column — OUI/NON → empty string before passing to findOrCreateAuthor
2026-05-19 00:08:05 +02:00
Pontoporeia
fa30aab368 Rename author_email→contact_interne, author_show_contact→contact_public across view/controllers/templates
- v_theses_full: author_email→contact_interne, author_show_contact→contact_public
- Updated schema.sql and live DB view
- Renamed all PHP variables: currentAuthorEmail→contactInterne, currentAuthorShowContact→contactPublic
- Restored contact_interne backoffice field with proper wiring (takes precedence over mail field)
- Updated admin/add.php, admin/edit.php, partage/index.php, public/tfe.php templates
2026-05-19 00:08:05 +02:00
Pontoporeia
b6908f7453 Rename Liens étudiant·e, add link name + edit dialog
- Rename 'Accès étudiant·e' → 'Liens étudiant·e' in acces.php
- Add 'name' column to share_links (schema.sql + ALTER TABLE migration)
- ShareLink::create() now accepts optional  parameter
- Add ShareLink::update() method for name/password/expiration
- Add 'update' action to acces-etudiante.php controller
- Remove Visiter (play) button; row click opens link in new tab
- Add edit dialog with name, password, expiration fields
- Add pen icon button to open edit dialog per row
- Add Nom column to table (also in archived links section)
2026-05-19 00:08:05 +02:00
Pontoporeia
7711557d08 refactor: Admin index — replace emoji buttons with Phosphor SVG icons, add back buttons + row click navigation, minimal JS, move export DB to Exporter modal, color stats, bulk bar anti-shift, credits reorder, tags icons 2026-05-19 00:08:05 +02:00
Pontoporeia
c4a23d5c2d Remove duration_pages/duration_minutes/file_size_info; rename cc4r → cc2r in DB and code 2026-05-19 00:08:05 +02:00
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
15d54fa19e add Néerlandais language option and make language_autre conditionally required 2026-05-08 22:58:25 +02:00
Pontoporeia
f3d9615562 merge banners into covers: remove banner field, migrate files, add covers to search/home/repertoire cards 2026-05-08 22:58:25 +02:00
Pontoporeia
51f9f56e09 Replace span with a link + href in about.php for credits 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
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
dce0e0b301 schema: validate against new TFE field spec
- add exemplaire_baiu, exemplaire_erg, cc4r, remarks;
- add is_ulb to jury;
- split jury_lecteurs into interne/externe in view;
- refactor admin edit form with backoffice fields;
- update public fiche to show promoteur ULB and split lecteurs
2026-05-07 17:53:24 +02:00
Pontoporeia
3f87d71e38 Fix: CSV importer and imported data
- pad rows, distinguish empty year, better error diagnostics
- derive year from identifier when year column is empty
- fix remaining 18 theses: Installation/Performance (slash→dash) orientation alias
- csv importer: use column-name-based header detection instead of hardcoded positions
2026-05-07 12:35:31 +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