From e6a989bb36e6a9fcbe50fc7326ad30712766c79f Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Fri, 3 Jul 2026 14:55:00 +0200 Subject: [PATCH] fix: add missing bootstrap require in markdown-cheatsheet-fragment.php, add bottom padding to dialog --- TODO.md | 85 +------------------ .../admin/markdown-cheatsheet-fragment.php | 2 + app/public/assets/css/admin.css | 2 +- app/public/assets/css/repertoire.css | 6 +- app/public/assets/css/tfe.css | 57 +------------ app/src/Controllers/MediaController.php | 18 ++-- app/templates/public/tfe.php | 35 +++----- 7 files changed, 30 insertions(+), 175 deletions(-) diff --git a/TODO.md b/TODO.md index 32be616..4d4f596 100644 --- a/TODO.md +++ b/TODO.md @@ -1,83 +1,2 @@ -# TODO - -> Last updated: 2026-06-24 -> Context: Security audit — fix open redirects, fragment auth, dead code, CSRF gaps - -## Deferred / Blocked -- [ ] #tighten-csp Tighten CSP to remove 'unsafe-inline' after inline JS extraction - -## Pending -- [ ] #rep-student-touch Replace hover student popover with tap-to-open drawer for mobile `(repertoire.php, repertoire.css, repertoire-student-popover.js)` - -## Deferred / Blocked -- [ ] #tighten-csp Tighten CSP to remove 'unsafe-inline' + 'unsafe-eval' from script-src — blocked on HTMX's use of new Function() for 'unsafe-eval'; 'unsafe-inline' needs OverType init + flash-warning inline scripts moved to external files, plus maintenance.php + validate-access.php inline styles extracted - -## Completed -- [x] #rep-polish Polish: scroll-position memory on HTMX swap, animation tuning `(repertoire.css)` ✓ -- [x] #icon-color-verify Verify icon colors render correctly across all pages (header, admin tables, forms, dialogs, cleanup modal) ✓ -- [x] #sec-open-redirect Fix open redirect in tag.php + language.php (protocol-relative URL bypass via str_starts_with) ✓ -- [x] #build-pipeline Setup biome + rolldown + lightningcss build pipeline ✓ - - [x] #build-packagejson Create package.json with devDependencies ✓ - - [x] #build-biomecss Update biome.json to handle CSS formatting ✓ - - [x] #build-rolldown-config Create rolldown.config.mjs + build-js.mjs for JS bundling ✓ - - [x] #build-lightningcss Add lightningcss CSS bundling (resolve @import chain) ✓ - - [x] #build-justfile Add just build/deploy recipes + integrate build into deploy ✓ - - [x] #build-head Update head.php + form-page.php + controllers to use bundled assets ✓ - - [x] #build-gitignore Add dist/ to .gitignore ✓ - - [x] #build-cssfix Fix stray `}` syntax error in admin.css line 305 ✓ -- [x] #sec-fragments-auth Gate partagé fragments on share_active session (read-only fragment renderers — no CSRF needed) ✓ -- [x] #sec-retry-csrf Add CSRF check to partage/retry-email.php POST ✓ -- [x] #sec-cleanup-dead-code Remove dead App::verifyCsrf() or refactor action handlers to use it ✓ -- [x] #gzip-nginx Enable gzip compression in nginx config `(nginx/xamxam.conf)` ✓ -- [x] #extract-inline-js Move inline JS to external files across 17 templates → 15 new JS files created `(app/public/assets/js/app/*.js)` ✓ -- [x] #inline-icon-helper Create `icon()` PHP helper + auto-load in bootstrap `(src/icon.php, bootstrap.php)` ✓ -- [x] #icon-fill-currentcolor Ensure all 36 icon SVGs use fill="currentColor" or stroke="currentColor" `(assets/icons/*.svg)` ✓ -- [x] #migrate-all-img-icons Replace all remaining `` with `` across 26 template files ✓ -- [x] #icon-css-cleanup Remove `` filter hacks from admin.css, add explicit sizes for header nav + public search icons `(admin.css, search.css)` ✓ -- [x] #icon-em-sizing Set icon helper to output width="1em" height="1em" so icons scale with parent font-size `(icon.php)` ✓ -- [x] #inline-svg-to-icons Move all inline SVGs to asset icon files, ensure currentColor fill for proper color inheritance `(33 icons, 22 files, 67+ inline SVGs replaced, 2 dynamic SVGs remain)` ✓ -- [x] #cleanup-modal-fixes Fix nettoyage modal: SVG caret icons, margin→padding, BBBDMSans summary `(admin.css, details.css)` ✓ -- [x] #structure-formulaire-page Move "Structure du Formulaire" from contenus.php to its own dedicated page with back button `(structure-formulaire.php [new], contenus.php)` ✓ -- [x] #contenus-indexes Add index on thesis_languages(language_id) + tags(deleted_at, name); fix count queries to exclude soft-deleted theses `(Database.php, DatabaseMigrations.php, schema.sql, migrations/applied/041_thesis_languages_index.sql)` ✓ -- [x] #peertube-orphans-check Add Peertube orphan video check + relink in admin — listChannelVideos (PeerTubeService), peertube-orphans.php endpoint, UI in nettoyage dialog, relink support on edit page `(peertube-relink.php, peertube-browser.php, fichiers-fragment.php, file-upload-filepond.js)` ✓ -- [x] #tfe-optional-formats Make TFE file optional when format is Site web (1), Performance (4) or Installation (6) — fixed incorrect format IDs [3→1,4,6] + added client-side JS toggle for TFE required/asterisk. Note d'intention remains required. 🎯 `(fichiers-fragment.php, file-upload-filepond.js)` ✓ -- [x] #typography-weight-300 Set search placeholder + apropos/charte/licence

content to BBBDMSans weight 300 `(search.css, apropos.css)` ✓ -- [x] #toc-parts-uppercase Hardcode "PARTIES" uppercase + black bottom border on TOC label `(about.php, charte.php, licence.php, apropos.css)` ✓ -- [x] #apropos-overflow Prevent #apropos-intro and content-section children from overflowing `(apropos.css)` ✓ -- [x] #toc-navigation Fix TOC links not navigating to headings — added `apply_id_to_heading: true` to CommonMark config so IDs land on headings not hidden elements; added scroll-margin-top to headings; unstuck main from flex container so sticky TOC works for full page height `(CharteController.php, LicenceController.php, apropos.css)` ✓ -- [x] #apropos-toc-style Fix TOC "Parties" label: Ductus font + lowercase, remove border-left from links, match global link style; rename .apropos-content → section.content, .apropos-section → .content-section, remove .prose wrapper `(apropos.css, about.php, charte.php, licence.php)` ✓ -- [x] #apropos-toc-confirm Fixed sticky TOC: removed `flex: 1; min-height: 0` on main for apropos-body so the sticky container is full content height; added `max-height` + `overflow-y: auto` to TOC for long lists `(apropos.css)` ✓ -- [x] #fix-finality-types Create standalone script + just command to rename finality types (Approfondi→Approfondie, Enseignement→Didactique, Spécialisé→Spécialisée) `(scripts/fix-finality-types.php, justfile)` ✓ -- [x] #context-note-synopsis Display contextual note above synopsis (italic) instead of in meta column on TFE page `(tfe.php, tfe.css)` ✓ -- [x] #decouple-contacts Decouple contact_visible (public) & contact_interne (private email): backend already decoupled; made contact_public checkbox functional in admin add/edit forms; contact_public now controls TFE page visibility `(FormBootstrap.php, ThesisCreateController.php, ThesisEditController.php, tfe.php, form.php)` ✓ -- [x] #csrf-rotation-race Stop CSRF token rotation in draft.php + remove hx-post from

— both broke FilePond uploads and form submission `(admin/actions/draft.php, partage/fragments/draft.php, FormBootstrap.php, pill-search.js)` ✓ -- [x] #adminold-return-type Fix adminOld closure return type from `:string` to `:string|array` `(FormBootstrap.php)` ✓ -- [x] #duration-integer-units Make duration field: integer for pages/Mo, dedicated h/m/s time inputs `(form.php, ThesisCreateController.php, tfe.php, form-base.css)` ✓ -- [x] #licence-svg-fix Fix licence details/summary SVG: width 1rem, inline-flex layout `(fieldset-licence-explanation.php, form-base.css)` ✓ -- [x] #restore-languages Un-soft-delete anglais (id=2) and néerlandais (id=71) in dev DB ✓ -- [x] #php-upload-limits Increase PHP upload_max_filesize to 8G, post_max_size to 8.5G `(.user.ini)` ✓ -- [x] #formdata-fieldset-crash Remove leftover debug console.log that called new FormData(fieldset) `(admin/footer.php)` ✓ -- [x] #csp-media-iframe-fix Fix CSP `frame-ancestors 'none'` blocking PDF iframes — replaced `try_files` redirect with direct `fastcgi_pass` in `location = /media` so `add_header` CSP override survives internal nginx redirect `(nginx/xamxam.conf)` ✓ -- [x] #duration-migration Add migration to reintroduce `duration_value` and `duration_unit` columns + update views `(migrations/applied/040_duration_fields.sql)` ✓ -- [x] #duration-database Update `createThesis`, `updateThesis`, `getThesisRawFields` in Database `(Database.php)` ✓ -- [x] #duration-controllers Handle duration in `ThesisCreateController` and `ThesisEditController` `(ThesisCreateController.php, ThesisEditController.php)` ✓ -- [x] #duration-form Add duration fieldset (value + unit dropdown) to form template `(templates/partials/form/form.php)` ✓ -- [x] #duration-display Show duration on public TFE detail page `(templates/public/tfe.php)` ✓ -- [x] #duration-view Include duration in v_theses_full and v_theses_public `(migrations/applied/040_duration_fields.sql, schema.sql, schema.sql.new)` ✓ -- [x] #duration-bootstrapWire Wire duration variables through FormBootstrap adminFormVariables `(FormBootstrap.php)` ✓ -- [x] #cleanup-drafts Add periodic cleanup job for orphaned drafts (`Database.php`, `justfile`, `deploy/xamxam-cleanup.cron`, `scripts/cleanup-drafts.php`) ✓ -- [x] #form-setup-helper Add `FormBootstrap` helper class to reduce bootstrap duplication across add/edit/partage `(admin/add.php)` `(admin/edit.php)` ✓ -- [x] #two-phase-commit Add two-phase commit: INSERT thesis `status='draft'`, COMMIT, move files, UPDATE to `active` `(ThesisCreateController.php)` ✓ -- [x] #filepond-preserve Preserve FilePond temp file IDs on partage validation redirect `(partage/index.php)` `(FilepondHandler.php)` ✓ -- [x] #refactor-partage Extract partage form page chrome to `templates/partage/form-page.php` `(partage/index.php)` ✓ -- [x] #htmx-migration HTMX v2 migration: OverType editors, autosave handler, backend `HX-Request` detection ✓ -- [x] #filepond-crash FilePond crash on TFE upload forms: root cause fixed (`.filename` → `.name`), all crashes resolved ✓ -- [x] #aria-errormessage WCAG AA: field-level `aria-errormessage`, `aria-invalid`, `aria-describedby` on all form fields ✓ -- [x] #nojs-upload-fix No-JS file uploads: `filepond_mode` default to `0 disabled`, server-side `$_FILES` fallback ✓ -- [x] #autosave-partage Autosave text fields on partage form: session draft endpoint (`fragments/draft.php`), HTMX autosave on change/input, page-load hydration, "Brouillon enregistré" indicator, draft cleared on submit ✓ -- [x] #autosave-partage-wire Wire `formExtraAttrs`, `showAutosaveStatus`, draft hydration, `autosave-handler.js`, draft cleanup into partage form (`partage/index.php`, `partage/form-page.php`) ✓ -- [x] #mobile-responsive Mobile-responsive form layout: `@media (max-width: 600px)` breakpoint, 44×44px touch targets ✓ -- [x] #aria-fieldset-fix Remove invalid `required` attribute from `
`, keep `aria-required="true"`, add `role="group"` ✓ -- [x] #split-form-css Split `form.css` into `form-base.css` and `form-admin.css` ✓ -- [x] #extra-css-admin Update `head.php` to support `$extraCssAdmin` for admin-only stylesheets `(head.php)` ✓ - +- [x] Fix markdown cheatsheet modal 500 error — missing `require_once bootstrap.php` in `app/public/admin/markdown-cheatsheet-fragment.php` +- [x] Add `padding-bottom` to `.md-cheatsheet-dialog` so last table row isn't flush against dialog edge diff --git a/app/public/admin/markdown-cheatsheet-fragment.php b/app/public/admin/markdown-cheatsheet-fragment.php index 7cddb9e..394c6a7 100644 --- a/app/public/admin/markdown-cheatsheet-fragment.php +++ b/app/public/admin/markdown-cheatsheet-fragment.php @@ -4,6 +4,8 @@ * Rendered as a element; caller should call .showModal() after swap. */ +require_once __DIR__ . '/../../bootstrap.php'; + $rows = [ [ 'syntax' => '# Titre 1', diff --git a/app/public/assets/css/admin.css b/app/public/assets/css/admin.css index 524018f..b72387d 100644 --- a/app/public/assets/css/admin.css +++ b/app/public/assets/css/admin.css @@ -2335,7 +2335,7 @@ th.admin-ap-col { .md-cheatsheet-dialog { border: 1px solid var(--border); border-radius: var(--radius-m, 8px); - padding: 0; + padding: 0 0 var(--space-m); max-width: 640px; width: 90vw; box-shadow: 0 8px 32px rgba(0,0,0,0.18); diff --git a/app/public/assets/css/repertoire.css b/app/public/assets/css/repertoire.css index 7d0345b..02029ea 100644 --- a/app/public/assets/css/repertoire.css +++ b/app/public/assets/css/repertoire.css @@ -20,8 +20,8 @@ minmax(9rem, 1fr) minmax(7rem, 1fr) minmax(8rem, 1fr) - minmax(min-content, 1fr); - grid-template-rows: auto 1fr; + minmax(3rem, 0.45fr); + grid-template-rows: auto minmax(0, 1fr); gap: var(--space-s); justify-content: space-between; padding: 0; @@ -69,7 +69,7 @@ overflow-wrap: break-word; } -.repertoire-col > ul { +.repertoire-col .rep-accordion__panel > ul { grid-row: 2; overflow-y: auto; overflow-x: hidden; diff --git a/app/public/assets/css/tfe.css b/app/public/assets/css/tfe.css index ee5a92a..551f28a 100644 --- a/app/public/assets/css/tfe.css +++ b/app/public/assets/css/tfe.css @@ -52,6 +52,8 @@ height: auto; display: block; border-radius: var(--radius); + user-select: none; + -webkit-user-drag: none; } .tfe-file-item embed, @@ -161,12 +163,6 @@ margin: 0; } -/* PDF fallback download link */ -.tfe-pdf-fallback { - font-size: var(--step--1); - margin: var(--space-3xs) 0 0; -} - /* Audio player */ .tfe-audio { width: 100%; @@ -174,55 +170,6 @@ display: block; } -/* Download-only files */ -.tfe-download-file { - display: flex; - align-items: center; - gap: var(--space-xs); - padding: var(--space-s) var(--space-m); - background: var(--bg-secondary); - border: 1px solid var(--border-primary); - border-radius: var(--radius); - flex-wrap: wrap; -} - -.tfe-download-link { - display: flex; - align-items: center; - gap: var(--space-xs); - font-size: var(--step--1); - font-weight: 500; - color: var(--text-primary); - text-decoration: none; - word-break: break-all; -} - -.tfe-download-link:hover { - color: var(--accent-primary); - text-decoration: none; -} - -.tfe-download-icon { - font-size: 1.3rem; - line-height: 1; - flex-shrink: 0; -} - -.tfe-download-size { - font-size: var(--step--2); - color: var(--text-tertiary); - margin-left: auto; -} - -.tfe-pdf-fallback a { - color: var(--text-primary); - text-decoration: none; -} - -.tfe-pdf-fallback a:hover { - color: var(--accent-primary); -} - /* "Not available" and "no files" notices */ .tfe-restricted, .tfe-no-files { diff --git a/app/src/Controllers/MediaController.php b/app/src/Controllers/MediaController.php index cc7fbe3..e6638b6 100644 --- a/app/src/Controllers/MediaController.php +++ b/app/src/Controllers/MediaController.php @@ -108,10 +108,7 @@ class MediaController exit; } - // 5. Determine if download was explicitly requested - $forceDownload = !empty($_GET['download']) && $_GET['download'] === '1'; - - // 6. Send response headers + // 5. Send response headers header('Content-Type: ' . $mimeType); header('Content-Length: ' . (int) filesize($realFull)); header('X-Content-Type-Options: nosniff'); @@ -121,12 +118,10 @@ class MediaController header('Cache-Control: public, max-age=86400'); } elseif (in_array($ext, ['jpg','jpeg','png','gif','webp'], true)) { header('Cache-Control: public, max-age=604800'); - if (!$forceDownload) { - header('Content-Disposition: inline'); - } + header('Content-Disposition: inline'); } elseif ($ext === 'pdf') { header('Cache-Control: public, max-age=86400'); - header('Content-Disposition: ' . ($forceDownload ? 'attachment' : 'inline')); + header('Content-Disposition: inline'); } elseif (in_array($ext, ['mp4','webm','ogv','mov'], true)) { // Video: no cache-control range requests should work header('Accept-Ranges: bytes'); @@ -135,10 +130,9 @@ class MediaController header('Accept-Ranges: bytes'); header('Cache-Control: public, max-age=86400'); } else { - // Unknown / other: force download - $safeFilename = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($realFull)); - header('Content-Disposition: attachment; filename="' . $safeFilename . '"'); - header('Cache-Control: private, no-store'); + // Unknown / other: serve inline, no download + header('Content-Disposition: inline'); + header('Cache-Control: public, max-age=86400'); } // 7. Stream file (with range support for media) diff --git a/app/templates/public/tfe.php b/app/templates/public/tfe.php index 1d5dddc..599daed 100644 --- a/app/templates/public/tfe.php +++ b/app/templates/public/tfe.php @@ -253,7 +253,7 @@

Lien : @@ -382,7 +382,7 @@ ?>

+ +