mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-08-10 23:31:21 +02:00
6269 lines
272 KiB
Plaintext
6269 lines
272 KiB
Plaintext
72d3075466cc | 2026-06-29 17:27
|
||
|
||
---
|
||
a790ddc02ad0 | 2026-06-24 14:57
|
||
Fix admin login heading: prevent 'Administration' from hyphenating or overflowing box
|
||
|
||
---
|
||
21f16ee093ec | 2026-06-24 14:49
|
||
chore: move #rep-polish to Completed, investigate #tighten-csp blockers
|
||
|
||
---
|
||
eb706214ce16 | 2026-06-24 14:45
|
||
feat: scroll-position memory on repertoire HTMX swaps + swap transition polish
|
||
|
||
- Add repertoire-scroll-restore.js: snapshots scrollTop of each column <ul>
|
||
before htmx:beforeSwap, restores after htmx:afterSwap (keyed by data-col)
|
||
- Add subtle opacity transition on #repertoire-index during htmx-swapping
|
||
- Tighten rep-indicator opacity transition to 0.1s for snappier feedback
|
||
- Import new module in public-entry.js
|
||
|
||
---
|
||
e0cf9f8f5746 | 2026-06-24 14:26
|
||
chore: update TODO — mark icon-color-verify and 3 security tasks complete
|
||
|
||
---
|
||
0062b296782e | 2026-06-24 14:17
|
||
Fix security issues from audit: gate partage fragments on share_active session, add CSRF to retry-email POST, remove dead App::verifyCsrf()
|
||
|
||
---
|
||
84869ad96811 | 2026-06-24 14:15
|
||
Fix open redirect in tag.php + language.php: reject protocol-relative URLs (//evil.com) by also checking for // prefix
|
||
|
||
---
|
||
6ecd3d4540ef | 2026-06-24 13:21
|
||
Fix biome lint errors: remove duplicate CSS properties, apply safe auto-fixes
|
||
|
||
CSS:
|
||
- Remove duplicate 'background' fallbacks in base.css, header.css, search.css
|
||
(solid color declared before gradient — gradient always wins)
|
||
- Remove duplicate 'padding' in admin.css .admin-import-log
|
||
|
||
JS (biome --write safe fixes applied):
|
||
- function() → arrow functions in all IIFEs and callbacks
|
||
- forEach/callback → arrow functions
|
||
- evaluePtrn → parseInt(x, 10) in admin-contacts-form.js
|
||
- Cleaned label text in build.mjs lint step
|
||
|
||
Remaining warnings are intentional: !important overrides, descending
|
||
specificity (admin.css cascade), noUnusedVariables (functions exported
|
||
to window/onclick), useTemplate style preference.
|
||
|
||
---
|
||
82d3dcb084b7 | 2026-06-24 13:18
|
||
Fix repertoire column scrolling + admin TOC duplication
|
||
|
||
- repertoire.css: add min-height: 0 to column <ul> scroll containers so
|
||
grid 1fr row shrinks below content and overflow-y: auto activates
|
||
- admin-toc.js: add __adminTocBuilt guard + nav.children check to prevent
|
||
double population when loaded both via admin.min.js and direct <script>
|
||
- admin-toc.php: remove duplicate <script src="admin-toc.js"> tag —
|
||
JS is already bundled in admin.min.js
|
||
|
||
---
|
||
9f8a4be84efe | 2026-06-24 13:11
|
||
Add dev-watch recipe with chokidar-cli for live rebuild on CSS/JS changes
|
||
|
||
- just dev now spawns a background chokidar watcher alongside the PHP server
|
||
- CSS/JS changes auto-rebuild into dist/ (~200ms per rebuild)
|
||
- just stop kills both the PHP server and the watcher
|
||
- just dev-watch still available standalone for split-terminal workflows
|
||
- chokidar-cli added as devDependency
|
||
|
||
---
|
||
20fe4b6c8c69 | 2026-06-24 13:09
|
||
Add biome + rolldown + lightningcss build pipeline for JS/CSS bundling & minification
|
||
|
||
- package.json with biome, rolldown, lightningcss devDependencies
|
||
- biome.json: add CSS formatter support
|
||
- scripts/build-css.mjs: lightningcss resolves @import chain, bundles/minifies CSS
|
||
- scripts/build-js.mjs: rolldown per-entry JS bundling (no code splitting)
|
||
- scripts/build.mjs: orchestrator for both CSS + JS
|
||
- scripts/check-build.mjs: staleness checker for CI/deploy guard
|
||
- justfile: add build, build-css, build-js, build-install, build-check recipes
|
||
- justfile: deploy recipe now runs build before deploy-code
|
||
- head.php + form-page.php: use dist/base.min.css instead of style.css
|
||
- All controllers + FormBootstrap: reference dist/*.min.{css,js}
|
||
- admin footer: load admin.min.js for all admin pages
|
||
- repertoire: use public.min.js instead of individual app JS files
|
||
- Fix stray '}' syntax error in admin.css line 305
|
||
- .gitignore: add app/public/assets/dist/
|
||
|
||
---
|
||
e74f9210c5d6 | 2026-06-24 12:56
|
||
#gzip #extract-inline-js enable gzip in nginx + move ~730 lines of inline JS to 15 external files
|
||
|
||
---
|
||
0ff6ee78d9e9 | 2026-06-24 12:49
|
||
#todo add gzip + inline-js extraction tasks from analysis
|
||
|
||
---
|
||
3c786b037a38 | 2026-06-24 11:32
|
||
Sort all filter columns: matched entries first, then unmatched, both alphabetical
|
||
|
||
---
|
||
982d91345dea | 2026-06-24 11:28
|
||
Sort keywords column: matched entries first, then unmatched, both alphabetical
|
||
|
||
When filters are active, viable (matched) keywords now appear at the top
|
||
of the Mots-clés column so users don't have to scroll past faded entries
|
||
to find clickable tags. Both groups maintain alphabetical order internally.
|
||
|
||
---
|
||
654c884fed4b | 2026-06-24 11:20
|
||
Remove mobile filter chip bar from repertoire page
|
||
|
||
The sticky chip bar added noise: users already see their active
|
||
filters highlighted in the accordion columns (with rep-entry--selected
|
||
styling and the active-count badge on each toggle). Removing chips
|
||
shifts focus back to the accordions, consistent with the expectation
|
||
that users return to the dropdowns to adjust filters.
|
||
|
||
---
|
||
bb3bb805670a | 2026-06-24 11:12
|
||
Fix accordion re-init after HTMX outerHTML swap on repertoire page
|
||
|
||
The htmx:afterSwap handler was calling initAccordions(e.detail.target)
|
||
but after an outerHTML swap, e.detail.target references the old detached
|
||
DOM element, not the new live element. This meant accordion click
|
||
handlers were never attached to the new filter column toggles after
|
||
applying or removing a filter — breaking all subsequent interaction.
|
||
|
||
Fix: query the live DOM with document.querySelector() instead.
|
||
|
||
---
|
||
ecb90ba5dd34 | 2026-06-22 15:45
|
||
Add accordion + active-filter chip bar for mobile repertoire
|
||
|
||
- repertoire-index.php: wrap each filter column in rep-accordion with toggle
|
||
button, chevron, badge (active filter count); add rep-chip-bar with
|
||
removable active-filter chips above the columns
|
||
- repertoire.css: mobile (≤640px) accordion mode — columns collapse to
|
||
single-open accordion sections with 48px touch targets; chip bar becomes
|
||
sticky; desktop/tablet layout unchanged via display:none on toggle elements
|
||
- repertoire.php: JS for single-accordion-open behavior on mobile, HTMX
|
||
re-init after swap, resize-breakpoint cleanup
|
||
- docs/repertoire-mobile-propositions.md: analysis + 4 architecture proposals
|
||
|
||
---
|
||
cca3d08f058f | 2026-06-22 15:25
|
||
Replace apropos/licence/charte sticky nav with responsive details/summary TOC
|
||
|
||
- Convert .apropos-toc <nav> to <details class="toc" open> in all three templates
|
||
- Add caret-down icon to summary (visible only on mobile via media queries)
|
||
- Desktop (≥768px): sticky sidebar via CSS grid, force-open via pointer-events:none, hide caret
|
||
- Mobile (≤767px): single column, collapsible TOC with rotating caret, margin-top below search bar
|
||
- Rename .apropos-toc-link to .toc-sidebar-link
|
||
- Merge 900px and 600px breakpoints into single 600px mobile media query
|
||
- Rename apropos.css → content-page.css; update 3 controller references
|
||
|
||
---
|
||
0a2b4781d143 | 2026-06-21 18:12
|
||
Fix search icon wrong color
|
||
|
||
---
|
||
6aee5ec131bf | 2026-06-21 17:52
|
||
Clean up cleanup dialog
|
||
|
||
- Remove duplicate 'Téléversements abandonnés' heading already communicated by the summary
|
||
- Replace #peertube-orphans-wrapper with display:contents wrapper so PeerTube details sit as direct grid siblings
|
||
- Add hx-confirm to all delete buttons (filepond, trash, PeerTube orphans)
|
||
|
||
---
|
||
dfde88eaa5d6 | 2026-06-21 17:23
|
||
Migrate all <img>-based icons to inline SVG via PHP helper
|
||
|
||
Replace every <img src="/assets/icons/..."> with <?= icon('name') ?>
|
||
across 26 template files. The PHP helper inlines the SVG markup into the
|
||
DOM so CSS color cascades naturally through fill="currentColor".
|
||
|
||
- Add src/icon.php helper: reads SVG file, sets width/height to 1em,
|
||
injects aria-hidden, supports optional CSS class
|
||
- Fix 12 icon SVGs that had hardcoded fill="#000000" or missing fill attr
|
||
- Replace search.svg with Phosphor fill-based magnifying glass
|
||
- Add explicit SVG sizes for admin header nav icons (16px/20px)
|
||
- Scope public search icon CSS to form[role=search]:not(.header-search-form)
|
||
to avoid breaking admin header layout; change stroke to fill
|
||
- Remove <img> filter: brightness(0) invert(1) hacks from admin.css
|
||
|
||
---
|
||
b1774e6e9798 | 2026-06-21 17:04
|
||
Replace all inline SVGs with icon files, ensure currentColor fill for proper color inheritance on buttons
|
||
|
||
---
|
||
71a92d682b86 | 2026-06-21 16:50
|
||
Fix nettoyage modal: SVG icon files, padding/margin consistency, BBBDMSans font, fix HTMX trigger, nested details cleanup
|
||
|
||
---
|
||
03c9c3566ff3 | 2026-06-21 13:33
|
||
Add SQLite indexes for contenus page language/tag queries + WIP: Peertube orphans, dialogs, contact decoupling, context note, finality types
|
||
|
||
---
|
||
0d5e9dac19fd | 2026-06-20 13:29
|
||
fix: make sticky TOC work for full scroll height and fix heading anchor links
|
||
|
||
---
|
||
19bf9f101ac6 | 2026-06-15 16:35
|
||
Refactor apropos/charte/licence pages: shared layout, TOC anchors, and UI polish
|
||
|
||
Unify the three public pages (à propos, charte, licence) onto a single
|
||
grid layout (.page-content) with sticky TOC sidebar, replacing the old
|
||
separate / / markup.
|
||
|
||
- Merge about.php, charte.php, licence.php templates into shared
|
||
.page-content / .content-section structure
|
||
- Add CommonMark HeadingPermalinkExtension for stable heading anchors
|
||
- Use SlugNormalizer for TOC links so they match rendered heading IDs
|
||
- Standardize link styling across content blocks: bold black, accent on
|
||
hover (consistent with global link style)
|
||
- Fix code block wrapping: use pre-wrap instead of pre, constrain grid
|
||
columns with min-width:0, auto scrollbar
|
||
- Fix apropos page grid placement: force content-section into column 2
|
||
so contacts and credits stay in the content area, not the sidebar
|
||
|
||
Also includes accumulated WIP changes:
|
||
- Header gradient: hardcoded purple-to-green (replaces CSS variables)
|
||
- Search placeholder font
|
||
- Duration field: replace minutes/sec/heures with h:m:s time inputs
|
||
- TFE file optional for formats 1,4,6 with client-side JS toggle
|
||
- Licence form: em-dash to hyphen, details/summary classes
|
||
- Pill search: block Enter key form submission when no results
|
||
- Draft autosave: remove CSRF rotation (broke concurrent FilePond uploads)
|
||
- Language pill: clear hints for excluded main languages
|
||
- Search results: gradient placeholder cards for items without covers
|
||
- TFE display: format durée values as XhYm instead of decimal
|
||
|
||
---
|
||
928e074d24f2 | 2026-06-15 16:27
|
||
fix: update dev server PHP limits in justfile to match large video uploads
|
||
|
||
The just dev command hardcodes upload_max_filesize=512M and
|
||
post_max_size=520M via -d flags, which override .user.ini.
|
||
Raised to 8192M/8704M to match the JS-side 8GB video size
|
||
caps. Also raised max_execution_time and max_input_time to
|
||
600s to accommodate large file transfers and PeerTube uploads.
|
||
|
||
---
|
||
c8af3bf869fe | 2026-06-15 16:27
|
||
fix: remove leftover debug console.log that crashed HTMX with new FormData(fieldset)
|
||
|
||
The htmx:beforeSend listener in admin/footer.php was a debugging
|
||
leftover that called new FormData(e.target.closest('fieldset')).
|
||
FormData only accepts HTMLFormElement or nothing — passing a
|
||
<fieldset> throws 'Argument 1 does not implement interface
|
||
HTMLFormElement'. Removed the serialization call; kept the
|
||
minimal debug log.
|
||
|
||
---
|
||
2633cb13b5f6 | 2026-06-15 16:17
|
||
fix: increase PHP upload limits to support large video files
|
||
|
||
Raised upload_max_filesize from 512M to 8192M (8G) and post_max_size
|
||
from 520M to 8704M to match the JS-side per-extension size caps that
|
||
allow up to 8GB for video files (mp4, webm, mov, etc.). Also raised
|
||
memory_limit to 512M and max_input_time to 600s.
|
||
|
||
---
|
||
a5db81a73d5a | 2026-06-15 16:16
|
||
fix: change adminOld return type from string to string|array
|
||
|
||
The closure returned arrays when formData values were arrays (e.g.
|
||
jury_promoteur), but the PHP return type annotation was :string.
|
||
PHP 8.x enforces this strictly, causing a fatal TypeError in
|
||
jury-fieldset.php on add mode.
|
||
|
||
---
|
||
d588ae004d45 | 2026-06-11 13:05
|
||
Reintroduce TFE duration metadata: DB columns, form fields, controllers, views, and migration
|
||
|
||
Add 'unsafe-eval' to CSP script-src directives (htmx requires Function())
|
||
|
||
---
|
||
00fed5f0e3f8 | 2026-06-11 12:23
|
||
Add periodic cleanup of orphaned drafts: cleanup job, just command, deploy cron
|
||
|
||
---
|
||
a19e9e145452 | 2026-06-11 12:11
|
||
Extract FormBootstrap helper to eliminate bootstrap duplication across add/edit form pages
|
||
|
||
---
|
||
f4a3e26901e8 | 2026-06-11 12:09
|
||
Add thesis status column for two-phase commit lifecycle tracking
|
||
|
||
---
|
||
11a6f6a9f2a4 | 2026-06-11 11:41
|
||
Preserve FilePond temp files across partage validation redirects
|
||
|
||
---
|
||
b744271cf633 | 2026-06-11 11:29
|
||
Extract partage page chrome to templates/partage/form-page.php
|
||
|
||
---
|
||
cbd369bc7284 | 2026-06-11 11:04
|
||
Split form.css into form-base.css and form-admin.css, drop dead upload-progress code
|
||
|
||
Also introduces $extraCssAdmin support in head.php for admin-only
|
||
stylesheets (form-admin.css, filepond CSS, system.css). Admin pages
|
||
now use $extraCssAdmin for admin-only assets and $extraCss for
|
||
shared stylesheets like form-base.css.
|
||
|
||
---
|
||
99125cc8e310 | 2026-06-11 10:32
|
||
Add autosave draft system for partage form with HTMX-based session persistence
|
||
|
||
- New fragment endpoint POST/GET /partage/fragments/draft.php:
|
||
saves all form fields to PHP session, excludes file/csrf/slug fields
|
||
GET returns JSON for JS hydration on page load
|
||
rotates both global CSRF and share CSRF tokens in sync
|
||
|
||
- form.php accepts optional $formExtraAttrs and $showAutosaveStatus:
|
||
allows injecting HTMX attributes and 'Brouillon enregistré' indicator
|
||
|
||
- renderShareLinkForm adds hx-post with change/input debounce trigger,
|
||
loads autosave-handler.js, hydrate fields from draft on page load
|
||
|
||
- Draft cleared on successful form submission in handleShareLinkSubmission
|
||
|
||
- autosave-handler.js now also updates share_link_token hidden input
|
||
when rotating CSRF token (partage form uses both csrf_token and share_link_token)
|
||
|
||
- Added .autosave-status CSS to form.css (was admin.css-only)
|
||
|
||
- Updated fragment routing to accept GET requests (needed for draft hydration)
|
||
|
||
---
|
||
4b37a05be39c | 2026-06-11 10:32
|
||
Guard no-JS file uploads: disabled filepond_mode by default, server-side fallback
|
||
|
||
The partage/admin form had a hardcoded filepond_mode=1 hidden input,
|
||
so without JavaScript the server always entered the FilePond async
|
||
path — which found no hex IDs and silently dropped all files.
|
||
|
||
Three-layer fix:
|
||
1. HTML: filepond_mode input starts disabled with value=0; JS enables
|
||
it and sets value=1 on DOMContentLoaded (and after HTMX swaps).
|
||
Disabled inputs aren't submitted → server gets no filepond_mode
|
||
→ naturally falls to legacy path.
|
||
2. JS: enableFilepondMode() called on page load and hx:afterSwap so
|
||
FilePond-enhanced forms always send filepond_mode=1.
|
||
3. Server (defense-in-depth): ThesisFileHandler::hasFilePondQueueData()
|
||
scans POST['queue_file'] for 32-char hex IDs; ThesisCreateController
|
||
and ThesisEditController use it alongside filepond_mode, so even if
|
||
the flag somehow arrives without async upload IDs, the path
|
||
takes over.
|
||
|
||
---
|
||
63e65d98562e | 2026-06-11 10:23
|
||
Add mobile-responsive form layout with WCAG 2.5.5 touch targets
|
||
|
||
Add @media (max-width: 600px) rule to form.css:
|
||
- Stack form row labels above inputs (1fr grid, single column)
|
||
- Ensure 44×44px minimum touch targets on checkboxes, radios,
|
||
selects, textareas, text inputs, and .btn/.btn--sm
|
||
- Stack thesis-add-header and recap-dl grids to single column
|
||
- Stack form footer buttons vertically with full width
|
||
- Unstick sticky formats fieldset on mobile
|
||
- Tighten fieldset margins for narrow viewports
|
||
|
||
---
|
||
e17246c850cf | 2026-06-11 10:22
|
||
Add field-level aria-errormessage, aria-invalid, and aria-describedby across the TFE form
|
||
|
||
WCAG 3.3.1 (Error Identification): failing fields now get
|
||
aria-errormessage pointing to the flash-error container and
|
||
aria-invalid="true". WCAG 3.3.3 (Error Suggestion): <small>
|
||
hint text on inputs, selects, and file fields is now linked via
|
||
aria-describedby (always, not just on error).
|
||
|
||
Changes:
|
||
- text-field.php, select-field.php, checkbox-list.php: accept
|
||
$errorFieldName; add aria-errormessage/aria-invalid on match;
|
||
add id to <small> and aria-describedby on the control
|
||
- fieldset-tfe-info.php: aria-invalid on synopsis textarea
|
||
- fichiers-fragment.php: aria-describedby on cover, note
|
||
d'intention, TFE, annexes, and website inputs; aria-invalid
|
||
on format checkboxes when error matches 'formats'
|
||
- form.php: id="flash-error" + tabindex="-1" on flash-error
|
||
div; accept $errorFieldName from callers
|
||
- admin/add.php: set $errorFieldName, wire $withAutofocusFn
|
||
(was identity default)
|
||
- admin/edit.php: set $errorFieldName
|
||
- partage/index.php: consume autofocus field, wire autofocus
|
||
function, add App::flashAutofocus() in submit catch block
|
||
|
||
Also fixes WCAG standards issue: removed invalid 'required'
|
||
HTML attribute from <fieldset> elements in checkbox-list.php
|
||
and fichiers-fragment.php (only aria-required stays). Added
|
||
role="group" for explicit ARIA semantics.
|
||
|
||
---
|
||
c0ba99e86151 | 2026-06-11 10:14
|
||
TODO: add form accessibility & resilience tasks from assessment
|
||
|
||
---
|
||
2c6b55777f57 | 2026-06-10 00:13
|
||
Fix logs being captured
|
||
|
||
---
|
||
fb752f5ba27e | 2026-06-09 23:35
|
||
cleanup: remove _write guard — FilePond external API doesn't expose _write
|
||
|
||
ro=['fire','_read','_write'] is an exclusion list in Ee(), not an inclusion
|
||
list. The external pond object has none of these. The only safe interception
|
||
point is inside the closure (vendor patch), but the root-cause fix
|
||
(fileValidateSizeFilter .filename → .name) already prevents the crash.
|
||
|
||
---
|
||
6d93199fa2e6 | 2026-06-09 23:22
|
||
docs: HTMX/destroy race hypothesis investigation — REFUTED
|
||
|
||
Investigation verdict:
|
||
- HTMX does NOT swap the FilePond container on the edit page; the
|
||
htmx:targetError in the crash log is unrelated noise
|
||
- The pre-destroy abort in destroyFilePondsIn has a wrong status check
|
||
(filters for nonexistent status 4, misses status 7 LOADING) but is
|
||
moot because no HTMX swap targets the FilePond container
|
||
- The load-file-error -> DID_THROW_ITEM_INVALID path is vulnerable
|
||
(passes t.status directly, unlike every other error handler which
|
||
wraps it), but for local files the LOAD_FILE plugins always wrap
|
||
rejections properly
|
||
- Likely actual trigger: Firefox XHR abort edge case in server.load
|
||
for the existing cover file, racing with addition of a new local file
|
||
that replaces it in the single-file queue
|
||
|
||
---
|
||
d8d925243e3b | 2026-06-09 23:02
|
||
docs: add filepond crash analysis report
|
||
|
||
Documents the 'can't access property main, n.status is undefined'
|
||
crash in FilePond 4.32.12. Root cause: vendor code in filepond.min.js
|
||
has a property name mismatch — createResponse objects use .code but the
|
||
load-file-error handler reads .status. When action.status is undefined,
|
||
the view writers crash.
|
||
|
||
Proposes Option B (custom load function) as the cleanest fix.
|
||
|
||
---
|
||
2829d13a16bb | 2026-06-09 21:53
|
||
filepond: fix crash 'can't access property main, n.status is undefined'
|
||
|
||
Fixes three root causes of FilePond errors on TFE upload forms:
|
||
|
||
1. server.process.onerror accessed .status on a string (XHR response
|
||
text body) — now extracts the body safely.
|
||
|
||
2. server.load was a bare URL string with no error handling — converted
|
||
to object with onload/onerror to prevent FilePond internal _write
|
||
crash when load.php returns HTTP errors.
|
||
|
||
3. destroyFilePondsIn now aborts in-flight processing before pond.destroy()
|
||
to prevent stale XHR callbacks firing on a torn-down FilePond instance.
|
||
|
||
Server-side: FilepondHandler now emits Content-Type: text/plain on all
|
||
responses (PHP defaults to text/html on die(), confusing FilePond's
|
||
response parser).
|
||
|
||
---
|
||
38ef55039727 | 2026-06-09 19:57
|
||
feat: render actual elements in markdown cheatsheet instead of labels
|
||
|
||
Replace text labels (h1, bold, italic) with rendered HTML in the Rendu column:
|
||
headings, strong, em, del, code, links, blockquote, lists, hr, sup, small
|
||
|
||
---
|
||
4a2b000fca3f | 2026-06-09 19:43
|
||
Add Charte static page (public + admin editing)
|
||
|
||
---
|
||
317547ac93fc | 2026-06-09 19:35
|
||
Fix #4 v2: decouple contact_interne from contact_visible in ThesisCreateController
|
||
|
||
validateAndSanitise() no longer cross-contaminates:
|
||
- contact_interne overwrote mail, which then copied to contact_visible
|
||
- Fixed: contactInterne from contact_interne (admin) or confirmation_email (student)
|
||
- Fixed: contactVisible from contact_visible (admin) or mail (student)
|
||
- Fixed: submit() uses contactInterne as author email, not mail
|
||
|
||
---
|
||
1490c9926800 | 2026-06-09 17:41
|
||
Fix FilePond: maxFileSize as bytes + temp files survive page reload
|
||
|
||
1. maxFileSize bug: FileValidateSize plugin overrides core's maxFileSize
|
||
setter. Core uses toBytes('1GB') = 1073741824, but plugin registers
|
||
maxFileSize as [null, Type.INT] which calls toInt('1GB') = 1.
|
||
Fix: all maxFileSize and perExtensionMaxSize values as raw bytes.
|
||
Also fix option name: fileValidateSizeFilterItem → fileValidateSizeFilter.
|
||
|
||
2. Temp file persistence: files uploaded via FilePond went to
|
||
tmp/filepond/ and vanished from the UI on page reload because
|
||
data-existing-files only included DB-persisted files.
|
||
Fix: session-track temp file_ids in handleProcess, inject via
|
||
getSessionTempFiles() into data-existing-files, teach handleLoad
|
||
to stream temp files from disk, and route JS remove → revert for hex IDs.
|
||
|
||
---
|
||
c4a550f9d14f | 2026-06-09 17:10
|
||
Rework contenus-edit: auto-save, OverType toolbar, dynamic sidebar links
|
||
|
||
- Auto-save: new autosave.js with 1.5s debounce, watches all forms with
|
||
data-autosave, POSTs to form action with Accept: application/json, shows
|
||
saving/saved/error status indicator
|
||
- All action handlers (page.php, apropos.php, form-help.php) now detect
|
||
JSON Accept header and return {success, csrf_token} or {error} responses
|
||
- OverType toolbar enabled (toolbar:true) on all three markdown editors
|
||
(page, about_page, form_help)
|
||
- Sidebar links: replaced fixed erg_site_url / source_code_url rows with
|
||
dynamic sidebar_links array of {label, url} objects. Add/remove via JS.
|
||
Fallback migration reads legacy keys if sidebar_links is empty.
|
||
- Updated AboutController and about.php template to render dynamic links
|
||
- Updated apropos.css: unified .apropos-toc-link replacing .apropos-toc-erg
|
||
and .apropos-toc-source
|
||
- New CSS: autosave-status states, sidebar-link-row layout
|
||
- Removed all Enregistrer + Annuler buttons — auto-save and h1 back-arrow
|
||
make them redundant
|
||
|
||
---
|
||
a45a2c9ac4eb | 2026-06-09 16:49
|
||
Changed dev serve recipe to be just dev
|
||
|
||
---
|
||
655dd4c038cb | 2026-06-09 14:01
|
||
feat: clarification contact étudiant + déplacer Contact visible dans Informations du TFE
|
||
|
||
- Label : « Contact visible (optionnel) », placeholder : mail/site/insta/etc.
|
||
- Hint : demander l'URL complète, le système raccourcit à l'affichage
|
||
- Affichage public (tfe.php) : extraction d'identifiant depuis l'URL
|
||
- Déplacement de contact_visible du Backoffice vers le fieldset Informations du TFE
|
||
- Renommage « Identité » → « Informations du TFE » dans le récapitulatif admin
|
||
|
||
---
|
||
021c58925efd | 2026-06-09 13:40
|
||
fix: auto-regenerate thesis identifier on any year-prefix mismatch, support .php migrations in runner
|
||
|
||
ThesisEditController::save() previously only regenerated the identifier when
|
||
the year field changed during an edit. If a thesis had its year corrected in
|
||
a past edit (or via other means) and the identifier still carried the old
|
||
year prefix, subsequent edits that didn't touch the year field would leave
|
||
the mismatched identifier in place.
|
||
|
||
Now saves() also checks whether the existing identifier's 4-digit prefix
|
||
matches the thesis year, and regenerates if not — regardless of whether year
|
||
changed in the current edit.
|
||
|
||
The migration runner (run.php) only scanned for .sql files, so PHP migrations
|
||
(013, 016, 018, 038) were never auto-applied. Extended the runner to also
|
||
discover and execute .php migrations in a subprocess. If a PHP migration fails
|
||
with an idempotent error (no such column, already exists, duplicate column),
|
||
the runner treats it as already-applied and continues rather than aborting
|
||
— preventing a stale migration like 016 (banner_path already dropped by 028)
|
||
from blocking migrations that come after it alphabetically (e.g. 038).
|
||
|
||
Updated migrations 016 and 038 to accept an optional $argv[1] DB path.
|
||
Fixed 016 to gracefully handle the banner_path column already being gone
|
||
(exit 0 instead of fatal).
|
||
|
||
---
|
||
07370b722176 | 2026-06-09 13:20
|
||
search: ajout filtres finalité et format, boutons plus compacts et Réinitialiser en neutre
|
||
|
||
---
|
||
34739d6ae584 | 2026-06-09 12:44
|
||
feat: migration 038 to fix thesis identifiers mismatched with their year
|
||
|
||
---
|
||
3df14567816d | 2026-06-09 12:33
|
||
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.
|
||
|
||
---
|
||
3016c199bdfa | 2026-06-08 18:31
|
||
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.
|
||
|
||
---
|
||
3d524226a1be | 2026-06-08 18:05
|
||
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)
|
||
|
||
---
|
||
c4664ec2e960 | 2026-06-08 17:48
|
||
fix: prevent mid-word break in repertoire column headers
|
||
|
||
Base.css applies word-break: break-word to all elements inside <main>,
|
||
causing mid-word breaks in narrow columns. Override in repertoire.css:
|
||
- hyphens: none, word-break: normal, overflow-wrap: normal on all h2
|
||
- redistribute grid fractions: shrink Orientations (1.2→0.9fr),
|
||
Étudiantes (1→0.8fr), boost Finalité (0.7→0.9fr, min 7rem)
|
||
|
||
---
|
||
a184e0d25311 | 2026-06-08 17:38
|
||
Ajouter l'affichage de la finalité du master sur la page publique TFE
|
||
|
||
---
|
||
a2092b58a75b | 2026-06-08 17:24
|
||
fix: supprimer les vidéos PeerTube lors de la suppression d'un TFE
|
||
|
||
- Ajout de PeerTubeService::deleteVideo() qui appelle DELETE /api/v1/videos/{uuid}
|
||
- deleteThesisFileToTrash() appelle maintenant deleteVideo() pour les fichiers peertube_ids:
|
||
- hardDeleteThesis() supprime aussi les vidéos PeerTube associées
|
||
|
||
---
|
||
312d9eab0e53 | 2026-06-08 12:47
|
||
À 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
|
||
|
||
---
|
||
a1a9a316ca65 | 2026-06-08 12:40
|
||
rework tfe.php layout: row1 author above title, row2 meta+synopsis 2-col grid, row3 flex files
|
||
|
||
---
|
||
e0d706c677de | 2026-06-08 12:31
|
||
tfe.css: tfe-meta-item font-weight 400→700
|
||
|
||
---
|
||
c9fa5943cf76 | 2026-06-08 12:20
|
||
repertoire: rep-entry + col h2 step-0, years col step-3
|
||
|
||
---
|
||
ef6bff895aec | 2026-06-08 12:16
|
||
admin nav-logo: grid layout for icon+text horizontal alignment and vertical centering
|
||
|
||
---
|
||
9e272873e1ac | 2026-06-08 12:10
|
||
style: set tfe-meta-item default to font-weight 400 so Accès/Licence values render at regular weight
|
||
|
||
---
|
||
3588f22d7ba2 | 2026-06-08 11:58
|
||
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
|
||
|
||
---
|
||
cb2b18e47072 | 2026-06-08 11:53
|
||
style: standardise links to Regular weight (400) with violet accent hover, body to Light (300)
|
||
|
||
---
|
||
cee3345ea3c8 | 2026-06-08 11:35
|
||
tfe.php: afficher CC2r + licence, formater contact court, supprimer download PDF
|
||
|
||
---
|
||
24b753a99297 | 2026-06-08 11:22
|
||
fix: add missing csrf_token to htmx checkbox in file access restrictions
|
||
|
||
The 'Activer la restriction d'accès' checkbox in /admin/acces.php used
|
||
htmx to POST to settings.php but the #fieldset-restrictions container
|
||
was missing a csrf_token hidden input. This caused two bugs:
|
||
1. 'Erreur de sécurité, token invalide' error
|
||
2. Full /admin/parametres.php HTML injected into #restrictions-response
|
||
(due to HTMX following the 302 redirect on CSRF failure)
|
||
|
||
---
|
||
3f200dae70fa | 2026-06-08 11:13
|
||
TFE page: replace dl/dt/dd with p/span for metadata, remove underlines, lowercase keywords/languages/formats, inclusive text, editable restriction messages
|
||
|
||
---
|
||
71949425c7eb | 2026-06-08 10:56
|
||
TFE page: remove underlines from all links, lowercase keywords/languages/formats, inclusive writing, prevent keyword mid-word breaks, editable restriction messages in admin
|
||
|
||
---
|
||
9a8f0cad6515 | 2026-06-08 10:50
|
||
fix(répertoire): colonnes différenciées, scrollbars discrètes, fontes conformes maquette, AP entre crochets
|
||
|
||
- grid-template-columns: années=0.4fr, orientations=1.2fr, AP/finalité/intermédiaires
|
||
- scrollbars: WebKit 5px transparent + Firefox scrollbar-width:thin global
|
||
- rep-entry: BBBDMSans Regular 398, --step--1
|
||
- années: BBBDMSans Medium 498 (semi-bold)
|
||
- titres colonnes: Ductus Regular 398, text-secondary, letter-spacing 0.12em
|
||
- AP: diminutifs entre crochets (ex: Design et Politique du Multiple [DPM])
|
||
- TODO: marquer les 4 correctifs répertoire comme faits
|
||
|
||
---
|
||
df70fba5d43a | 2026-06-08 10:28
|
||
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
|
||
|
||
---
|
||
fad38f4e0d35 | 2026-06-08 10:14
|
||
Added more logs to be excluded
|
||
|
||
---
|
||
053f09b1812f | 2026-06-08 09:48
|
||
fix(migration): deduplicate languages before LOWER() in 025_lowercase_languages.sql
|
||
|
||
Two rows (Néerlandais id=5, néerlandais id=3) collided when lowercased,
|
||
violating the UNIQUE constraint on languages.name.
|
||
|
||
Added DELETE to keep the lowest-ID row per LOWER(name) group before
|
||
the UPDATE SET name = LOWER(name).
|
||
|
||
---
|
||
f4cb06656ec9 | 2026-06-04 23:50
|
||
Improve .gitignore
|
||
|
||
---
|
||
2bb520bb8c36 | 2026-06-08 10:12
|
||
Fix: anchor vendor/ gitignore to root so app/public/assets/js/vendor/ is tracked (htmx, OverType, FilePond)
|
||
|
||
---
|
||
f398a0f1ff29 | 2026-05-31 17:49
|
||
Fix non-constant-time credential comparisons
|
||
|
||
- account.php: replace !== CSRF token check with hash_equals
|
||
- ShareLink::setPassword(): also encrypt and store plain-text password
|
||
alongside the hash, matching create() behavior so the decrypted_password
|
||
decoration stays correct after password updates
|
||
|
||
---
|
||
6246174fc570 | 2026-05-20 13:21
|
||
Export: add LINK.txt with PeerTube watch URLs to file ZIP export
|
||
|
||
---
|
||
a251aeb50096 | 2026-05-20 13:10
|
||
Fix: bootstrap.php autoload path detects vendor location (same dir vs parent dir) for flat server layout
|
||
|
||
---
|
||
d33a56981e89 | 2026-05-20 13:06
|
||
Fix: deploy-deps patches classmap path (app/src/ → src/) for flat server layout before composer install
|
||
|
||
---
|
||
47405e5334e0 | 2026-05-20 12:58
|
||
Fix: split deploy recipe into deploy-code/deploy-deps/deploy-migrate; deploy-deps always syncs composer.json+composer.lock, runs composer install only when lockfile checksum changed
|
||
|
||
---
|
||
4e409c409d4a | 2026-05-20 12:44
|
||
Fix: add ZipArchive guard to export-files.php, add composer install step + composer.json sync to deploy recipe
|
||
|
||
---
|
||
ae66c2baad71 | 2026-05-20 02:16
|
||
Integrate Monolog: replace four logging systems with single PSR-3 factory
|
||
|
||
- Add monolog/monolog dependency (^3.10)
|
||
- Create app/Logger.php central factory with channels: app, admin, error, audit
|
||
- Each channel gets RotatingFileHandler (30-day retention) with pass-through LineFormatter
|
||
preserving existing JSON format contracts
|
||
- Rewrite AppLogger as thin facade delegating to Logger::get('app')
|
||
- Rewrite ErrorHandler::log() to delegate to Logger::get('error')
|
||
- Rewrite AdminLogger file output to delegate to Logger::get('admin'), keep DB writes
|
||
- Add Monolog file shadow to Audit via Logger::get('audit') (Option A per monolog-plan)
|
||
- Log level controlled by LOG_LEVEL env var (defaults: DEBUG in cli-server, WARNING otherwise)
|
||
- Graceful NullHandler fallback when log directory is not writable
|
||
- Update SystemController LOG_FILES: remove php_error, add app/admin/error/audit
|
||
- JSON app logs parsed to readable one-liners in the log viewer
|
||
- Remove nginx config tab (parametres + fragment + template + css)
|
||
- Friendly empty-state message when app log files don't exist yet (notYet)
|
||
- PHP tail fallback when exec() unavailable
|
||
- All 228 PHPUnit tests pass, no call sites changed
|
||
|
||
---
|
||
a6e0aa5887c7 | 2026-05-20 02:03
|
||
Add code coverage configuration (phpunit.xml source filter), baseline coverage report (21.27% lines), gitignore coverage/ and .phpunit.result.cache; remove deprecated setAccessible() calls
|
||
|
||
---
|
||
a047062d8714 | 2026-05-20 01:55
|
||
Phase 4 cleanup: migrate old tests to PHPUnit, add ErrorHandler/PureLogic/SearchController tests, remove app/tests/, update justfile test target
|
||
|
||
---
|
||
93625d09b58b | 2026-05-20 01:51
|
||
Add integration tests (Phase 2: DatabaseExtended, ShareLinkExtended, RateLimitExtended) and controller validation tests (Phase 3: ThesisCreate, ThesisEdit, AutofocusField)
|
||
|
||
---
|
||
7a4d0fafb285 | 2026-05-20 01:28
|
||
Add PHPUnit setup (Phase 0) and pure-logic tests (Phase 1): Crypto, EmailObfuscator, SystemController helpers, StudentEmail, TfeController OG tags
|
||
|
||
---
|
||
d9e454174916 | 2026-05-20 01:21
|
||
Remove unused Parsedown.php (replaced by league/commonmark in Phase 1); update phpstan baseline
|
||
|
||
---
|
||
a0cda5b55d2c | 2026-05-20 01:17
|
||
Phase 3: Replace SmtpRelay SMTP socket with PHPMailer
|
||
|
||
---
|
||
ba578200169d | 2026-05-20 01:07
|
||
Phase 2: Replace PeerTubeService HTTP client with Guzzle
|
||
|
||
---
|
||
5e75cacad7a9 | 2026-05-20 01:02
|
||
Phase 1: Replace Parsedown with league/commonmark (4 call sites)
|
||
|
||
---
|
||
4683ba4116b1 | 2026-05-20 00:53
|
||
Add composer.json with league/commonmark, guzzlehttp/guzzle, phpmailer/phpmailer; wire autoloader into bootstrap; document de-librairisation strategy and PHP extension setup
|
||
|
||
---
|
||
728f05502c96 | 2026-05-19 23:59
|
||
Combine phpstan, cs-check, cs-fix into lint-php recipe; fix lint issues + test failures + duplicate detection bug
|
||
|
||
---
|
||
2e75a3b35ca0 | 2026-05-19 22:38
|
||
Fix beforeunload dialog appearing on edit.php when no changes made
|
||
|
||
---
|
||
42222abe7c25 | 2026-05-19 22:32
|
||
Récapitulatif admin: sections → fieldsets, fichiers en table, marges + pas de thumbnails
|
||
|
||
---
|
||
defc919cd01f | 2026-05-19 22:00
|
||
cleanup modal: list stale files to remove; storage restructure: documents/ → {objet}/
|
||
|
||
---
|
||
c6199525f94b | 2026-05-19 21:00
|
||
add sticky thead to index, langues, and mots-clés tables
|
||
|
||
---
|
||
bcf3140aa2e5 | 2026-05-19 20:53
|
||
edit submission: redirect to recapitulatif instead of edit
|
||
|
||
---
|
||
4da317de0a75 | 2026-05-19 19:39
|
||
deploy: remove .env from generic file perm check, remove router.php check (dev-only file)
|
||
|
||
---
|
||
5bbf633295d7 | 2026-05-19 19:26
|
||
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
|
||
|
||
---
|
||
678f9fc8044d | 2026-05-19 19:05
|
||
Index page: remove Mots-clés button, move export to bulk selection, fix ZipArchive error, move DB export to paramètres, sticky thead
|
||
|
||
- Remove 'Mots-clés' button from toolbar (redundant with admin sidebar tags)
|
||
- Replace export dialog with 'Exporter CSV' + 'Exporter fichiers' buttons in bulk selection bar
|
||
- Export dispatcher now accepts ?ids=1,2,3 for per-selection export
|
||
- All ExportController/Database methods accept optional thesisIds array
|
||
- Graceful error message when ZipArchive extension is missing on server
|
||
- Move DB export (SQLite download) to paramètres → Maintenance section
|
||
- Sticky table column headers (position: sticky, top: 0, z-index: 5) for index page table
|
||
|
||
---
|
||
b48494312891 | 2026-05-19 19:00
|
||
Unnest header.css (native CSS nesting silently broken in browsers without support)
|
||
|
||
---
|
||
2cb8d71fe9a5 | 2026-05-19 18:08
|
||
Fix dialog margins, add admin-dialog__body/styles, give trash page horizontal margins
|
||
|
||
---
|
||
d619d2f11673 | 2026-05-19 16:56
|
||
Update TODO after CSS refactoring fixes
|
||
|
||
---
|
||
7cf020c7bd82 | 2026-05-19 14:55
|
||
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).
|
||
|
||
---
|
||
7c30d1c55d4d | 2026-05-19 01:13
|
||
Fix relink: close modal + HTMX refresh for immediate pool update
|
||
|
||
- After relink, always close the modal (even if FilePond input not found,
|
||
e.g. page refreshed by live-reload during the fetch).
|
||
- After closing, re-fetch #format-fichiers-block via HTMX from
|
||
/admin/fragments/fichiers.php?_thesis_id=N which loads thesis files
|
||
from DB and re-renders the fragment with pre-populated FilePond pools.
|
||
The afterSwap handler auto-reinitializes FilePond instances.
|
||
- Updated admin/fragments/fichiers.php to accept _thesis_id, load
|
||
existing files from DB, build per-queue-type JSON, and render in
|
||
edit mode.
|
||
|
||
---
|
||
b77bc486e591 | 2026-05-19 00:41
|
||
Fix relink: FilePond addFile API, yellow border, limbo type + await
|
||
|
||
- Fix addFile argument format: FilePond.addFile() takes (source, options)
|
||
as two separate arguments, not a single {source, options} object.
|
||
- Change .filepond--file default border from accent-yellow to accent-green.
|
||
Existing files loaded in edit mode have type 'local' and never reach
|
||
processing-complete state, so they got the yellow border.
|
||
- Change relinked file add from type 'local' to 'limbo'. Limbo items
|
||
go through DID_COMPLETE_ITEM_PROCESSING which triggers onprocessfile
|
||
(ensures syncOrderInput runs with serverId available) and renders
|
||
the green checkmark visual.
|
||
- Await addFile Promise and close modal in .then() instead of
|
||
immediately, ensuring the item is created before cleanup.
|
||
- Remove duplicate modal.close() after the addFile block.
|
||
|
||
---
|
||
ae9a8a62c0f2 | 2026-05-19 00:26
|
||
deploy: exclude storage/{tmp,documents,theses,triage,backups,logs} from rsync + fix .env perm check to 640
|
||
|
||
---
|
||
41eebf8a02f2 | 2026-05-19 00:06
|
||
cleanup: squash commit history from 177 to 98 commits, resolve acces.php conflict markers
|
||
|
||
---
|
||
27e6abc7e491 | 2026-05-18 17:39
|
||
feat: file browser + relink for orphaned files + htmx fix + header cleanup + fix relinked FilePond integration + resolve acces.php conflict markers
|
||
|
||
---
|
||
79eddf5d5a02 | 2026-05-13 14:58
|
||
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
|
||
|
||
---
|
||
6f7a02244f3e | 2026-05-12 15:19
|
||
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.
|
||
|
||
---
|
||
da153fc60473 | 2026-05-12 15:01
|
||
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
|
||
|
||
---
|
||
2632730fa07c | 2026-05-12 14:36
|
||
.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.
|
||
|
||
---
|
||
2f4ac22bcb55 | 2026-05-12 14:36
|
||
Fix Interdit Info text
|
||
|
||
---
|
||
fdc301c20d13 | 2026-05-12 14:28
|
||
fix: require Crypto.php in ShareLink.php
|
||
|
||
---
|
||
9152b120e8a1 | 2026-05-12 13:50
|
||
feat: mandatory auto-generated passwords for share links + admin password copy/regeneration + password gate rate limiting
|
||
|
||
---
|
||
8bb0b3a1f2db | 2026-05-12 12:27
|
||
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
|
||
|
||
---
|
||
6e7c0c00e37b | 2026-05-12 12:08
|
||
refactor: merge video/audio FilePond pools into TFE input
|
||
|
||
- Remove separate video/audio/peertube_video/peertube_audio pools from UI
|
||
- TFE pool now accepts all file types including video/audio
|
||
- When PeerTube is enabled, video/audio dropped into TFE pool auto-upload
|
||
to PeerTube (process.php detects MIME and uploads immediately)
|
||
- PeerTube return IDs now encode type: peertube:video:UUID or peertube:audio:UUID
|
||
- load.php returns placeholder SVG for PeerTube files so they appear in FilePond
|
||
- Edit mode: all existing files (including PeerTube) shown in TFE FilePond pool
|
||
- Remove legacy video/audio/peertube_* handling from both controllers
|
||
- Remove unused vide/audio/peertube_* entries from JS QUEUE_CONFIG
|
||
|
||
---
|
||
1ff3c70ebe03 | 2026-05-12 10:35
|
||
fix: track vendor JS files, add 'unsafe-inline' to public CSP, gitignore filepond tmp
|
||
|
||
- Track vendor JS files (filepond, htmx, overtype) that were moved
|
||
to app/public/assets/js/vendor/ but never tracked → missing from deploys
|
||
- Add script-src 'self' 'unsafe-inline' to main CSP header so public
|
||
pages (jury fieldset, repertoire, partage) can use inline scripts
|
||
and onclick handlers
|
||
- Add storage/tmp/filepond/* to .gitignore with .gitkeep, and exclude
|
||
from deploy rsync to avoid syncing local test uploads to production
|
||
|
||
---
|
||
2e9ebfc684c4 | 2026-05-11 20:11
|
||
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
|
||
|
||
---
|
||
b56d07321030 | 2026-05-11 19:37
|
||
refactor: extract inline JS into app/ modules, remove dead overtype-webcomponent
|
||
|
||
- Remove overtype-webcomponent.min.js (zero references)
|
||
- Extract copyLogContent + fallbackCopy + HTMX tab-updater → app/admin-logs.js
|
||
(removes duplicate from both system.php and parametres.php)
|
||
- Extract copyUrl → app/clipboard.js (shared by acces.php)
|
||
- Extract tag/language pill-search logic → app/pill-search.js
|
||
Generalized with data-pill-search attributes, auto-inits via
|
||
DOMContentLoaded + htmx:afterSwap
|
||
- Extract access-request form handler → app/access-request.js
|
||
(was inline in templates/public/tfe.php)
|
||
|
||
Files created: admin-logs.js, clipboard.js, pill-search.js, access-request.js
|
||
Files modified: 9 templates/controllers to drop inline scripts and
|
||
reference external JS files
|
||
|
||
---
|
||
04094d802db6 | 2026-05-11 18:03
|
||
fix: harden security based on pentest scan findings
|
||
|
||
- Add Content-Security-Policy to main nginx server block (was only on /admin/)
|
||
- Add Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy headers
|
||
- Add includeSubDomains to HSTS header
|
||
- Set HttpOnly, Secure, SameSite=Lax session cookie params on public pages
|
||
(AdminAuth already hardens the /admin session with SameSite=Strict)
|
||
- Update xamxam.conf.reference and SECURITY_HEADERS.md to match
|
||
|
||
---
|
||
4717b4d67efa | 2026-05-11 17:56
|
||
feat: add passive pentest scanner script with PEP 723 uv metadata
|
||
|
||
---
|
||
df12af842344 | 2026-05-11 17:03
|
||
fix: validation error messages hidden by generic fallback in ErrorHandler::userMessage
|
||
|
||
ErrorHandler::userMessage only handled RuntimeException, but all validation
|
||
throws in ThesisCreateController and ThesisEditController use plain Exception.
|
||
This caused user-friendly messages like 'Le champ Nom/Prénom/Pseudo est requis'
|
||
to fall through to the 'Une erreur inattendue est survenue…' generic message.
|
||
|
||
Fix: add Exception check (after PDOException, since PDOException extends it)
|
||
so all validation exceptions pass their message through.
|
||
|
||
---
|
||
c3f6e8a03362 | 2026-05-11 16:55
|
||
fix: upload progress bar not visible — collectFileNames checks FilePond instances; remove admin auth from progress poll endpoint
|
||
|
||
---
|
||
d873a7f09e6d | 2026-05-11 16:22
|
||
fix: add upload-progress.js to partage form (progress bar was missing on public submissions)
|
||
|
||
---
|
||
e5e76cfb70c1 | 2026-05-11 16:07
|
||
add migration 033 to deduplicate format_types, fix 019 to use INSERT OR IGNORE
|
||
|
||
---
|
||
3ae22cd42737 | 2026-05-11 16:03
|
||
add system dependency checks (php-curl, sqlite3) to deploy-server.sh step 0
|
||
|
||
---
|
||
31ccbd195b48 | 2026-05-11 16:01
|
||
add syntaqlite SQL validation to migrate.sh before applying schema.sql
|
||
|
||
---
|
||
cb6394e119cd | 2026-05-11 15:54
|
||
add incremental migration runner to deploy recipe — execute whole SQL files (not semicolon-split), catch 'no such column' for idempotent re-runs, merge into migrate.sh
|
||
|
||
---
|
||
c1960d224b23 | 2026-05-11 15:23
|
||
fix deploy: multiple deploy recipe fixes — upload xamxam.conf before deploy-server.sh, sudo rsync for chown, migrate.sh via sqlite3, chmod WAL/SHM sidecar files, deploy-verify-permissions awk fix, .env sudo perms
|
||
|
||
---
|
||
206a6427e70e | 2026-05-11 14:53
|
||
style: unify recap+edit file figure styling — two-column grid dl, vertical spacing, admin-back-btn sizing, standardise file display and delete-to-trash SVG icons
|
||
|
||
---
|
||
230555a4c4c2 | 2026-05-11 14:45
|
||
exhaustive recap: all fields, figures for files, PeerTube ID display, same in edit form
|
||
|
||
---
|
||
8bf95f4e144a | 2026-05-11 14:19
|
||
feat: refactor licence fragment — Libre→CC2r+licence, Interne→opt-in want_license, Interdit→none, add details/summary to license UI
|
||
|
||
---
|
||
927ee2fe2acc | 2026-05-11 13:12
|
||
feat: upload progress bar — fieldset layout, accent colors, file name display, completion animation, 800ms redirect delay; decorelate formats from fichiers; server-side poll via token; bump PeerTube embed audio player
|
||
|
||
---
|
||
cdec3e96a679 | 2026-05-11 12:09
|
||
fix PeerTube upload: final working solution — simple multipart POST with CURLFile; iterated through Google-resumable PATCH protocol debugging (HTTP version negotiation, chunk body encoding, off-by-one fixes) before settling on simpler POST approach
|
||
|
||
---
|
||
1b0451581d53 | 2026-05-11 11:40
|
||
refactor: move Restrictions d'accès aux fichiers from contenus.php to acces.php, cleanup section
|
||
|
||
---
|
||
d000f9e1d485 | 2026-05-11 11:35
|
||
cleanup: merge SMTP fields into single fieldset, rename to Emails
|
||
|
||
- Renamed section from 'Relay SMTP' to 'Emails'
|
||
- Merged 'Expéditeur par défaut' fieldset into the main SMTP grid
|
||
- Removed separate 'from_email' field: now uses username as from_email
|
||
- Changed username label from 'Nom d\'utilisateur' to 'Adresse e-mail'
|
||
with placeholder xamxam@erg.be and type=email
|
||
- from_name and notify_email now inline in the main grid after password
|
||
|
||
---
|
||
83a5a508ea5e | 2026-05-11 10:47
|
||
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
|
||
|
||
---
|
||
28ef35dce5ad | 2026-05-11 10:31
|
||
fix: make schema.sql fully idempotent — add IF NOT EXISTS to all CREATE INDEX, CREATE TRIGGER, and CREATE VIEW statements
|
||
|
||
---
|
||
973444bdbb9d | 2026-05-11 03:51
|
||
feat(backup): deploy cron-based SQLite backups to production
|
||
|
||
- Create deploy/xamxam-backup.cron with hourly (30d) and daily (90d) jobs
|
||
- Add just recipes for deploying backup infrastructure:
|
||
- deploy-backup-script: upload backup-sqlite.sh to /usr/local/bin
|
||
- deploy-backup-cron: install cron.d file, create /var/backups/xamxam + log
|
||
- deploy-backup: one-shot convenience (script + cron)
|
||
- deploy-check-backup-log: tail the backup log
|
||
- deploy-list-backups: ls remote backup directory
|
||
- trigger-backup: manually invoke backup on server
|
||
- test-restore: scp, gunzip, verify a remote snapshot
|
||
- Add reminder to run deploy-backup after first deploy
|
||
- Replace 'Contenu (Markdown)' label with 'Syntax Markdown' link (cheatsheet)
|
||
|
||
---
|
||
be50ac5eb09d | 2026-05-11 03:18
|
||
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
|
||
|
||
---
|
||
43064ccbd7e2 | 2026-05-11 03:11
|
||
feat(admin): add htmx toast feedback for settings checkboxes in contenus.php
|
||
|
||
- Replace hx-swap="none" with hx-target on response divs inside each of the
|
||
three fieldsets (Restrictions d'accès, Degré d'ouverture, Types de travaux)
|
||
- Add hxToastSuccess / hxToastError helpers in settings.php that return HTML
|
||
toast fragments with self-referencing auto-dismiss after 3s
|
||
- Each response div has aria-live="polite" for accessibility
|
||
- Add comprehensive PHP/JS debugging logs:
|
||
- settings.php logs raw POST values per field before resolving to 0/1
|
||
- checkboxes have hx-on::before-request and hx-on::after-request console.log
|
||
- global htmx:beforeSend and htmx:sendError listeners in admin footer
|
||
- toast lifecycle logged (creation + removal) for traceability
|
||
- Fix toast auto-remove: use getElementById with random unique ID instead
|
||
of querySelector which could remove wrong toast on rapid clicks
|
||
- Follows the Django+HTMX ajax checkbox pattern from the reference tutorial
|
||
|
||
feat(admin): add htmx toast feedback for settings checkboxes in contenus.php
|
||
|
||
- Replace hx-swap="none" with hx-target on response divs inside each of the
|
||
three fieldsets (Restrictions d'accès, Degré d'ouverture, Types de travaux)
|
||
- Add hxToastSuccess / hxToastError helpers in settings.php that return HTML
|
||
toast fragments with self-referencing auto-dismiss after 3s
|
||
- Each response div has aria-live="polite" for accessibility
|
||
- Add comprehensive PHP/JS debugging logs:
|
||
- settings.php logs raw POST values per field before resolving to 0/1
|
||
- checkboxes have hx-on::before-request and hx-on::after-request console.log
|
||
- global htmx:beforeSend and htmx:sendError listeners in admin footer
|
||
- toast lifecycle logged (creation + removal) for traceability
|
||
- Fix toast auto-remove: use getElementById with random unique ID instead
|
||
of querySelector which could remove wrong toast on rapid clicks
|
||
- Fix checkbox unresponsive after toggles: move hidden value="0" inputs
|
||
outside <label> to prevent HTML label double-activation
|
||
- Follows the Django+HTMX ajax checkbox pattern from the reference tutorial
|
||
|
||
feat(admin): add htmx toast feedback for settings checkboxes in contenus.php
|
||
|
||
- Replace hx-swap="none" with hx-target on response divs inside each of the
|
||
three fieldsets (Restrictions d'accès, Degré d'ouverture, Types de travaux)
|
||
- Add hxToastSuccess / hxToastError helpers in settings.php that return HTML
|
||
toast fragments with self-referencing auto-dismiss after 3s
|
||
- Each response div has aria-live="polite" for accessibility
|
||
- Add comprehensive PHP/JS debugging logs:
|
||
- settings.php logs raw POST values per field before resolving to 0/1
|
||
- checkboxes have hx-on::before-request and hx-on::after-request console.log
|
||
- global htmx:beforeSend and htmx:sendError listeners in admin footer
|
||
- toast lifecycle logged (creation + removal) for traceability
|
||
- Fix toast auto-remove: use getElementById with random unique ID instead
|
||
of querySelector which could remove wrong toast on rapid clicks
|
||
- Fix checkbox unresponsive after toggles: remove hidden value="0" inputs entirely; unchecked checkboxes are simply absent from POST and server treats missing key as 0
|
||
outside <label> to prevent HTML label double-activation
|
||
- Follows the Django+HTMX ajax checkbox pattern from the reference tutorial
|
||
|
||
---
|
||
72f7192156ec | 2026-05-11 02:28
|
||
feat(deploy): add deploy-verify-permissions recipe + upload/run deploy-server.sh before verification + run migrations in deploy
|
||
|
||
---
|
||
3136fa7113c3 | 2026-05-11 01:45
|
||
fix: settings checkboxes — fix unchecked state handling, split into separate forms to avoid cross-resets, use HTMX auto-save with hidden value=0 inputs
|
||
|
||
---
|
||
926659087fbd | 2026-05-11 01:08
|
||
feat: implement SQLite backup & data integrity plan (Phases 2-4)
|
||
|
||
---
|
||
c0163ca4d53e | 2026-05-11 00:43
|
||
fix: exclude entire var/ from rsync --delete to preserve logs
|
||
|
||
---
|
||
7e987d281c6c | 2026-05-11 00:34
|
||
fix: add hx-swap="none" to admin auto-save checkboxes to prevent page swap
|
||
|
||
---
|
||
cf9bd5cd5d5f | 2026-05-10 23:49
|
||
feat: require 3 mots-clés in partage, language asterisk toggle, admin auto-save checkboxes
|
||
|
||
- tag-search: add minTags/required params, counter shows red if < 3, accent if ≥ 3
|
||
- form.php: pass minTags=3 for partage mode keywords
|
||
- checkbox-list: support labelHtml for raw HTML label with targetable asterisk span
|
||
- language-autre-fragment: OOB swap updates #languages-required-asterisk when autre pills change
|
||
- language-search: client-side update #languages-required-asterisk on pill add/remove
|
||
- contenus.php: replace 3 form+submit-button fieldsets with HTMX auto-save checkboxes
|
||
- settings.php: detect HX-Request header, return OOB CSRF token updates, skip redirect
|
||
|
||
---
|
||
48da914bc8e9 | 2026-05-10 23:40
|
||
fix: obfuscate email in contact links, raise rate limits, make Libre toggleable
|
||
|
||
- about.php: use EmailObfuscator::email() for contact email link text instead of htmlspecialchars
|
||
- SearchController: raise rate limit from 30 to 300 req/min
|
||
- request-access.php: raise rate limit from 3 to 30 req/10min
|
||
- partage/index.php: raise rate limit from 5 to 50 req/10min
|
||
- contenus.php: make Libre option toggleable (remove disabled class), move to top of Degré d'ouverture, remove temporary note about next academic year
|
||
|
||
---
|
||
2bacc7849287 | 2026-05-10 23:22
|
||
import dialog: add Terminé button, fix padding, make success permanent, avoid POST resend
|
||
|
||
import dialog: add Terminé button, fix padding, make success permanent, avoid POST resend
|
||
|
||
---
|
||
cab65ea4a43e | 2026-05-10 23:06
|
||
fix: jury-fieldset.php calling old() with wrong signature for partage
|
||
|
||
jury-fieldset.php called old('jury_promoteur') as a global function,
|
||
but the partage context defines old(array $data, string $key) —
|
||
passing a string where array is expected caused a TypeError.
|
||
|
||
Changed jury-fieldset.php to use $oldFn callable (like fieldset-tfe-info.php),
|
||
with fallback to global old() when not provided. The add-mode repopulation
|
||
block no longer calls the global old() directly.
|
||
|
||
---
|
||
9bcfaf5fd57f | 2026-05-10 22:40
|
||
Make Auteur(s) and Accès columns sortable alphabetically in admin list
|
||
|
||
---
|
||
406752bc6f98 | 2026-05-10 22:36
|
||
Improve recap page + fix CSV import for jury roles
|
||
|
||
recapitulatif.php (partage):
|
||
- Center .thanks-success and add bottom margin/padding
|
||
- Display ALL fields: identifier, synopsis, languages, formats,
|
||
jury (all roles), baiu link, license, access type
|
||
- Add validation notice asking user to verify info, with
|
||
xamxam@erg.be contact link (email obfuscated)
|
||
|
||
StudentEmail:
|
||
- Add 'Note contextuelle' and license_custom to email recap
|
||
- Rename 'Promoteur·ice(s)' to 'Promoteur·ice(s) interne'
|
||
- Change email message to ask student to verify info + contact
|
||
for errors
|
||
|
||
CSV export/import:
|
||
- Add 3 new CSV columns: Lecteur·ice(s) interne,
|
||
Lecteur·ice(s) externe, Promoteur·ice(s) ULB
|
||
- Export splits supervisors by role/is_external/is_ulb into
|
||
separate columns
|
||
- Import inserts supervisors with correct role, is_external,
|
||
and is_ulb flags (was: all treated as generic supervisors)
|
||
- Add header matching for short distinguishers (ulb, externe)
|
||
via str_contains fallback
|
||
|
||
---
|
||
8545daaccc6c | 2026-05-10 22:18
|
||
fix migration 028, promoteurice repopulation, DB bootstrap
|
||
|
||
- Fix undefined $isExternalUrl in tfe.php (moved after assignment)
|
||
- Disable PeerTube rendering in tfe.php entirely
|
||
- Migration 028: drop banner_path from theses with proper view handling
|
||
- Drop dependent views before column, recreate without banner_path
|
||
- Remove broken 027_drop_banner_path.sql
|
||
- Move 025_fix_oui_non_artefacts.sql and 021_peertube_settings.sql to applied/
|
||
- Add scripts/ensure-db.php to init fresh DB from schema.sql when missing
|
||
- Update deploy justfile to run ensure-db.php before migrations
|
||
- Fix promoteurice array repopulation in partage form:
|
||
- Fix old() to return raw arrays (not json_encode) so callers can iterate
|
||
- Handle jury_promoteur[] and jury_promoteur_ulb_name[] arrays properly
|
||
|
||
---
|
||
8db7b6e9eb92 | 2026-05-10 20:41
|
||
feat: FilePond production hardening — extension-based validation, server-side size limits (2GB), annexe validation, drop accept attributes, FilePond file styling
|
||
|
||
---
|
||
7b5f3efe4095 | 2026-05-10 20:33
|
||
feat: add FilePond pools for couverture, note_intention, video, audio; refactor queue config
|
||
|
||
---
|
||
ecb559a18af2 | 2026-05-10 20:28
|
||
refactor: decouple format extras from main fichiers block, scope FilePond destroy to individual slots, fix FilePond integration for decoupled extras
|
||
|
||
---
|
||
1aff5ff46dee | 2026-05-10 20:10
|
||
Replace custom file-upload-queue.js with FilePond
|
||
|
||
- Delete file-upload-queue.js (495 lines of custom queue logic)
|
||
- Delete sortable.min.js dependency
|
||
- Add file-upload-filepond.js: thin wrapper that upgrades .tfe-file-picker
|
||
inputs to FilePond instances with storeAsFile:true for native multipart
|
||
form submission (no form-submit interception needed)
|
||
- Update fichiers-fragment.php: replace queue container <ul> elements
|
||
and empty-state <p> with bare <input> elements that FilePond upgrades;
|
||
change name attributes to queue_file[tfe][] etc. for PHP compatibility
|
||
- Update add.php, edit.php, partage/index.php: swap JS/CSS refs
|
||
- Clean up form.css: remove .fq-* and .tfe-file-queue custom styles,
|
||
add FilePond theme overrides matching xamxam design tokens
|
||
- Update dead-code fieldset-files.php for consistency
|
||
|
||
Server-side stays unchanged: PHP receives ['queue_file']['tfe'][]
|
||
exactly as before through native multipart submission.
|
||
|
||
---
|
||
223a15b397ef | 2026-05-10 20:10
|
||
chore: resolve rebase conflict markers in acces.php (no functional changes)
|
||
|
||
---
|
||
11e6eed980d6 | 2026-05-10 19:53
|
||
fix: stop checkbox click in admin index from navigating to recapitulatif
|
||
|
||
---
|
||
f28a20d642ca | 2026-05-10 19:23
|
||
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.
|
||
|
||
---
|
||
d5fee1acfbdb | 2026-05-10 18:08
|
||
fix: repair form submission with queued files + add comprehensive debug logging
|
||
|
||
- Replace fetch(redirect:manual) with XMLHttpRequest in file-upload-queue.js.
|
||
The previous fetch-based redirect detection was broken because opaque
|
||
redirects hide the Location header. XHR's responseURL reliably exposes
|
||
the final URL after server-side redirects.
|
||
|
||
- Add console.log tracing at every decision point in submit interception:
|
||
entry, hasFiles check, enctype check, double-submit guard, XHR status,
|
||
redirect detection, error fallback.
|
||
|
||
- Add error_log entry-point logging to all 16 admin action files plus
|
||
the partage/index.php submission handler and password gate. Each logs:
|
||
request method, content type/length, POST keys, file counts, and
|
||
queue-specific file counts where applicable.
|
||
|
||
- Add double-submit guard (_xamxamActiveSubmit) to prevent duplicate
|
||
XHR sends when the native submit handler fires after interception.
|
||
|
||
---
|
||
13d26ded66e4 | 2026-05-10 17:16
|
||
Replace HTMX+PHP file upload queues with client-side JS
|
||
|
||
Drops the session-backed HTMX incremental upload system in favour of a
|
||
single JS module that manages `File` objects client-side and injects
|
||
them into `FormData` on submit.
|
||
|
||
Key changes:
|
||
|
||
* `file-upload-queue.js`: client-side queues with validation, reorder
|
||
(SortableJS), removal, dirty-state tracking, and fetch-based submit
|
||
with manual redirect handling
|
||
* `fichiers-fragment.php`: empty queue containers for JS-managed queues;
|
||
HTMX format switching still works with queue rehydration after swap;
|
||
annexe uploads now support multiple files
|
||
* Form UI cleanup: moved existing files and cover preview into the
|
||
`Fichiers` fieldset (edit mode); removed redundant queue labels while
|
||
keeping labels for single-file inputs (`couverture`,
|
||
`note d'intention`); added delete buttons for existing files
|
||
* `ThesisFileHandler.php`: added
|
||
`handleTfeQueueFiles()`/`handleAnnexeQueueFiles()` reading from
|
||
`$_FILES['queue_file']`; introduced `extractFilesSubArray()` for
|
||
nested upload arrays; removed session-based queue handling
|
||
* `ThesisCreateController.php` &
|
||
`ThesisEditController.php`: switched to extracted
|
||
`['queue_file']` uploads
|
||
* `beforeunload-guard.js`: now also watches
|
||
`window.__xamxamDirty`
|
||
* Deleted obsolete PHP upload/remove/reorder queue endpoints for
|
||
`partage` and `admin`
|
||
* Cleaned up route dispatch in `partage/index.php`
|
||
* Misc form and styling updates in templates/CSS
|
||
* Added `docs/cms-migration-plan.html`
|
||
|
||
---
|
||
98ed83fac237 | 2026-05-10 16:32
|
||
fix: scoped HTMX file validation, add validation to TFE/PeerTube inputs
|
||
|
||
- Wrap file-field.php validation in <form> to scope hx-include (fixes
|
||
cross-field contamination where cover change triggered note_intention
|
||
validation)
|
||
- Add inline MIME/size validation to upload-tfe-file.php
|
||
- Add inline validation to PeerTube video/audio and direct video/audio
|
||
file inputs in format-extras-block
|
||
- Fallback in validate-file-fragment-shared.php: if field_name doesn't
|
||
match any $_FILES key, try the first uploaded file (handles
|
||
PeerTube inputs where name differs from field_name)
|
||
- Fix file-field.php admin_mode using $adminMode variable instead of
|
||
undefined ADMIN_MODE constant
|
||
|
||
---
|
||
ca7707cd4733 | 2026-05-10 16:19
|
||
refactor: session-based incremental TFE upload via HTMX, drop SortableJS
|
||
|
||
Replace the client-side FileArray + Sortable drag-to-reorder with a
|
||
server-side session-based upload flow:
|
||
|
||
- New endpoints: /partage/upload-tfe-file, /partage/remove-tfe-file
|
||
(and /admin/ variants) — single-file incremental upload via HTMX
|
||
multipart/form-data with progress bar support
|
||
- Session storage: uploaded files go to STORAGE_ROOT/uploads/{session_id}/
|
||
with metadata in $_SESSION['tfe_uploads']
|
||
- file-upload-queue.js reduced to single-file previews only (couverture,
|
||
note_intention, annexes thumbnails)
|
||
- ThesisFileHandler gains handleTfeFilesFromSession + writeTfeFileFromSrc
|
||
+ cleanupSessionUploads for final commit from session temp
|
||
- Sortable.min.js removed from all script tags; drag handles and ghost
|
||
CSS removed
|
||
- No file_orders[]/file_labels[] hidden field injection needed
|
||
- Upload queue survives page refresh (server-owned list)
|
||
|
||
This eliminates the SortableJS dependency entirely while keeping the
|
||
same UX: pick files, see them in a queue, remove individual files.
|
||
|
||
---
|
||
e06a31749937 | 2026-05-10 15:55
|
||
fix: req annexes, add HTMX inline file validation (MIME/size)
|
||
|
||
- Annexes file input now required when 'has_annexes' checkbox is checked
|
||
- PHP-side validation: if has_annexes but no files, throw error
|
||
- HTMX inline file validation: POSTs to validate-file-fragment on file change
|
||
- Validates MIME type against per-field whitelists (couverture, note_intention,
|
||
tfe, annexes)
|
||
- Validates file size with PDF-specific 100MB limit
|
||
- Supports both single-file and multi-file inputs
|
||
- Returns green ✓ or red ✕ inline validation messages
|
||
- Shared validation logic in src/Controllers/validate-file-fragment-shared.php
|
||
- Admin wrapper: admin/validate-file-fragment.php (with AdminAuth guard)
|
||
- Partage route: /partage/validate-file-fragment (dispatched via index.php)
|
||
- CSS: .file-validation-msg, .fv-ok (green), .fv-error (red)
|
||
- file-field.php: accepts $fieldName for per-input validation type,
|
||
auto-detects admin/partage validate URL
|
||
|
||
---
|
||
a1a5d4609ff2 | 2026-05-10 15:14
|
||
fix: TFE and annexes files not saved, plus keyword validation and file preview CSS
|
||
|
||
- ThesisCreateController::submit() was missing call to handleAnnexeFiles
|
||
- ThesisEditController::save() was missing annexe upload handling
|
||
- handleAnnexeFiles now applies ALLOWED_MIME_TYPES/ALLOWED_EXTENSIONS validation
|
||
(same restrictions as TFE files, formerly only size was checked)
|
||
- Use correct $_FILES key 'annexes' (matching the form input name)
|
||
- Relax keyword minimum: admin create/edit require 1+, student (partage) requires 3
|
||
- Add CSS styles for file preview items (.fp-item, .fp-thumb, .fp-icon,
|
||
.fp-meta, .fp-name, .fp-size) so multi-file previews (annexes, etc.) wrap correctly
|
||
- Fix TFE file input accept attribute in fichiers-fragment.php to include
|
||
video/audio/archive extensions
|
||
|
||
---
|
||
38dc8de9d8dc | 2026-05-10 14:51
|
||
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. foo@...)
|
||
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).
|
||
|
||
---
|
||
ab6e26680779 | 2026-05-10 14:06
|
||
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.
|
||
|
||
---
|
||
6224e3ede0a8 | 2026-05-10 13:20
|
||
Fix language-search fragment
|
||
|
||
- mots-clé and language where sharing the same q variable for the input value; they now have unique variables.
|
||
|
||
The admin language-search-fragment was missing App::boot() which the tag-search
|
||
fragment had. This caused the language suggestion dropdown to not return results
|
||
in Firefox. Both fragments now follow the same bootstrap pattern.
|
||
|
||
Rewrote language-search-fragment.php to use the same clean pattern as
|
||
tag-search-fragment.php: ->searchLanguages(), simple exact match check,
|
||
no predefined exclusion list. Both fragments now share identical structure.
|
||
|
||
fix: exclude main languages (français, anglais, néerlandais) from language-search suggestions
|
||
|
||
---
|
||
a3ded169158f | 2026-05-10 12:52
|
||
Add sidebar TOC, simplify Données Secondaires section
|
||
|
||
- Rename 'Éditer Données Secondaires' → 'Données Secondaires', remove fieldset wrapper on Mots-clés link
|
||
- Create admin-toc.php partial: IntersectionObserver-based sidebar nav
|
||
- Include TOC on contenus.php, acces.php, parametres.php
|
||
- Add .admin-with-toc flex layout (sidebar + main) and .admin-toc CSS
|
||
- Fonts (Ductus, BBB DM Sans): verified loaded via variables.css → common.css import chain
|
||
- TOC: move inside <main> as <aside>, content in <article>, fix scrolling
|
||
- Lazy load: hx-trigger='load delay:100ms' with spinner (htmx-indicator) for tags/langues
|
||
- Inline rename: edit button in Nom cell, HTMX post for rename, validate+ cancel buttons
|
||
- Checkbox column: width:1% / fit-content
|
||
- Remove per-row merge forms/selects, only bulk merge when ≥2 checkboxes selected
|
||
- Remove per-row merge dialogs, keep only bulk merge and delete dialogs
|
||
- Add htmx-settling CSS transition for lazy-load fade-in
|
||
- Update acces.php/parametres.php: article layout, TOC inside main
|
||
- TOC: DOMContentLoaded guard, use <nav>+<a> directly instead of <ul>/<li>
|
||
- Section spacing: margin-bottom on sections and fieldsets in admin-main--toc
|
||
- Language dedup: GROUP BY LOWER(name) in getAllLanguagesWithCount and searchLanguages
|
||
- deduplicateLanguages() merges duplicate names and reassigns thesis_languages
|
||
- Sticky bulk-actions: position:sticky;top:0;z-index:10
|
||
- Tags toolbar: title left, stat count right (margin-left:auto), search bar under
|
||
- Tags count stat updated via hx-swap-oob from fragment
|
||
- Remove margin/max-width from .admin-main--toc
|
||
- Gap between TOC and article: --space-xs, sticky top: --space-xs
|
||
- Main padding: --space-s / --space-m / --space-xl (was --space-l/--space-l/--space-2xl)
|
||
- Article padding-top: --space-m
|
||
|
||
---
|
||
396cf19e9f12 | 2026-05-10 12:13
|
||
Add Mots-clés and Langues management to contenus page
|
||
|
||
- Add searchLanguages, getAllLanguagesWithCount, renameLanguage, mergeLanguage, deleteLanguage to Database
|
||
- Create actions/language.php handler with rename/merge/merge_bulk/delete actions
|
||
- Add merge_bulk action to actions/tag.php
|
||
- Add Mots-clés section to contenus template with HTMX search, select checkboxes, rename/delete/merge buttons, and multi-select merge toolbar
|
||
- Add Langues section to contenus template with same pattern
|
||
- Create contenus-tags-fragment.php and contenus-languages-fragment.php HTMX fragments
|
||
- Remove form-settings- from flat-fieldset CSS selector so fieldsets in contenus retain border/padding
|
||
- contenus.php: add 'Gérer les mots-clés' link to /admin/tags.php
|
||
- contenus.php: add Langues fieldset with HTMX search + table (rename/merge/delete/bulk)
|
||
- tags.php: add HTMX search bar, checkbox column, bulk merge toolbar
|
||
- Create tags-fragment.php and contenus-langues-fragment.php for HTMX
|
||
- Remove tab component and associated CSS
|
||
- Simplify JS: separate tags/langues-prefixed functions
|
||
- Fix redirects: tag.php defaults to /admin/tags.php, supports return override
|
||
- Keep tags.php standalone page and Mots-clés button unchanged
|
||
|
||
---
|
||
494675d78c5f | 2026-05-10 11:39
|
||
Move Formulaire settings to contenus, remove delete-all TFE
|
||
|
||
- Removed 'Supprimer tous les TFE' danger zone from parametres (template, dialog,
|
||
backend handler, Database::deleteAllTheses(), AdminLogger method)
|
||
- Moved Formulaire section (access type toggles, restricted files) from parametres
|
||
to contenus under new h2 'Paramètres du Formulaire'
|
||
- Moved Types de travaux from parametres to contenus as sub-section under
|
||
Paramètres du Formulaire
|
||
- Existing 'Structure du formulaire' section now a sub-heading (h3) under
|
||
Paramètres du Formulaire in contenus
|
||
- Sub-sections: Restrictions d'accès aux fichiers, Degré d'ouverture,
|
||
Types de travaux, Structure du Formulaire
|
||
- Added siteSettings query to contenus controller
|
||
|
||
---
|
||
048a14bc2edc | 2026-05-10 10:59
|
||
Add language-search component for Autre Langue input + active search in lists
|
||
|
||
Mirrors the mots-clé tag-search system: dropdown suggestions from
|
||
existing languages via HTMX, pill display with bin-icon remove buttons,
|
||
'Créer' option for new languages. Replaces the plain text input.
|
||
|
||
- New partial: templates/partials/form/language-search.php
|
||
- New fragment: public/partage/language-search-fragment.php
|
||
- Admin wrapper: public/admin/language-search-fragment.php
|
||
- Updated language-autre-fragment to return just the required asterisk indicator
|
||
- Updated both controllers to handle language_autre as array (pill-based)
|
||
with backward-compatible string path
|
||
- Updated edit form to compute selectedOtherLanguages from DB
|
||
- Registered new route in partage/index.php
|
||
- Fix CSV importer: split comma-separated language column into individual entries
|
||
- Add htmx active search to admin index, title line-clamp, predefined languages only in checkboxes
|
||
- Admin index: filter form now uses htmx triggers (input delay:300ms on search,
|
||
change on selects) to actively search without page reload
|
||
- Sort links include hx-push-url for back-button support
|
||
- Added loading indicator bar (.admin-search-indicator)
|
||
- Title column: line-clamp at 2 lines with overflow hidden, native title attr
|
||
tooltip for full text
|
||
- Language checkboxes now show only 3 predefined languages (Français, Anglais,
|
||
Néerlandais); all others go via the Autre langue search component
|
||
- Added Database::getPredefinedLanguages() and excluded predefined from
|
||
language-search-fragment suggestions
|
||
- Included hidden sort/dir inputs in table-wrap so sort state preserved across
|
||
filter changes
|
||
- Fix language-search: block 'Créer' for predefined languages in dropdown
|
||
The 'Créer' option in the language-search dropdown now also checks against the
|
||
predefined set (français, anglais, néerlandais) to avoid offering creation of
|
||
languages that already exist as checkboxes.
|
||
|
||
---
|
||
96fa8ee26687 | 2026-05-10 03:33
|
||
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
|
||
|
||
---
|
||
fa30aab36837 | 2026-05-10 03:02
|
||
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
|
||
|
||
---
|
||
8a4b2541fbd6 | 2026-05-10 02:55
|
||
Fix: email clearing in findOrCreateAuthor, htmlspecialchars(null) crash in old(), dead contact_interne field, access_type_id radio clearing
|
||
|
||
- findOrCreateAuthor: always update email column (pass null when empty/falsy) so clearing an email actually persists
|
||
- admin/add.php & admin/edit.php old(): add null guard before htmlspecialchars, cast to string
|
||
- jury-fieldset.php: guard against old() returning array for scalar-checked jury_lecteur keys
|
||
- formulaire.php: only suppress display_errors in production (not cli-server dev mode)
|
||
- Removed dead contact_interne field from backoffice form (no DB column, never saved)
|
||
- Removed dead contactInterne validation from ThesisCreateController
|
||
- Added "— Non défini" radio option for access_type_id in admin mode for clearing
|
||
- Fixed strict int-vs-string comparison breaking radio button checked detection
|
||
|
||
---
|
||
6cc0e407f305 | 2026-05-09 21:36
|
||
Error tests, FK violations fix
|
||
|
||
- ErrorHandler tests: 77 assertions covering FK extraction, normalization, dedup, edge cases. Fix FK table map for child tables.
|
||
- Fix FK violation: (int)null → 0 in createThesis for orientation/ap/finality/license FK columns. Add FK value logging to updateThesis.
|
||
- Add CURRENT_ISSUES.md with summary of FK violation, dev debugging, and tag dedup status for next conversation
|
||
|
||
---
|
||
a80b2c08bf80 | 2026-05-09 20:51
|
||
Admin mobile block: fix inline style beating media query
|
||
|
||
---
|
||
6614b04dbddd | 2026-05-09 20:42
|
||
Fix bulk form nesting, remove count bar, stopPropagation on actions
|
||
|
||
- Remove admin-bulk-meta__default (TFE count bar) — only bulk actions on selection
|
||
- Move #bulk-form out of table wrapper to avoid nested forms (was breaking
|
||
per-row publish/unpublish which submitted to bulk form instead)
|
||
- execBulk() now populates #bulk-checkboxes with hidden inputs from checked boxes
|
||
- Add event.stopPropagation() to edit link and delete+publish forms so
|
||
clicking actions doesn't navigate the row to recapitulatif
|
||
- Delete button: only opens confirm modal, no row nav
|
||
|
||
---
|
||
b6908f745316 | 2026-05-09 20:39
|
||
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)
|
||
|
||
---
|
||
7711557d080c | 2026-05-09 19:11
|
||
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
|
||
|
||
---
|
||
dc3191f458e3 | 2026-05-09 18:58
|
||
add explanation hint to is_published checkbox in Backoffice fieldset
|
||
|
||
---
|
||
bcf683c5c146 | 2026-05-09 18:54
|
||
Merge Publication fieldset's is_published checkbox into Backoffice fieldset
|
||
|
||
Move the is_published checkbox from its own separate Publication fieldset
|
||
into the Backoffice fieldset (as item #8). This means the publish control
|
||
is now present in both add and edit admin forms (previously it was only
|
||
shown in edit mode via $showPublish).
|
||
|
||
---
|
||
c4a23d5c2d21 | 2026-05-09 18:39
|
||
Remove duration_pages/duration_minutes/file_size_info; rename cc4r → cc2r in DB and code
|
||
|
||
---
|
||
cc0ae32df045 | 2026-05-09 16:58
|
||
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.
|
||
|
||
---
|
||
59bbcf4642fd | 2026-05-09 16:15
|
||
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.
|
||
|
||
---
|
||
013317c97f90 | 2026-05-09 16:11
|
||
link creation: fieldset with checkboxes for objet restriction, TFE checked by default
|
||
|
||
link creation: fieldset with checkboxes for objet restriction, TFE checked by default; password/expiration in second fieldset 'Accès'
|
||
|
||
---
|
||
21c2b55bfbda | 2026-05-08 19:24
|
||
style: normalize headers, overtype editor rounded corners, remove duplicate cover preview, thesis-add-header grid layout, subtitle below header with top gradient
|
||
|
||
---
|
||
7ccadbb224ec | 2026-05-08 19:18
|
||
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
|
||
|
||
---
|
||
862ed02136a1 | 2026-05-08 17:57
|
||
style: unify form element styles in common.css, redesign focus rings, refactor public search bar, tweak admin section
|
||
|
||
---
|
||
77fd282e29c0 | 2026-05-08 17:31
|
||
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.
|
||
|
||
---
|
||
8f4f9d00b457 | 2026-05-08 17:03
|
||
Refactor: Form improvements and cleanup: note contextuel, annexes, fichiers
|
||
|
||
1. fix: form improvements — multiple promoteurices, asterisks, contact dedup, bentopdf
|
||
|
||
- Multiple promoteurice (interne + ULB): both fieldsets now support dynamic
|
||
add/remove rows (same pattern as lecteurs). field names changed to arrays
|
||
(jury_promoteur[], jury_promoteur_ulb_name[]). Controllers accept both
|
||
scalar and array forms for backwards compat.
|
||
- ULB promoteurice: when finality=Approfondi, asterisk appears on legend
|
||
and first ULB input is marked required (JS toggle). Non-Approfondi hides
|
||
the fieldset and clears values.
|
||
- Contact visibility duplication: removed redundant contact_public checkbox
|
||
from admin add/edit forms (showContact=false). The 'mail' field in
|
||
fieldset-tfe-info already serves this purpose.
|
||
- Asterisk fixes: website URL field now has asterisk+required when Site web
|
||
format selected. Video/audio already had correct required handling.
|
||
- bentopdf link: clearer full URL 'https://bentopdf.com/' in both
|
||
fichiers-fragment.php and form.php (edit mode)
|
||
|
||
2. refactor: merge Note contextuelle into Backoffice, add Lien BAIU, reorder fields
|
||
|
||
Backoffice fieldset now contains in order:
|
||
1. Note contextuelle (was standalone fieldset)
|
||
2. Points du jury
|
||
3. Remarques
|
||
4. Lien BAIU (moved from Métadonnées complémentaires)
|
||
5. Exemplaire physique BAIU
|
||
6. Exemplaire physique ERG
|
||
7. Contact interne
|
||
|
||
|
||
Métadonnées complémentaires now only has: pages, minutes, annexes checkbox.
|
||
Removed dead showContextNote variable from form.php, add.php, edit.php.
|
||
Controller baiu_link still mapped to input name "lien" (no migration needed).
|
||
|
||
3. refactor: move annexes checkbox from Métadonnées into Fichiers fieldset
|
||
|
||
- Removed 'Ce TFE comporte des annexes' checkbox from
|
||
fieldset-metadata.php.
|
||
- Added annexes checkbox + conditional file input to
|
||
fichiers-fragment.php. When checked, an HTMX swap reveals
|
||
the 'annexes' file input (multiple, PDF or ZIP/TAR, max 500 MB).
|
||
- form.php seeds ['has_annexes'] for initial fragment render.
|
||
- Métadonnées complémentaires now only contains pages + minutes.
|
||
|
||
---
|
||
03c5fd217eaa | 2026-05-08 16:48
|
||
feat: dual upload system — direct file storage + PeerTube API integration
|
||
|
||
Adds a parallel PeerTube upload system behind a feature flag (disabled by default
|
||
until upload quota is granted). When disabled, the existing direct file upload
|
||
path works unchanged.
|
||
|
||
Files:
|
||
- src/PeerTubeService.php — credential storage (encrypted), OAuth2 token
|
||
retrieval, multipart upload to /api/v1/videos/upload
|
||
- migrations/021_peertube_settings.sql — peertube_settings singleton table
|
||
+ peertube_upload_enabled site_setting (default 0)
|
||
- admin/actions/settings.php — peertube section handler
|
||
- admin/parametres.php / templates/admin/parametres.php — PeerTube UI section
|
||
- partage/fichiers-fragment.php — shows file inputs when enabled, TODO notice otherwise
|
||
- ThesisCreateController / ThesisEditController — handlePeerTubeUpload()
|
||
- tfe.php — PeerTube iframe embed detection
|
||
- AdminLogger — logPeerTubeUpdate()
|
||
|
||
---
|
||
11e61226e2aa | 2026-05-08 16:35
|
||
fix: justfile shebang recipes indentation (spaces → tabs)
|
||
|
||
---
|
||
e6829994b6d1 | 2026-05-08 13:03
|
||
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.
|
||
|
||
---
|
||
7e35bba5304c | 2026-05-08 12:48
|
||
Encrypt SMTP password at rest with AES-256-GCM
|
||
|
||
---
|
||
95fcbc919a44 | 2026-05-08 12:40
|
||
Remove required from all admin add/edit form inputs
|
||
|
||
- Skip required-field validation for orientation/ap/finality/licence/jury in admin add+edit
|
||
|
||
---
|
||
5735ccbc3878 | 2026-05-08 11:30
|
||
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.
|
||
|
||
---
|
||
6ba13e00eabd | 2026-05-08 10:56
|
||
test: add ShareLinkTest + PureLogicTest (TDD), fix coverMap undefined in SearchController
|
||
|
||
---
|
||
15d54fa19ee3 | 2026-05-08 10:55
|
||
add Néerlandais language option and make language_autre conditionally required
|
||
|
||
---
|
||
f3d961556260 | 2026-05-08 10:46
|
||
merge banners into covers: remove banner field, migrate files, add covers to search/home/repertoire cards
|
||
|
||
---
|
||
e3896811c416 | 2026-05-07 23:45
|
||
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
|
||
|
||
---
|
||
bdd95341b09c | 2026-05-07 22:48
|
||
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.
|
||
|
||
---
|
||
ac0008df6cc5 | 2026-05-07 21:24
|
||
Add website-type TFE support: URLs stored as thesis_files rows, HTMX-toggle on Site web format
|
||
|
||
---
|
||
9dc7ea98f252 | 2026-05-07 20:58
|
||
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.
|
||
|
||
---
|
||
03121d6b7ef5 | 2026-05-07 20:52
|
||
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
|
||
|
||
---
|
||
d9dd4bdbc777 | 2026-05-07 20:42
|
||
Edit Email confirmation styling
|
||
|
||
---
|
||
696259afae08 | 2026-05-07 19:54
|
||
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)
|
||
|
||
---
|
||
51f9f56e0995 | 2026-05-07 19:40
|
||
Replace span with a link + href in about.php for credits
|
||
|
||
---
|
||
e0c748d8e7b0 | 2026-05-07 18:44
|
||
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
|
||
|
||
---
|
||
24d68dda598e | 2026-05-07 17:52
|
||
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
|
||
|
||
---
|
||
dce0e0b30194 | 2026-05-07 17:30
|
||
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
|
||
|
||
---
|
||
7793b6f86d4f | 2026-05-07 16:28
|
||
add file export system for admins
|
||
|
||
- ExportController: getAllThesisFiles(), buildExportManifest(), createExportZip()
|
||
builds a ZIP archive with manifest.json + files/ mirror of storage/theses/
|
||
- Database: getAllThesisFilesForExport() queries all thesis_files + identifier
|
||
- AdminLogger: logFilesExport() audit log entry
|
||
- admin/actions/export-files.php: thin dispatcher, streams zip with headers
|
||
- templates/admin/index.php: 'Exporter fichiers' button next to CSV export
|
||
|
||
---
|
||
821369f00468 | 2026-05-07 12:27
|
||
exclude maintenance.flag from rsync deploy and git
|
||
|
||
---
|
||
3f87d71e389e | 2026-05-05 18:54
|
||
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
|
||
|
||
---
|
||
b063312642c5 | 2026-05-05 18:27
|
||
centralise repertoire filter column rendering
|
||
- shared repFilterEntry() and config array
|
||
- shared repFilterEntry() and $filterColumns config array
|
||
- fix single-valued FK fading via full intersection
|
||
|
||
---
|
||
bca707ee9687 | 2026-05-05 11:34
|
||
standardise buttons: .btn base class (border-radius 10px, padding var(--space-xs))
|
||
|
||
---
|
||
b58445f71c84 | 2026-05-05 11:09
|
||
sticky save/cancel buttons at top-right of admin edit page
|
||
|
||
---
|
||
95066de7b4d3 | 2026-05-05 10:31
|
||
standardise multi-author support across all forms
|
||
|
||
- ThesisCreateController: comma-split auteurice, sort alphabetically,
|
||
use setThesisAuthors() instead of hardcoded createThesis() author_id
|
||
- Database::createThesis(): removed author_id param and hardcoded insert
|
||
- Database::findDuplicateThesis(): accepts array of author names, matches
|
||
any shared author via IN + DISTINCT
|
||
- ThesisEditController::save(): sort authors alphabetically on save
|
||
- File folder naming: slug from all authors alphabetically sorted
|
||
- v_theses_full GROUP_CONCAT: ORDER BY a.name ASC for deterministic display
|
||
- Migration 012_author_view_order.sql: rebuilds view with alphabetical order
|
||
|
||
---
|
||
125c501f4080 | 2026-05-04 18:33
|
||
Fix 403 on HTMX fragment requests: AdminAuth Basic Auth sets session key
|
||
|
||
---
|
||
37111eaac452 | 2026-05-04 18:19
|
||
fix: add missing remote DB migrations and deploy-migrate recipe
|
||
|
||
Four ALTER TABLE / CREATE TABLE statements were applied locally but never
|
||
deployed to the remote production database, causing:
|
||
|
||
- acces.php → 500: share_links.is_archived missing (ShareLink::listActive/listArchived)
|
||
- parametres.php → 500: smtp_settings.notify_email missing (SmtpRelay::getSettings)
|
||
- /tfe?id=N → redirect-to-home: thesis_files.sort_order missing (getThesisFiles ORDER BY)
|
||
- admin_audit_log table missing (AdminLogger::insertDb, best-effort but noisy)
|
||
|
||
Adds four pending migrations (008–011) covering all missing schema changes.
|
||
Adds 'deploy-migrate' just recipe to run migrations on the remote after deploy.
|
||
|
||
---
|
||
ae6d9b86b32a | 2026-05-04 17:52
|
||
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
|
||
|
||
---
|
||
ca5983075d2d | 2026-05-04 17:34
|
||
feat: admin audit logging across all admin actions
|
||
|
||
- AdminLogger: JSON-lines → /var/log/xamxam.log (prod) / storage/logs/admin.log (dev)
|
||
+ best-effort DB mirror to admin_audit_log table
|
||
- DB: admin_audit_log table, share_links.is_archived column
|
||
- ShareLink: archive() replaces delete(), toggleActive() returns new state,
|
||
listActive()/listArchived() split, validateLink blocks archived slugs
|
||
- All action handlers wired: publish, unpublish, visibility, delete, csv/db export,
|
||
tfe add/edit, tags, pages, apropos, form-help, access-request, maintenance,
|
||
settings (formulaire toggles, objet types, smtp update), smtp-test
|
||
- acces.php: archive button replaces delete; collapsible archived links section
|
||
- setup-server.sh: provision /var/log/xamxam.log (www-data:xamxam 640)
|
||
|
||
---
|
||
5f24dcae7eb2 | 2026-05-04 17:04
|
||
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
|
||
|
||
---
|
||
a2cba6d3c01c | 2026-05-04 16:29
|
||
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
|
||
|
||
---
|
||
0a05f3911cd6 | 2026-05-04 16:06
|
||
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`.
|
||
|
||
---
|
||
d6e30ec9cdae | 2026-05-04 14:46
|
||
Enhance serve recipe to automatically open the browser
|
||
|
||
- use xdg-open firefox
|
||
- keep serve recipe in foreground
|
||
|
||
---
|
||
8a38708fc8f0 | 2026-05-01 22:41
|
||
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
|
||
|
||
---
|
||
d09f1942f014 | 2026-05-01 22:39
|
||
Fix Mistral provider: change api from openai-completions to mistral-conversations
|
||
|
||
---
|
||
34b2d073ee1c | 2026-04-30 16:00
|
||
style(toast): reposition to bottom-center, solid bg, larger text, longer duration
|
||
|
||
---
|
||
e8bf89d18479 | 2026-04-30 14:16
|
||
admin header: replace déconnexion text with SVG sign-out icon
|
||
|
||
---
|
||
0f849468f70b | 2026-04-30 13:52
|
||
feat: inline email retry on 550 rejection in tfe access request form
|
||
|
||
---
|
||
da53bf5d7a99 | 2026-04-30 13:44
|
||
feat: email retry page on 550 rejection; confirmation_email optional in admin form
|
||
|
||
---
|
||
898a87789b0f | 2026-04-30 13:37
|
||
fix(smtp-test): catch SmtpSendException to surface delivery errors as flash messages
|
||
|
||
---
|
||
19784090a3b6 | 2026-04-30 13:26
|
||
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.
|
||
|
||
---
|
||
6a37d21f3fc0 | 2026-04-30 13:22
|
||
docs: add file-uploads.md — accepted types, limits, storage, ordering, security
|
||
|
||
---
|
||
a83dc1c74e8e | 2026-04-30 13:07
|
||
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
|
||
|
||
---
|
||
2188ff5479db | 2026-04-30 12:56
|
||
docs: add SMTP 550 postfix fix report for mail admin
|
||
|
||
---
|
||
89b7ab476e66 | 2026-04-30 12:40
|
||
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)
|
||
|
||
---
|
||
8d115dc96504 | 2026-04-30 12:36
|
||
smtp: enable TLS peer verification, fix envelope injection, fix dot-stuffing
|
||
|
||
---
|
||
33987c9b1589 | 2026-04-30 12:19
|
||
smtp: add notify_email field; fix admin notification sent to no-reply sender
|
||
|
||
---
|
||
bdb68479d571 | 2026-04-30 12:16
|
||
smtp: typed probe errors with per-field UI highlighting on save
|
||
|
||
---
|
||
b750aca2f502 | 2026-04-30 12:10
|
||
smtp: probe credentials on save (connect+auth+quit, no message sent)
|
||
|
||
---
|
||
56c8d5443589 | 2026-04-30 11:42
|
||
repertoire: align all column headings to shared baseline row
|
||
|
||
---
|
||
a9e03c4b1c80 | 2026-04-30 11:41
|
||
repertoire: fixed-header columns, remove main/index padding, minimal column padding
|
||
|
||
---
|
||
0960afb7317d | 2026-04-30 11:38
|
||
fix: add missing favicon tags to partage/recapitulatif.php
|
||
|
||
---
|
||
9ba60084bf93 | 2026-04-30 11:34
|
||
fix: require SmtpRelay.php before StudentEmail.php in partage/index.php
|
||
|
||
---
|
||
cb883ab33f61 | 2026-04-30 11:27
|
||
fix: deploy-server.sh migrates posterg.db → xamxam.db and cleans legacy nginx configs
|
||
|
||
---
|
||
ab51bf3a6615 | 2026-04-30 11:11
|
||
fix: deploy-server.sh cleans up legacy posterg configs and prunes old xamxam backups
|
||
|
||
---
|
||
68e30abb5673 | 2026-04-30 11:10
|
||
fix: remove Post-ERG branding → XAMXAM; drop legacy posterg nginx symlink in deploy script; rename posterg.db → xamxam.db
|
||
|
||
---
|
||
c949cf948170 | 2026-04-30 10:50
|
||
rename posterg → xamxam throughout: nginx conf, scripts, PHP source, docs
|
||
|
||
---
|
||
3e35bbc40f4c | 2026-04-30 00:03
|
||
style: align mobile nav dropdown links left
|
||
|
||
---
|
||
471c89263836 | 2026-04-30 00:03
|
||
style: larger mobile nav dropdown links
|
||
|
||
---
|
||
42286b1b7152 | 2026-04-30 00:03
|
||
Header link modification
|
||
|
||
---
|
||
671cfb6d83ec | 2026-04-29 22:13
|
||
fix: hamburger dropdown not showing — reset display:none at mobile breakpoint
|
||
|
||
---
|
||
11f429eb7243 | 2026-04-29 22:12
|
||
feat: pure-CSS hamburger menu for public nav (≤640px)
|
||
|
||
---
|
||
c27ffafa7edd | 2026-04-29 21:58
|
||
fix: add missing favicon tags to partage/index.php (error, password gate, form)
|
||
|
||
---
|
||
80b7fddea43f | 2026-04-29 21:54
|
||
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.
|
||
|
||
---
|
||
992f74b31cfd | 2026-04-29 21:47
|
||
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.
|
||
|
||
---
|
||
43702542ebdd | 2026-04-29 21:44
|
||
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
|
||
|
||
---
|
||
5c39e856a352 | 2026-04-29 21:34
|
||
fix: pass enabledAccessTypes from ThesisEditController to edit view
|
||
|
||
---
|
||
885150ea45d3 | 2026-04-29 21:33
|
||
css: centralise semantic element baseline styles in common.css
|
||
|
||
---
|
||
b5189c0d089f | 2026-04-29 21:18
|
||
admin: merge acces-etudiante+file-access into acces.php, absorb system.php into parametres.php
|
||
|
||
---
|
||
670a38f30da4 | 2026-04-29 21:08
|
||
add form help blocks: DB table, admin editor, live rendering in partage form
|
||
|
||
---
|
||
0437ec8d1567 | 2026-04-29 21:05
|
||
fix: escape apostrophe in FORM_HELP_LABELS string (Database.php:2005)
|
||
|
||
---
|
||
d665cb502dde | 2026-04-29 20:59
|
||
centralise form fieldsets into shared partials; add TODO stubs in partage form
|
||
|
||
---
|
||
0628efbba37f | 2026-04-29 20:47
|
||
Updated the README
|
||
|
||
---
|
||
89de6dd748d0 | 2026-04-28 22:21
|
||
Removed the test csv
|
||
|
||
---
|
||
18a02a0018a5 | 2026-04-28 22:21
|
||
deploy: rename deploy path from /var/www/posterg to /var/www/xamxam
|
||
|
||
---
|
||
cd68e6e9d7d4 | 2026-04-28 22:12
|
||
deploy: exclude posterg.db, theses/, covers/ from rsync to avoid overwriting remote data
|
||
|
||
---
|
||
59c4cf055f5d | 2026-04-27 21:32
|
||
smtp-test: bypass DB, use POST fields directly for credentials
|
||
|
||
---
|
||
9ff8b1b4642b | 2026-04-27 21:16
|
||
fix: call RateLimit::checkKey() as instance method in request-access.php
|
||
|
||
---
|
||
e09b0561156f | 2026-04-27 21:11
|
||
fix: iframe for PDF display, exclude cover files from public loop, no session on media requests
|
||
|
||
---
|
||
46a3c360ecf6 | 2026-04-27 21:06
|
||
fix: use local storage/ in dev, create upload dirs, gitignore uploads
|
||
|
||
---
|
||
48059c23175e | 2026-04-27 21:03
|
||
fix: serve logs, formulaire.php error_log path, CSRF debug, undefined $redirect
|
||
|
||
---
|
||
32a75095987f | 2026-04-27 20:41
|
||
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)
|
||
|
||
---
|
||
aca7e7eef8e6 | 2026-04-27 20:40
|
||
rename thanks.php to recapitulatif.php in admin and partage
|
||
|
||
---
|
||
4d88bd8cc5ab | 2026-04-27 20:38
|
||
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
|
||
|
||
---
|
||
8e864fc62482 | 2026-04-27 20:33
|
||
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)
|
||
|
||
---
|
||
27e1b6828d57 | 2026-04-27 20:12
|
||
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
|
||
|
||
---
|
||
5c776dd39e48 | 2026-04-27 19:30
|
||
Updated gitignore to keep cache folder but exclude rate_limit logs
|
||
|
||
---
|
||
d2d54b577a0c | 2026-04-27 19:30
|
||
Added cache folder to gitignore
|
||
|
||
---
|
||
88b9f341cd57 | 2026-04-27 18:31
|
||
Replace Posterg branding with XAMXAM in all user-facing content
|
||
|
||
---
|
||
7e26351f4b05 | 2026-04-27 18:07
|
||
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
|
||
|
||
---
|
||
780105eec025 | 2026-04-25 19:20
|
||
Fix storage/cache ignore path and untrack cache files
|
||
|
||
---
|
||
209dc86990ae | 2026-04-25 19:20
|
||
Added the cache folder wildcard
|
||
|
||
---
|
||
54ef24d21f4a | 2026-04-24 22:48
|
||
ignore *.db files, fix thesis identifier to use max seq instead of count, untrack .db files
|
||
|
||
---
|
||
4986fa74f40a | 2026-04-24 16:55
|
||
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
|
||
|
||
---
|
||
decb9e29076e | 2026-04-24 13:38
|
||
fix: replace mb_strlen/mb_substr in student-preview.php (mbstring unavailable)
|
||
|
||
---
|
||
9b4cb52617e2 | 2026-04-24 13:32
|
||
fix: replace mb_strtolower with strtolower in admin import (mbstring unavailable in php8.4-fpm)
|
||
|
||
---
|
||
769beae4ee48 | 2026-04-24 13:27
|
||
fix: drop hx-trigger once — rely on browser cache for dedup
|
||
|
||
---
|
||
743268cf1b0e | 2026-04-24 13:27
|
||
fix: drop hx-trigger once — rely on browser cache for dedup
|
||
|
||
---
|
||
6eb111a6ab7e | 2026-04-24 13:20
|
||
perf: htmx lazy popover with Cache-Control — no pre-render, images load on hover only
|
||
|
||
---
|
||
e590d8e0351a | 2026-04-24 13:17
|
||
perf: pre-render student popover cards server-side into <template> tags — zero per-hover requests
|
||
|
||
---
|
||
53c3127140b2 | 2026-04-24 13:13
|
||
feat: student name popover preview on /repertoire via htmx
|
||
|
||
---
|
||
ede53746ba3b | 2026-04-24 13:11
|
||
feat: student name popover preview on /repertoire via htmx
|
||
|
||
---
|
||
d961f9533c2c | 2026-04-22 14:06
|
||
feat: add objet field (tfe/thèse/frart) with share-link restriction and site-settings toggles
|
||
|
||
---
|
||
dbaabaf8a058 | 2026-04-22 11:30
|
||
merge all migrations into schema.sql
|
||
|
||
---
|
||
38031374c1c8 | 2026-04-22 11:23
|
||
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
|
||
|
||
---
|
||
95bce2bbad0d | 2026-04-22 11:18
|
||
Extract form CSS into form.css; scope system.css to system.php only
|
||
|
||
---
|
||
d82556c59679 | 2026-04-22 11:10
|
||
Unify form CSS: move licence/share-badge styles to admin.css, remove inline style from partage form
|
||
|
||
---
|
||
057d2539eb3a | 2026-04-22 10:58
|
||
SmtpRelay: parse EHLO caps, prefer AUTH PLAIN over AUTH LOGIN
|
||
|
||
---
|
||
a3849a8e6993 | 2026-04-22 10:53
|
||
SmtpRelay: replace mail() stub with native socket SMTP client
|
||
|
||
---
|
||
b448d0d40c24 | 2026-04-22 10:45
|
||
Lock body scroll: only main/inner elements scroll on admin and public pages
|
||
|
||
---
|
||
5a58eefe66be | 2026-04-22 10:40
|
||
feat(admin): add SMTP test email button on parametres page
|
||
|
||
---
|
||
4839b568deba | 2026-04-21 21:49
|
||
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
|
||
|
||
---
|
||
362688c0faa7 | 2026-04-21 19:25
|
||
fix: remove broken flash-messages include from admin footer; make repertoire columns scrollable
|
||
|
||
---
|
||
19ef2a11dca6 | 2026-04-21 19:16
|
||
fix CSV importer AP/orientation name resolution + seed missing AP programs
|
||
|
||
- migration 014: adds Récits et expérimentation (RE), PACS, sets code NS
|
||
on Narration Spéculative; applied to both posterg.db and test.db
|
||
|
||
- importer (admin/index.php): replaced the code-only ap_programs lookup
|
||
(SELECT WHERE code=?) and the orientationMap short-code translation with
|
||
two resolver closures that handle the real CSV format (full names):
|
||
|
||
resolveAP(): alias map for L.I.E.N.S., case variants → exact name
|
||
match → code match (legacy) → case-insensitive name match
|
||
|
||
resolveOrientation(): legacy 2-letter code map → alias map for
|
||
Installation/Performance, Arts numériques, Design numérique →
|
||
exact name match → case-insensitive name match
|
||
|
||
All 5 AP values and 13 orientation values from the real CSV now
|
||
resolve to correct DB IDs. Legacy short-code CSVs (test.db format)
|
||
continue to work unchanged.
|
||
|
||
---
|
||
73fdda4a7fc9 | 2026-04-21 19:05
|
||
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.
|
||
|
||
---
|
||
78449afe641f | 2026-04-20 16:48
|
||
some css changes
|
||
|
||
---
|
||
1b02ccb1d5b3 | 2026-04-20 16:19
|
||
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.
|
||
|
||
---
|
||
e21a4d81a2c3 | 2026-04-20 15:02
|
||
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).
|
||
|
||
---
|
||
fa75ca4a65f4 | 2026-04-20 14:23
|
||
fix: inline getDatabasePath into Database.php, delete config/config.php
|
||
|
||
- Remove require_once for config/config.php (file was never deployed — outside app/)
|
||
- Inline DB path resolution directly in Database::determineDatabasePath()
|
||
- Uses APP_ROOT when defined (bootstrap already loaded), falls back to __DIR__/../
|
||
- DB_ENV=test|prod env-var override preserved for tests
|
||
- php -S cli-server -> test.db, nginx/fpm -> posterg.db
|
||
|
||
---
|
||
468278349adb | 2026-04-20 14:13
|
||
fix: router.php — bypass admin requests to front controller
|
||
|
||
---
|
||
033599aa36b5 | 2026-04-20 14:12
|
||
fix: config/config.php — correct DB paths to app/storage/, add test.db for dev
|
||
|
||
---
|
||
5af31acedaaa | 2026-04-20 14:11
|
||
fix: Database.php require_once -> resolve config.php from app/src/
|
||
|
||
---
|
||
de2e7a61ee6f | 2026-04-20 12:41
|
||
feat: single entry point routing — convert to front controller pattern
|
||
|
||
- Create app/public/index.php as front controller (bootstrap + Dispatcher)
|
||
- Rewrite app/router.php for PHP dev server → all non-asset requests to index.php
|
||
- Update Dispatcher to render full page layouts (head+header+view+footer)
|
||
- Move public view templates into templates/public/ (home, search, tfe, about, repertoire)
|
||
- Delete dead direct-access public/*.php files (apropos, search, tfe, licence, repertoire)
|
||
- Add clean URL routes to Dispatcher (/search, /tfe, /repertoire, /apropos, /licence, /media)
|
||
- Remove .php extensions from all internal links (header, views, templates, URLs)
|
||
- Update OG tags in controllers to use clean URLs
|
||
- Update nginx posterg.conf → front-controller try_files pattern, block direct .php access
|
||
- Update header.php and search-bar.php form actions to clean URLs
|
||
- Switch AboutController nav key from 'nav' to 'currentNav' for consistency
|
||
|
||
---
|
||
75f808bee4bd | 2026-04-17 11:44
|
||
feat: extract MediaController, wire into Dispatcher, delete media.php
|
||
|
||
---
|
||
b03be51b92df | 2026-04-16 14:45
|
||
feat: migrate admin system page to HTMX with tab-based navigation and log viewer
|
||
|
||
---
|
||
05002ccee459 | 2026-04-16 14:33
|
||
fix: allow isAuthenticated() bypass in development mode
|
||
|
||
---
|
||
bf30aab0b3d7 | 2026-04-16 13:44
|
||
migrate apropos data from config/apropos.php to SQLite
|
||
|
||
- Create apropos_contents table via migration 010
|
||
- Add Database methods: getAproposContent(), saveAproposContent(), getAllAproposContents()
|
||
- Replace admin/pages.php with admin/contenus.php (renamed header from 'Pages statiques' to 'Contenus')
|
||
- Replace admin/pages-edit.php with admin/contenus-edit.php (support editing pages + apropos contents)
|
||
- Create admin/actions/apropos.php for saving apropos data (contacts, credits, erg_url)
|
||
- Update public/apropos.php to read contacts/credits/erg_url from DB
|
||
- Delete config/apropos.php
|
||
|
||
---
|
||
4158c72d0862 | 2026-04-16 13:03
|
||
admin: replace header 'Ajouter un TFE' nav link with toolbar button
|
||
|
||
---
|
||
e70a65ffb66a | 2026-04-16 12:56
|
||
fix: session boot on POST path, consolidate rate limiter via checkKey()
|
||
|
||
---
|
||
a6df3c8c0e34 | 2026-04-16 12:00
|
||
fix: /partage/<slug> routing (regex delimiter + nginx location)
|
||
|
||
---
|
||
b7be93e30bc2 | 2026-04-16 11:50
|
||
Security: rate limiting and flash messaging for partage share links
|
||
|
||
- Add rate limiting (5 submissions per IP per 10 min, per share link)
|
||
to prevent abuse of shared submission endpoints
|
||
- Replace all plain die() error responses with styled flash messages
|
||
and redirects (invalid slug, disabled link, expired link, wrong password,
|
||
rate limit exceeded, CSRF failure)
|
||
- Add dedicated error page renderer for disabled/expired links with
|
||
home page link
|
||
- Password gate now uses flash message via session redirect instead
|
||
of inline error variable
|
||
|
||
---
|
||
150099dc3ce5 | 2026-04-16 11:50
|
||
admin: replace header 'Ajouter un TFE' nav link with toolbar button
|
||
|
||
---
|
||
c4705f6265bc | 2026-04-15 14:17
|
||
docs: add bookmarklet for auto-filling TFE test form
|
||
|
||
- bookmark.md with draggable link + readable source + lookup table reference
|
||
- all field selectors match actual form name attributes and schema IDs
|
||
|
||
---
|
||
f4aba500e69a | 2026-04-15 13:49
|
||
feat: student mode support for thanks page (admin-auth only)
|
||
|
||
- add hidden student_mode field in add.php form
|
||
- pass mode=student through redirect to thanks.php in formulaire.php
|
||
- thanks.php renders clean student thank-you page (no header, centered button)
|
||
- add CSS for .thanks-student-page, .btn-new-form, .thanks-success, .thanks-error
|
||
- admin auth always required; student mode is purely UI variant on the physical machine
|
||
|
||
---
|
||
c3affd22851f | 2026-04-15 13:43
|
||
admin/add: add ?mode=student toggle — hides admin header, keeps admin form css
|
||
|
||
---
|
||
150b5b1dacac | 2026-04-15 13:42
|
||
admin/add: add ?mode=student toggle — hides admin header, keeps auth
|
||
|
||
---
|
||
0eb2e310f444 | 2026-04-15 13:28
|
||
admin/parametres: cleanup page — remove card syntax, use semantic HTML (checkboxes/fieldsets), move delete-all-TFE danger zone into maintenance
|
||
|
||
---
|
||
fd4fb5ce4a90 | 2026-04-15 12:58
|
||
Add delete/batch-delete and sortable columns to admin list
|
||
|
||
- Database: add deleteThesis() and bulkDeleteTheses() methods with file cleanup
|
||
- Database: add SORT_MAP + buildOrderBy() for safe column sorting
|
||
- Database: getThesesList() now respects sort/dir filter params
|
||
- New action: actions/delete.php (single + batch delete with CSRF)
|
||
- Admin index: delete button per row with confirmation dialog
|
||
- Admin index: batch 'Supprimer' button in bulk actions bar
|
||
- Admin index: sortable column headers (ID, Titre, Année, Orientation, AP, Statut)
|
||
- Admin index: sort state preserved in pagination links
|
||
- CSS: admin-btn-delete (red muted), admin-sort-link styles
|
||
|
||
---
|
||
1b104df51eca | 2026-04-15 12:55
|
||
Fix undefined $from– variable: brace-interpolate variables before en-dash in double-quoted string
|
||
|
||
---
|
||
6f04514aa268 | 2026-04-15 12:51
|
||
fix: add structural guard for migration 008 in migrate.sh
|
||
|
||
---
|
||
0cb4451218bc | 2026-04-15 11:57
|
||
formulaire: default interne, unpublished, contact toggle, settings section
|
||
|
||
---
|
||
67a4aaac26a0 | 2026-04-15 10:58
|
||
Fix nginx deduplication: remove nginx/scripts/ entirely, fix README typos and dead references
|
||
|
||
---
|
||
507f3eb70454 | 2026-04-15 10:58
|
||
Consolidate nginx docs and scripts, update paths
|
||
|
||
---
|
||
3cd96ed28a70 | 2026-04-14 18:25
|
||
Deduplicate and standardise documentation
|
||
|
||
- Consolidate 36 markdown files → 14 (plus TODO.md)
|
||
- Merge overlapping docs into authoritative files:
|
||
- database.md (from DATABASE_SPECIFICATION + QUICK_SCHEMA_REFERENCE + DATABASE_CONFIG + SETUP)
|
||
- deployment.md (from SERVER_SETUP + COMPLETE_DEPLOYMENT_GUIDE + DEPLOYMENT_STEPS)
|
||
- security.md (from SECURITY_ANALYSIS + TODO.SECURITY)
|
||
- development.md (from DEVELOPMENT_GUIDE + LIVE_RELOAD_SETUP + TEST_CENTRALIZATION)
|
||
- migration-history.md (from 11 past migration docs)
|
||
- Standardise all filenames to lowercase
|
||
- Remove non-doc files (Context.md research notes, chat export)
|
||
- Remove superseded docs (SECURITY.md pre-SQLite, SECURITY_IMPLEMENTATION, README_SECURE_SEARCH)
|
||
- Fix stale cross-references
|
||
|
||
---
|
||
5c5054d74426 | 2026-04-13 11:10
|
||
Investigating VM crash
|
||
|
||
---
|
||
0c29fa21e905 | 2026-04-09 14:37
|
||
Prevent admin nav wrapping to match public header height
|
||
|
||
---
|
||
c5c049eace85 | 2026-04-09 14:37
|
||
Move public search bar below header so admin and public headers have same height
|
||
|
||
---
|
||
1885f2da92c0 | 2026-04-09 14:34
|
||
Replace random HSL gradients in homepage cards with header-style gradient; header keeps its own CSS-variables gradient
|
||
|
||
---
|
||
aa3fc50d927e | 2026-04-09 14:33
|
||
use exact hard-coded gradient on header and card placeholders
|
||
|
||
---
|
||
a13e73aed3e4 | 2026-04-09 14:29
|
||
Replace random HSL gradients in homepage cards with header gradient
|
||
|
||
---
|
||
07f0afde252b | 2026-04-09 14:07
|
||
cache-bust: add filemtime-based versioning to all CSS and JS assets
|
||
|
||
---
|
||
424f79c8197a | 2026-04-08 18:07
|
||
typography: switch display font from Combined to Ductus
|
||
|
||
---
|
||
a333a5fdade0 | 2026-04-08 18:04
|
||
Rebrand: replace PostErg with XAMXAM in admin header link, default title, and OG site_name
|
||
|
||
---
|
||
18045af243b1 | 2026-04-08 18:01
|
||
favicon: replace SVG placeholder with full PNG/ICO set from assets/favicon/
|
||
|
||
---
|
||
d68645f1b108 | 2026-04-08 17:49
|
||
style: retheme system page to site light palette — remove dark --sys-* tokens
|
||
|
||
---
|
||
df414346e939 | 2026-04-08 17:46
|
||
fix: SystemController php-fpm detection — probe phpX.Y-fpm from running PHP version first
|
||
|
||
---
|
||
7117934d07f3 | 2026-04-08 17:42
|
||
fix: replace mb_strlen/mb_substr with strlen/substr — mbstring not available on prod
|
||
|
||
---
|
||
9eec5d3ac04c | 2026-04-08 16:03
|
||
SPECS.md
|
||
|
||
---
|
||
49b113319ac4 | 2026-04-08 15:17
|
||
Add AP filter to admin list; fix reset as unstyled button
|
||
|
||
---
|
||
4199b206db05 | 2026-04-08 15:12
|
||
Move CSV import to inline dialog on list page
|
||
|
||
---
|
||
603af07b68f9 | 2026-04-08 15:06
|
||
Add Paramètres page: consolidate maintenance + account settings
|
||
|
||
---
|
||
ba135f0cb509 | 2026-04-07 15:20
|
||
fix: replace 'Xamxam' with 'Posterg' in public nav header
|
||
|
||
---
|
||
f6977384b97a | 2026-04-07 15:13
|
||
migrate to utopia fluid type and space scales across all CSS
|
||
|
||
---
|
||
ad06bbbcaf14 | 2026-04-07 15:03
|
||
bump all font-size values ~10% across all CSS files
|
||
|
||
---
|
||
0c2276d5ad14 | 2026-04-07 15:01
|
||
Split search into search.php; repertoire.php is index-only
|
||
|
||
---
|
||
e96ec572bef9 | 2026-04-07 14:38
|
||
tfe: hyperlink metadata values to repertoire.php with correct filter params
|
||
|
||
---
|
||
55c6ac21b852 | 2026-04-07 14:37
|
||
fix tfe page: scope common.css header to body>header, fix grid width collapse, remove overflow-y clip
|
||
|
||
---
|
||
3a1cd5b43e92 | 2026-04-07 14:29
|
||
tfe page: author above title, interne/externe jury split, rounded images, strip contact protocol
|
||
|
||
---
|
||
547d581e2663 | 2026-04-07 14:25
|
||
Removed footer navbar
|
||
|
||
---
|
||
11a665e096ca | 2026-04-07 14:21
|
||
Improve À propos page layout: sticky TOC nav, bordered contact rows, credits dl grid
|
||
|
||
---
|
||
dddfc8554be0 | 2026-04-07 14:15
|
||
fonts: add --font-body/--font-display vars, use them everywhere
|
||
|
||
---
|
||
797eaf87d1f1 | 2026-04-07 14:14
|
||
Apply correct fonts: Combined for titles, BBBDMSans for body text
|
||
|
||
---
|
||
572ef75a1e6a | 2026-04-07 13:57
|
||
répertoire: rename search.php, 6-column layout, HTMX filter, faded entries disabled, URL-shareable
|
||
|
||
---
|
||
088324cb804e | 2026-04-06 17:20
|
||
Match Accueil.png mockup: nav layout, full-width search, section label
|
||
|
||
---
|
||
8b27acec2754 | 2026-04-06 17:09
|
||
fix homepage card grid styling: gaps, borders, rounded corners, gradient aspect-ratio
|
||
|
||
---
|
||
0bfb24723f17 | 2026-04-06 16:57
|
||
fix(font): rebuild Combinedd.otf with sorted kern pairs to fix browser discard
|
||
|
||
---
|
||
b8e94f1b6b69 | 2026-04-06 16:54
|
||
fix(css): add format hint + descriptors to @font-face for police1/Combinedd.otf
|
||
|
||
---
|
||
b45e6c50cc55 | 2026-04-06 16:49
|
||
fix: admin CSP allow inline scripts
|
||
|
||
script-src 'self' 'unsafe-inline' added to admin Content-Security-Policy.
|
||
default-src 'self' was blocking OverType editor init block and
|
||
the dev live-reload poller. Admin section is auth-gated so
|
||
unsafe-inline is acceptable.
|
||
|
||
---
|
||
e6960f0c9c04 | 2026-04-06 16:40
|
||
fix: RateLimit permission denied — code + deploy scripts
|
||
|
||
RateLimit.php:
|
||
- Silence mkdir() with @ operator
|
||
- Guard file_put_contents with is_writable() check (graceful degrade)
|
||
|
||
scripts/deploy-server.sh + setup-server.sh:
|
||
- mkdir -p storage/cache/rate_limit on every deploy
|
||
- chown www-data:posterg + chmod 2775 on storage/cache/
|
||
so php-fpm can always write rate limit files
|
||
|
||
---
|
||
756ddb576545 | 2026-04-06 16:39
|
||
fix: RateLimit graceful degradation on permission denied
|
||
|
||
Silence mkdir() with @ operator; guard file_put_contents with
|
||
is_writable() check. When storage/cache/rate_limit is not writable
|
||
by php-fpm, requests are allowed through instead of throwing
|
||
warnings that flood the nginx error log.
|
||
|
||
---
|
||
6a1b41ac9376 | 2026-04-06 16:16
|
||
css: remove dark mode, unify token system, eliminate all hardcoded colors
|
||
|
||
- Remove @media (prefers-color-scheme: dark) block from variables.css
|
||
- Delete colors.css (dead reference doc, never loaded)
|
||
- Add semantic tokens to variables.css:
|
||
--header-gradient-fade, --header-shadow-strong/soft, --header-nav-active-border
|
||
--search-error-bg/border/color
|
||
--sys-bg-surface/deep/panel, --sys-border/border-deep/border-hover
|
||
--sys-text-dim/bright/body, --sys-overlay, --sys-syntax-* (7 highlight tokens)
|
||
--success/warning/error/blue/yellow/green -muted-bg/border/hover alpha overlays
|
||
--danger-border-muted
|
||
- Replace all hardcoded hex/rgba in common.css, main.css, search.css, admin.css, system.css
|
||
- Fix --border-color typo -> --border-primary in search.css
|
||
- Fix view-toggle__btn active color: --text-primary -> --accent-foreground
|
||
- Admin and public share identical token set, no separate admin theme
|
||
- Update README.md
|
||
|
||
---
|
||
e73fcfd0c850 | 2026-04-06 16:11
|
||
fix: drop curl_close() call (deprecated PHP 8.5, no-op since 8.0)
|
||
|
||
---
|
||
9a9dfd2b9e9a | 2026-04-06 15:26
|
||
fix(apropos): replace straight apostrophe in l'ERG role string (parse error)
|
||
|
||
---
|
||
f6e2c77d1a26 | 2026-04-06 15:25
|
||
Adjusting admin static pages edit page
|
||
|
||
---
|
||
480451aa2b76 | 2026-04-06 15:19
|
||
replace EasyMDE (333KB) with OverType (118KB) in pages-edit.php
|
||
|
||
- Remove easymde.min.js (320KB) and easymde.min.css (13KB)
|
||
- Vendor overtype.min.js (118KB, v2.3.5)
|
||
- Replace <textarea name=content> + 60-line toolbar/SVG init with:
|
||
- <input type=hidden name=content> for form submission
|
||
- <div id=editor> as OverType mount target
|
||
- 6-line init: value from hidden input, onChange syncs it back
|
||
- Net saving: ~215KB assets, ~54 lines of inline JS
|
||
|
||
---
|
||
d51cd62088ed | 2026-04-06 15:09
|
||
Extract last inline style from header.php into admin.css
|
||
|
||
The SVG icon in the admin nav's public-site link had two inline styles:
|
||
style="vertical-align:middle;margin-right:0.4em"
|
||
|
||
Moved to a new CSS rule:
|
||
.admin-body header nav > a svg { vertical-align: middle; margin-right: 0.4em; }
|
||
|
||
templates/header.php now contains zero style= attributes.
|
||
The only remaining inline styles project-wide are:
|
||
- dynamic gradient (hsl computed from $item['id']) in public/index.php — legitimately dynamic
|
||
- --disk-pct/--disk-color custom properties in system.php — carry PHP runtime values
|
||
|
||
---
|
||
a60e742d1a20 | 2026-04-06 15:06
|
||
Extract last 3 inline styles from admin templates into CSS classes
|
||
|
||
admin/thanks.php:
|
||
- <div style="margin-top:1.5rem;display:flex;gap:.75rem;flex-wrap:wrap;"> → class="admin-action-bar"
|
||
- <p style="color:var(--text-secondary);"> → class="admin-muted"
|
||
|
||
admin/pages.php:
|
||
- Éditer button style="font-size:.8rem;padding:.3rem .75rem;" → class="admin-btn admin-btn--sm"
|
||
|
||
admin.css (Thesis info sections block):
|
||
- Added .admin-action-bar { margin-top:1.5rem; display:flex; gap:0.75rem; flex-wrap:wrap }
|
||
- Added .admin-muted { color: var(--text-secondary) }
|
||
|
||
The only remaining inline style in any admin PHP file is the dynamic
|
||
--disk-pct/--disk-color custom properties on the disk bar in system.php,
|
||
which carry PHP runtime values and cannot be moved to static CSS.
|
||
|
||
---
|
||
ca8081575cbb | 2026-04-06 14:45
|
||
Add prefers-color-scheme dark mode for public pages
|
||
|
||
Scope: variables.css, search.css, todo/04-accessibility.md
|
||
|
||
- variables.css: add @media (prefers-color-scheme: dark) block scoped to
|
||
body:not(.admin-body); overrides all semantic tokens with dark equivalents:
|
||
--bg-* (#111→#333 range), --text-* (#eee/aaa/777),
|
||
--border-* (#333/#444), --accent-primary lightened to #b87fd4
|
||
(4.5:1 contrast on #111 background), --accent-secondary stays #9557b5,
|
||
--accent-foreground flipped to #111111 for dark buttons,
|
||
--accent-muted adjusted to rgba(184,127,212,0.15),
|
||
status colours muted for dark (success #4db886, error #e05555,
|
||
warning #d4a830); new --search-error-{bg,border,color} tokens added
|
||
to :root (light: #fff0f0/#c00) and overridden in dark (#2a1515/#e05555)
|
||
|
||
- search.css: replace three hardcoded hex values in .search-error rule
|
||
with var(--search-error-bg/border/color) so dark mode applies cleanly
|
||
|
||
- Admin pages are entirely unaffected: .admin-body body class is excluded
|
||
from the dark-mode selector; system.css already has its own dark palette
|
||
|
||
---
|
||
2841e057168a | 2026-04-06 14:37
|
||
Extract ThesisCreateController; add Database publish methods
|
||
|
||
Consolidate action handlers into controller methods (todo/02-php-components.md).
|
||
|
||
src/ThesisCreateController.php (new, 435 lines)
|
||
Mirrors ThesisEditController for the add-thesis flow.
|
||
|
||
make() — factory; instantiates Database via new Database()
|
||
loadFormData() — returns all lookup tables needed by admin/add.php
|
||
(orientations, apPrograms, finalityTypes, languages,
|
||
formatTypes, licenseTypes)
|
||
submit(post, files) — full new-thesis creation pipeline:
|
||
1. validateAndSanitise() — trims/strips HTML, validates required fields,
|
||
year range, orientation/ap/finality IDs, language selection, max-10
|
||
keywords, URL format; throws named Exception on failure
|
||
2. findOrCreateAuthor() — reuses existing DB method
|
||
3. Transaction: createThesis + setThesisJury + setThesisLanguages +
|
||
setThesisFormats + setThesisTags; rolls back on any failure
|
||
4. File uploads outside transaction: cover image (JPG/PNG only, stored in
|
||
storage/covers/), banner via handleBannerUpload(), thesis files
|
||
(PDF/JPG/PNG/MP4/ZIP/VTT, stored in storage/theses/YEAR/IDENT/,
|
||
file_type auto-detected: caption/annex/main/other)
|
||
autofocusFieldForError() — static; maps exception messages to field names
|
||
for WCAG 3.3.1 autofocus on re-render (same contract as
|
||
ThesisEditController::autofocusFieldForError)
|
||
|
||
admin/actions/formulaire.php 346 → 45 lines
|
||
Now: bootstrap + CSRF guard + ThesisCreateController::make()->submit() +
|
||
flash/redirect on error. All validation, DB logic, and file handling removed.
|
||
|
||
admin/add.php
|
||
Lookup-table block (new Database() + 6 individual DB calls) replaced with
|
||
ThesisCreateController::make()->loadFormData() + extract().
|
||
|
||
src/Database.php — two new methods added
|
||
setPublished(int , bool ): void
|
||
UPDATE theses SET is_published = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||
bulkSetPublished(int[] , bool ): void
|
||
Same but with an IN (...) clause for multiple IDs
|
||
|
||
admin/actions/publish.php 100 → 65 lines
|
||
Raw SQL (->prepare('UPDATE theses SET is_published = ?...')) replaced
|
||
with ->setPublished() / ->bulkSetPublished(). No raw PDO calls remain
|
||
in any action handler file.
|
||
|
||
---
|
||
b1e70a2bf12e | 2026-04-06 14:29
|
||
Extract HomeController from public/index.php
|
||
|
||
Move all data-fetching and view-variable assembly out of public/index.php
|
||
into a new src/HomeController.php, following the same pattern as
|
||
SearchController, TfeController, SystemController, and ThesisEditController.
|
||
|
||
HomeController::create() builds the Database singleton dependency.
|
||
HomeController::handle() encapsulates:
|
||
- GET param parsing (page, year) with safe type coercion
|
||
- Display-mode detection: default random-latest view / year-filtered /
|
||
paginated-all theses
|
||
- All DB calls: getLatestPublishedYear, getLatestYearTheses, searchTheses,
|
||
countSearchResults, getPublishedTheses, countPublishedTheses,
|
||
getCoverPathsForTheses, getAvailableYears
|
||
- Batch cover-image loading for theses without a banner_path
|
||
- baseParams assembly for the pagination partial
|
||
- OG / meta tag array construction
|
||
- Graceful error handling (logs exception, returns safe empty state)
|
||
- Returns a flat array of view variables
|
||
|
||
public/index.php is now a 6-line dispatcher (require + create + handle +
|
||
extract) followed by a pure view template. Reduced from 100 to 71 lines.
|
||
All error-handling and data logic removed from the view layer entirely.
|
||
|
||
---
|
||
89067a521fe0 | 2026-04-06 14:25
|
||
Extract TfeController from public/tfe.php
|
||
|
||
src/TfeController.php (new, 195 lines):
|
||
- Dedicated controller for the public TFE detail page
|
||
- create(): Database singleton injection, ready-to-use factory
|
||
- handle(): validates id param (redirect to index.php on missing/invalid/404),
|
||
loads thesis via getThesisById(), fetches access type via getThesisAccessTypeId()
|
||
- buildMetaDescription(): strip_tags + 160-char mb_substr truncation
|
||
- resolveOgImage(): banner_path → first image file → empty string resolution
|
||
- buildOgTags(): full og:type/title/description/url/image/image_alt/site_name +
|
||
article:author / article:published_time assembly
|
||
- collectCaptionPaths(): ordered list of VTT paths for N-th-video pairing
|
||
- returns flat array of all view variables including ogTags, captionFiles,
|
||
pageTitle, metaDescription, isInterdit, bodyClass, extraCss, currentNav
|
||
|
||
public/tfe.php (271 → 206 lines):
|
||
- Reduced to 9-line dispatcher: require TfeController, create(), handle(), extract()
|
||
- $db reference removed from view layer entirely
|
||
- Inline OG tag block (~20 lines) removed
|
||
- Inline meta-description block (~5 lines) removed
|
||
- Inline caption-collection loop (~10 lines) removed
|
||
- $captionFiles replaces $_captionFiles in the video pairing section
|
||
|
||
todo/02-php-components.md:
|
||
- TfeController extraction marked done
|
||
- 'Move OG tag construction into controller logic' marked done
|
||
- Remaining item narrowed to public/index.php home-page controller
|
||
|
||
---
|
||
41629398d321 | 2026-04-05 19:17
|
||
Extract ThesisEditController from admin/edit.php and actions/edit.php
|
||
|
||
src/ThesisEditController.php (285 lines) centralises all data-fetching and
|
||
mutation logic for the thesis-edit workflow:
|
||
|
||
load(int $thesisId): array
|
||
Fetches the thesis row, current language/format/jury selections, and all
|
||
lookup tables (orientations, AP programmes, finality types, languages,
|
||
formats, licences, access types) in one call. Returns a flat view-variable
|
||
array that the dispatcher extracts directly.
|
||
|
||
save(int $thesisId, array $post, array $files): void
|
||
Runs the full edit inside a transaction: thesis metadata, authors, jury,
|
||
languages, formats, tags. Banner upload/removal is handled outside the
|
||
transaction (filesystem op). Rolls back and re-throws on any failure.
|
||
|
||
static autofocusFieldForError(string $msg): ?string
|
||
Centralises the WCAG 3.3.1 exception-message → field-name mapping that
|
||
was previously duplicated inline in actions/edit.php.
|
||
|
||
Dispatcher changes:
|
||
admin/edit.php 191 → 162 lines (pure view + ThesisEditController::create() + load())
|
||
actions/edit.php 153 → 53 lines (CSRF guard + ThesisEditController::save() call)
|
||
|
||
Follows the same pattern as SearchController and SystemController.
|
||
|
||
---
|
||
40cb119448ed | 2026-04-05 17:39
|
||
Extract SystemController: centralise system page data logic, eliminate frag_ helper duplication
|
||
|
||
- Add src/SystemController.php (452 lines) encapsulating:
|
||
- runStatusChecks(): nginx, php-fpm, HTTP ping, SQLite DB, storage, maintenance flag
|
||
- getStatusData() / getPhpInfo() / getDiskInfo() with SystemCache TTL delegation
|
||
- getLogData(tab, n): log file tail reading + file metadata
|
||
- getNginxConfigData(): live-then-local nginx config reading
|
||
- Static helpers: logLineClass(), nginxLineClass(), statusLabel(), statusClass(),
|
||
humanBytes(), diskColor() — shared by both entry points
|
||
- invalidateAll() for ?refresh=1 cache busting
|
||
|
||
- Rewrite admin/system.php: 582 → 282 lines
|
||
- All free functions (safeExec, systemdStatus, localHttpCheck, humanBytes,
|
||
statusLabel, statusClass, logLineClass, nginxLineClass, readLogTail) removed
|
||
- Data sections replaced by controller method calls
|
||
- View template unchanged; now calls SystemController::statusClass() etc. directly
|
||
|
||
- Rewrite admin/system-fragment.php: 213 → 137 lines
|
||
- All duplicated frag_readLogTail(), frag_logLineClass(), frag_nginxLineClass()
|
||
helpers removed
|
||
- Now instantiates SystemController and delegates getLogData()/getNginxConfigData()
|
||
- Identical rendering logic preserved; constant references updated to
|
||
SystemController::LOG_FILES and SystemController::ALLOWED_LINES
|
||
|
||
No behaviour change; no CSS/JS changes.
|
||
|
||
---
|
||
9a58b97cb87c | 2026-04-04 12:39
|
||
Extract SearchController from public/search.php
|
||
|
||
Move all data-fetching and request logic out of the 285-line search page
|
||
into src/SearchController.php:
|
||
|
||
- SearchController::create() — static factory; builds RateLimit + Database
|
||
dependencies, sends HTTP 429 (and exits) if rate limit is exceeded,
|
||
runs probabilistic cleanup, returns ready instance
|
||
- SearchController::handle() — sanitises GET params (query/year/orientation/
|
||
ap_program/keyword), runs all DB queries (searchTheses, countSearchResults,
|
||
getAvailableYears, getAllOrientations, getAllAPPrograms, getUsedTags,
|
||
getPublishedAuthors), builds alphabetical author→id map, assembles
|
||
OG/meta tags, returns a flat array of view variables
|
||
- Rate-limit 429 HTML response moved into private sendRateLimitResponse()
|
||
|
||
public/search.php is now a 6-line dispatcher:
|
||
require SearchController; extract(SearchController::create()->handle());
|
||
followed by the unchanged view template (162 lines total, was 285).
|
||
|
||
The view template is byte-for-byte equivalent: same HTML, same variable
|
||
names, same pagination partial include.
|
||
|
||
---
|
||
c3a02e0aaa42 | 2026-04-04 12:36
|
||
system.php: extract inline JS and style= attrs into separate assets
|
||
|
||
Move the ~130-line $extraJsInline heredoc from admin/system.php into a
|
||
static file public/assets/js/system.js, loaded via $extraJs so the
|
||
template footer emits a normal <script src=…>.
|
||
|
||
Replace 4 inline style= attributes with named CSS modifier classes in
|
||
system.css:
|
||
- style="margin:0;border:none;padding:0" on .srv-section-title
|
||
→ .srv-section-title--compact
|
||
- style="margin-bottom:.75rem" on sub-heading <h3>
|
||
→ .srv-section-title--sub
|
||
- style="margin-bottom:0" on .php-grid
|
||
→ .php-grid--flush
|
||
- style="font-size:.84rem;color:var(--text-secondary)" on <label>
|
||
→ .log-toolbar label rule in system.css
|
||
|
||
The one remaining inline style (--disk-pct / --disk-color CSS custom
|
||
properties on .disk-bar) is intentionally kept: it carries PHP runtime
|
||
values that cannot be expressed in a static stylesheet.
|
||
|
||
---
|
||
9637114f6ba5 | 2026-04-04 12:31
|
||
Clean up flash key legacy code and extract import.php inline styles
|
||
|
||
App::consumeFlash() had 18-line legacy fallback chains reading from seven old
|
||
session keys (error, admin_error, edit_error, form_error, success,
|
||
admin_success, edit_success) that were written by no code in the codebase.
|
||
All action handlers have used App::flash() -> _flash_error / _flash_success
|
||
since the App class was introduced. Removed the dead fallbacks; consumeFlash()
|
||
is now 4 lines.
|
||
|
||
admin/import.php was the last admin template with inline style= attributes.
|
||
Extracted four of them to named CSS classes in admin.css:
|
||
- admin-error-list — error <ul> spacing (was style="margin:.5rem 0 0;padding-left:1.2rem")
|
||
- admin-file-hint — <small> display + margin (was style="margin-top:.5rem")
|
||
- admin-import-results — results panel margin (was style="margin-top:2rem")
|
||
- admin-import-results__title — <h2> typography (was multi-property inline style)
|
||
|
||
Closes the 'unify flash message keys' item in todo/02-php-components.md and
|
||
the import.php inline style item in todo/01-css-semantic-refactor.md.
|
||
|
||
---
|
||
c2eff757897b | 2026-04-04 12:23
|
||
WCAG 3.3.1: autofocus first invalid field on add/edit form validation failure
|
||
|
||
Add App::flashAutofocus(fieldName) and consumeAutofocus() to the thin App
|
||
helper so action handlers can identify which field caused a validation error
|
||
and the form page can move browser focus directly to it on reload.
|
||
|
||
Changes:
|
||
- src/App.php — flashAutofocus() stores field name in _flash_autofocus
|
||
session key; consumeAutofocus() drains it and returns the name (or null)
|
||
- actions/formulaire.php — catch block maps exception messages to field
|
||
names (auteurice, titre, synopsis, année, orientation, ap, finality,
|
||
languages, tag, lien) and calls App::flashAutofocus()
|
||
- actions/edit.php — catch block maps common edit errors to field names
|
||
and calls App::flashAutofocus()
|
||
- add.php — consumes the hint via App::consumeAutofocus() into
|
||
$autofocusField; withAutofocus() helper merges autofocus=>true into
|
||
$attrs for every field include; synopsis textarea gets inline autofocus
|
||
- edit.php — same pattern with inline ternary merges and textarea autofocus
|
||
- templates/partials/form/text-field.php — $attrs loop now emits bare
|
||
attribute names (no ="...") when value === true, supporting autofocus,
|
||
disabled, readonly etc. without special-casing
|
||
- templates/partials/form/select-field.php — same boolean-attr support
|
||
added; $attrs variable initialised to [] when caller omits it
|
||
|
||
Closes WCAG 3.3.1 autofocus item in todo/04-accessibility.md.
|
||
|
||
---
|
||
4c3f71b6e4c7 | 2026-04-04 12:05
|
||
Extract apropos contacts/credits to config/apropos.php
|
||
|
||
Names, roles, emails, and credits on the À propos page were hardcoded
|
||
directly in apropos.php HTML. To update a contact meant editing a
|
||
template file — risky for non-developers and easy to introduce a typo
|
||
or broken mailto link.
|
||
|
||
Changes:
|
||
- config/apropos.php: new config array with erg_url, contacts[] (name,
|
||
role, email per person) and credits[] (label/value pairs); follows
|
||
the same pattern as config/admin_credentials.php
|
||
- public/apropos.php: loads config via require; aside section now loops
|
||
over $apropos['contacts'] and $apropos['credits'] with htmlspecialchars
|
||
throughout; hardcoded HTML strings removed entirely
|
||
|
||
Also audited todo/02-php-components.md and marked 8 stale items as done:
|
||
all 5 form field partials were already implemented and in use, the
|
||
flash-message consolidation was already handled by App::consumeFlash(),
|
||
and the RateLimit cache dir was already at storage/cache/rate_limit
|
||
(excluded from deploy rsync).
|
||
|
||
---
|
||
94e9060dc72c | 2026-04-03 13:24
|
||
WCAG 4.1.2: add WebVTT caption support for <video> elements on tfe.php
|
||
|
||
Problem: <video> elements on tfe.php had no <track kind="captions"> element,
|
||
violating WCAG 4.1.2 (name, role, value) for video content.
|
||
|
||
Changes:
|
||
- public/tfe.php: collect all text/vtt files from the thesis file list before
|
||
rendering; skip standalone rendering of .vtt entries; for each MP4 emit a
|
||
<track kind="captions" srclang="fr" label="Sous-titres" default> pointing
|
||
to the N-th VTT file (N-th video paired with N-th caption in document order)
|
||
- public/media.php: add text/vtt to allowed MIME list; normalise finfo
|
||
text/plain -> text/vtt for .vtt files; add vtt branch to cache/header
|
||
block (Content-Type: text/vtt; charset=utf-8, 1-day cache)
|
||
- public/admin/actions/formulaire.php: allow .vtt uploads (text/vtt MIME,
|
||
vtt extension); normalise text/plain finfo result; set file_type='caption'
|
||
for VTT files so they are distinguishable from other thesis files
|
||
- public/admin/add.php: extend files field accept attr to include .vtt;
|
||
update hint text to document the VTT sidecar convention
|
||
|
||
VTT files uploaded under theses/ inherit the same access_type visibility
|
||
gate in media.php as all other thesis content (403 for access_type_id=3).
|
||
|
||
---
|
||
6e68edfbff02 | 2026-04-03 13:14
|
||
Fix WCAG 4.1.2 truncated select text + split admin/public favicons
|
||
|
||
- admin/edit.php: remove mb_strimwidth(60) truncation from access_type
|
||
<select> option labels; full 'name — description' text is now the
|
||
accessible name so screen readers get unambiguous option text (WCAG 4.1.2)
|
||
|
||
- public/assets/favicon.svg: new public favicon — brand-purple (#9557b5)
|
||
rounded square with white 'P' lettermark; distinct from admin_favicon.svg
|
||
(archive-restore Lucide icon in #c104fc) which is admin-only
|
||
|
||
- templates/head.php: favicon <link> now conditionally serves favicon.svg
|
||
(public pages) or admin_favicon.svg (admin pages) based on $isAdmin;
|
||
closes the open favicon task in todo/01-css-semantic-refactor.md
|
||
|
||
- todo/04-accessibility.md: mark WCAG 3.1.1 lang audit and WCAG 4.1.2
|
||
select truncation items as done
|
||
- todo/01-css-semantic-refactor.md: mark favicon task as done
|
||
|
||
---
|
||
d9f94eeb13d5 | 2026-04-03 13:10
|
||
a11y(jury-fieldset): fix WCAG 3.3.2, 4.1.2, 2.1.1 + audit 1.4.4/1.4.12
|
||
|
||
3.3.2 Labels or instructions
|
||
- Replace bare <label>Lecteur·ices :</label> (no 'for', no associated control)
|
||
with <fieldset class="admin-jury-lecteurs"><legend>Lecteur·ices</legend>
|
||
giving AT a proper programmatic label for the entire lecteur group
|
||
|
||
4.1.2 Name, role, value — Externe checkboxes lacked group context
|
||
- Add aria-label="Promoteur·ice — externe" on the promoteur Externe checkbox
|
||
- Add aria-label="Lecteur·ice N — nom" on every lecteur name input
|
||
- Add aria-label="Lecteur·ice N — externe" on every lecteur Externe checkbox
|
||
- All three attributes added to both PHP-rendered rows and the addJuryRow() JS
|
||
that builds new rows dynamically
|
||
|
||
2.1.1 Keyboard — remove buttons already had aria-label; verified and updated
|
||
label text to "Supprimer le lecteur·ice N" (consistent with new numbering)
|
||
|
||
CSS (admin.css)
|
||
- Add .admin-body fieldset fieldset.admin-jury-lecteurs rule: removes
|
||
border/padding/background from the nested fieldset so it reads as a
|
||
sub-group inside the outer jury fieldset, not a double-bordered card
|
||
|
||
Audit (no code change)
|
||
- WCAG 1.4.4: all font-size values use rem — no px text sizing anywhere
|
||
- WCAG 1.4.12: only overflow:hidden on media containers and .sr-only utility;
|
||
no essential text content is clipped by text-spacing overrides
|
||
- WCAG 4.1.2 bulk JS: result is a redirect to flash-messages.php which already
|
||
emits role="alert"/role="status" — no additional JS announcement needed
|
||
|
||
---
|
||
769d56fabcca | 2026-04-03 13:05
|
||
wcag: fix 2.4.4 duplicate link text on home page cards
|
||
|
||
Add <span class="sr-only">, YEAR</span> to each thesis card <p> in
|
||
public/index.php. Screen readers now read "Author – Title, 2024" instead
|
||
of bare "Author – Title", so two theses sharing the same title produce
|
||
distinct accessible names (WCAG 2.4.4 Link Purpose — In Context).
|
||
|
||
Also audit and close WCAG 2.4.3: the tfe.php back link (<a class="tfe-back-link">
|
||
← Retour</a>) is already the first child of <header class="tfe-left">
|
||
in DOM order, preceding <h1 class="tfe-title">. No code change needed;
|
||
TODO item marked done.
|
||
|
||
---
|
||
fe1f8629ea38 | 2026-04-03 12:54
|
||
rename admin-submit-wrap → admin-form-footer across all templates and CSS
|
||
|
||
- Updated 6 admin templates: add.php, edit.php, login.php, account.php,
|
||
import.php, pages-edit.php — replaced <div class="admin-submit-wrap">
|
||
with <div class="admin-form-footer">
|
||
- Updated 8 CSS selectors in admin.css:
|
||
- .admin-form-footer { margin-top/padding-top } (was .admin-submit-wrap)
|
||
- .admin-form > div:not(.admin-form-footer) grid exclusion guard (×3)
|
||
- .admin-login-box .admin-form > div:not(.admin-form-footer) overrides (×2)
|
||
- .admin-login-box .admin-form-footer compact spacing override
|
||
- No visual change; purely a semantic rename to a descriptive class name
|
||
- Also marked status-badge.php partial and WCAG 1.3.1 badge tasks as
|
||
already-done in todo/02-php-components.md and todo/04-accessibility.md
|
||
(partial + CSS were fully implemented but todo had not been updated)
|
||
|
||
---
|
||
62eee63f80f9 | 2026-04-03 12:35
|
||
fix(admin): add aria-current nav indicator + fix undefined --admin-purple variable
|
||
|
||
WCAG 1.4.1 — Active nav link had no non-colour indicator in the admin panel.
|
||
Public nav already had border-bottom via common.css; admin nav had nothing.
|
||
|
||
admin.css:
|
||
- Add `[aria-current="page"]` rule on admin nav links:
|
||
border-bottom: 2px solid currentColor; padding-bottom: 1px
|
||
This gives a visible underline as a non-colour signal for the active page.
|
||
|
||
- Fix `--admin-purple` undefined CSS variable in pagination button hover.
|
||
The variable was referenced but never defined in variables.css (which was
|
||
refactored to use --accent-primary / --accent-secondary). Replaced both
|
||
border-color and color usages with var(--accent-primary) (#9557b5 — same
|
||
value), restoring the intended purple hover tint on pagination buttons.
|
||
|
||
todo/01-css-semantic-refactor.md:
|
||
- Audited ~15 pending CSS/HTML tasks; all were already implemented.
|
||
Marked as done: .admin-main, .admin-page-title, .admin-form-row,
|
||
.admin-label, .admin-input/select/textarea, .admin-table, .admin-fieldset,
|
||
tfe.css class replacements, search.css h2 selector, admin-alert replacement,
|
||
login.php/edit.php inline style removal, form partial hints (<small>).
|
||
|
||
todo/04-accessibility.md:
|
||
- Marked WCAG 1.4.1 admin nav and --admin-purple audit items as completed.
|
||
|
||
---
|
||
234d7bae4054 | 2026-04-03 12:29
|
||
admin/index.php: add server-side pagination (25/page)
|
||
|
||
- Add Database::getThesesListCount(array $filters) — runs the same WHERE
|
||
clauses as getThesesList() but with COUNT(DISTINCT t.id); used to compute
|
||
total pages without loading all rows.
|
||
- Extend Database::getThesesList() with $limit/$offset parameters; when
|
||
$limit > 0 appends LIMIT/OFFSET and re-binds positional params individually
|
||
to avoid the PDO mixed-style restriction.
|
||
- Fix getThesesList() SELECT: add LEFT JOIN access_types + at.name as
|
||
access_type — the column was referenced in the template but never fetched.
|
||
- Wire admin/index.php: read ?page=, compute $totalPages/$offset, pass
|
||
$perPage=25 + $offset to getThesesList(); include pagination.php partial
|
||
below the table with filter-preserving $baseParams.
|
||
- Add result-count line (<p class="admin-list-meta">) showing "X–Y sur Z TFE"
|
||
when multiple pages exist.
|
||
- Add .admin-body .pagination-wrap / .pagination-btn / .pagination-info styles
|
||
to admin.css (scoped to .admin-body to avoid colliding with public pages).
|
||
|
||
---
|
||
ff8e33727ded | 2026-04-02 21:06
|
||
admin: semantic HTML pass — checkbox fieldset, landmarks, dl/dt, autocomplete, inline styles
|
||
|
||
checkbox-list.php partial:
|
||
- Replace outer <div>/<label> with <div>/<span class="admin-row-label"> + inner
|
||
<fieldset class="admin-checkbox-group"><legend class="sr-only"> to satisfy
|
||
WCAG 1.3.1 (group label for multi-checkbox rows without duplicating visible text)
|
||
- Replace <div class="admin-checkbox-list"> with <ul>; each checkbox wrapped in <li>
|
||
|
||
admin.css:
|
||
- Drop .admin-checkbox-list; add .admin-body fieldset.admin-checkbox-group rules
|
||
(border/padding reset so it doesn't inherit jury-fieldset box styling)
|
||
- Extend form-row label rule to span.admin-row-label
|
||
- .admin-inline-form + .admin-inline-form { margin-top:.35rem } replaces inline style
|
||
- .admin-input--inline / .admin-select--inline get width:160px (was inline style)
|
||
- .admin-tags-count + table th sizing via :has() replaces th inline styles
|
||
|
||
login.php: wrap content in <main id="main-content"> (missing landmark)
|
||
|
||
account.php:
|
||
- <div class="admin-account-status"> → <dl>; __label <span> → <dt>
|
||
- <div class="admin-danger-zone__description"> → <p>
|
||
|
||
index.php: <div class="admin-maintenance-bar"> → <aside role="status" aria-label="Statut du site">
|
||
|
||
add.php / edit.php: autocomplete="name" on author field, autocomplete="email" on
|
||
contact field (WCAG 1.3.5 / input purpose)
|
||
|
||
tags.php: all inline style= attributes removed (width, text-align, margin-top,
|
||
display:inline); all moved to CSS classes
|
||
|
||
---
|
||
fde05da49381 | 2026-04-02 18:57
|
||
common.css: taller header, larger nav text (header height -10%)
|
||
|
||
---
|
||
b2ec15372c96 | 2026-04-02 18:57
|
||
common.css: taller header, larger nav text
|
||
|
||
- header nav padding 1rem → 1.6rem (top/bottom)
|
||
- logo link font-size 0.95rem → 1.1rem
|
||
- nav link font-size 0.85rem → 0.95rem
|
||
|
||
---
|
||
da6d06f65a21 | 2026-04-02 18:56
|
||
common.css: header text-shadow — purple glow, 0 offset, 12px blur, 65% opacity
|
||
|
||
---
|
||
c68e355de7b0 | 2026-04-02 18:55
|
||
common.css: add subtle text-shadow to header nav text
|
||
|
||
0 offset, 8px blur, rgba(0,0,0,0.35) — no directional shadow, just a
|
||
soft glow that improves legibility of white text against the lighter
|
||
portions of the gradient header.
|
||
|
||
---
|
||
d85fb22cfc4b | 2026-04-02 18:54
|
||
admin: center main content with margin-inline: auto
|
||
|
||
---
|
||
f18e3381ea0c | 2026-04-02 18:49
|
||
admin.css: rewrite from scratch using only variables.css tokens
|
||
|
||
The file had accumulated severe corruption in its lower half (garbled
|
||
selector text, variable names spliced into property values, orphaned
|
||
declarations, broken nesting) alongside hardcoded hex colours throughout.
|
||
|
||
Rewrote the entire file cleanly:
|
||
- Every colour is now a var() referencing a token defined in variables.css:
|
||
--accent-primary/secondary/foreground, --accent-blue/green/yellow/red,
|
||
--bg-secondary/tertiary, --border-primary, --text-primary/secondary/tertiary,
|
||
--error, --warning, --success, --accent-muted.
|
||
- Zero raw hex values remain in admin.css.
|
||
- Removed the corrupted/dead CSS from the bottom half and reconstructed
|
||
all selectors from what the templates actually use (audited via grep).
|
||
- Fixed structural issues: broken border shorthand, nested rules that
|
||
were not valid CSS, orphaned declaration blocks.
|
||
- New/restored rules: .admin-maintenance-bar (was corrupted),
|
||
.status-access variants (was corrupted), .admin-section-title--danger,
|
||
.admin-danger-zone, .admin-account-status (all reconstructed cleanly).
|
||
- .admin-btn--warning and .admin-btn--danger now use var(--accent-yellow)
|
||
and var(--accent-red) instead of hardcoded dark hex values.
|
||
- .admin-btn-remove hover now uses var(--error) instead of #e55.
|
||
- .admin-btn-unpublish now uses var(--bg-secondary)/var(--text-tertiary)
|
||
instead of hardcoded grey hex values.
|
||
- select option background colours removed (browser chrome, not styleable
|
||
cross-platform).
|
||
|
||
Templates: replace 4 inline var(--admin-text-muted) with var(--text-secondary)
|
||
in index.php, thanks.php, import.php.
|
||
|
||
---
|
||
871e919efaca | 2026-04-02 18:45
|
||
system.css: use only variables.css tokens, remove undefined custom properties
|
||
|
||
Replace the two undefined variables that had crept in:
|
||
- var(--admin-border) → #555 (in .log-output border)
|
||
- var(--admin-text-muted) → #969696 (inline style on log toolbar label,
|
||
in both system.php and system-fragment.php)
|
||
|
||
Revert the incorrect intermediate attempt that mapped dark-UI hex values
|
||
to light-theme tokens (--bg-primary: #fff, --border-primary: #ddd, etc.)
|
||
and also revert the .admin-body override block that was added to
|
||
variables.css — variables.css is shared and must not have per-component
|
||
overrides.
|
||
|
||
All remaining var() calls in system.css now reference tokens that exist
|
||
in variables.css:
|
||
--accent-primary, --accent-green, --error, --warning, --success,
|
||
--text-tertiary
|
||
The dark surface colours (#1a1a1a, #242424, #0d0d0d, #555, #969696, etc.)
|
||
stay as literal hex values, consistent with how admin.css handles them.
|
||
|
||
---
|
||
b981223ff4a1 | 2026-04-02 18:39
|
||
admin/system: fetch()-based tab switching, no full-page reload
|
||
|
||
Add system-fragment.php — a thin authenticated endpoint that returns only
|
||
the tab-panel HTML (toolbar + meta + log/nginx-config output) for a given
|
||
?tab=&n= combination. No page shell, no status section, no DB queries.
|
||
|
||
system.php changes:
|
||
- Tab <a> elements gain data-tab= attributes used by JS to identify the
|
||
target without parsing hrefs.
|
||
- Tab panel content wrapped in <div id=sys-tab-panel data-tab= data-n=>
|
||
which JS uses as both the swap target and its own state store.
|
||
- JS rewritten: tab clicks and lines-select changes call loadPanel()
|
||
which fetch()es system-fragment.php, swaps innerHTML, updates active
|
||
tab ARIA attributes, and pushes state via history.pushState.
|
||
- Browser back/forward handled via popstate listener.
|
||
- bindPanelControls() re-wires the lines-select and copy-to-clipboard
|
||
button after every innerHTML swap (event delegation not feasible here
|
||
because log-output is replaced wholesale).
|
||
- fetch() failure falls back to window.location.href (full page load).
|
||
- Tabs without JS still work: <a href> links go to system.php?tab=…
|
||
as before.
|
||
|
||
system-fragment.php:
|
||
- Requires AdminAuth::isAuthenticated(); returns 403 on failure.
|
||
- Validates tab and n params against the same whitelist as system.php.
|
||
- All helper functions namespaced with frag_ prefix to avoid redeclaration
|
||
if PHP ever includes both files in the same process.
|
||
- Renders identical HTML to the corresponding section in system.php.
|
||
|
||
system.css:
|
||
- #sys-tab-panel gets min-height:8rem and position:relative to prevent
|
||
layout jump during fetch.
|
||
- .sys-panel-loading: opacity 0.4 + pointer-events:none + subtle
|
||
diagonal-stripe ::after overlay with shimmer animation.
|
||
|
||
---
|
||
c86781b9be5b | 2026-04-02 18:31
|
||
admin/system: move status panel above tabs, add collapse toggle
|
||
|
||
Status (services, PHP env, disk) is now always visible above the log/config
|
||
tab bar rather than being one of the tab targets:
|
||
|
||
- Status section rendered unconditionally above <nav class="sys-tabs">.
|
||
- Services grid, PHP info grid and disk bar grouped inside a collapsible
|
||
<section> with a header row containing the cache-freshness badge and a
|
||
toggle button (▲ Réduire / ▼ Développer).
|
||
- Collapse state persisted in localStorage so the preference survives
|
||
page reloads (e.g. when switching log tabs).
|
||
- Tab bar now only contains the three log tabs + nginx config; the 'Statut'
|
||
tab is removed. Legacy ?tab=status URLs fall through to nginx_access.
|
||
- PHP/disk sub-sections laid out in a 2-col grid inside the status panel;
|
||
responsive single-col below 700px.
|
||
- system.css: new .sys-status-section / .sys-status-header /
|
||
.sys-status-toggle / .sys-status-meta rules added.
|
||
- aria-current="page" added to active tab links.
|
||
- todo/03-system-cache.md: all items marked done; notes added explaining
|
||
why log caching was deliberately omitted.
|
||
|
||
---
|
||
e1ce90011399 | 2026-04-02 18:17
|
||
a11y: WCAG 2.5.5 target sizes + 2.5.3 label-in-name fixes
|
||
|
||
Increase touch/click target sizes to meet WCAG 2.5.5 (minimum 44×44px
|
||
for navigation, 32px for admin UI controls):
|
||
|
||
- main.css / search.css: pagination buttons 2rem → min-height/min-width
|
||
2.75rem (44px). Changed display to inline-flex for proper centering.
|
||
- admin.css: .admin-btn-sm gains min-height: 2rem (32px) and switches
|
||
to inline-flex so the constraint is respected.
|
||
- admin.css: .admin-btn-remove (jury ✕ buttons) gains min-height: 2rem
|
||
and inline-flex display + explicit cursor:pointer.
|
||
|
||
WCAG 2.5.3 label-in-name — jury remove buttons already had aria-label;
|
||
wrap the visible ✕ glyph in <span aria-hidden='true'> so screen readers
|
||
hear only the aria-label, not the symbol:
|
||
|
||
- templates/partials/form/jury-fieldset.php: all three ✕ occurrences
|
||
(static PHP blocks + JS-generated innerHTML string) wrapped.
|
||
|
||
WCAG 4.1.2 / semantic HTML:
|
||
- admin/index.php: add role='toolbar' aria-label='Actions groupées' to
|
||
the bulk-actions bar.
|
||
|
||
---
|
||
ba367251117a | 2026-04-02 18:08
|
||
Split TODO.md into todo/ folder by topic (completed tasks removed)
|
||
|
||
---
|
||
d78befe62283 | 2026-04-02 18:01
|
||
restore TODO.md: recover full historical TODO from 9108c4069d82, append CSS color variables section (2026-04-02)
|
||
|
||
---
|
||
7e0ac45a659b | 2026-04-02 17:42
|
||
Changed colors to the shared colors
|
||
|
||
---
|
||
758bdce669b4 | 2026-04-02 17:24
|
||
refactor: unify CSS color variables across public and admin
|
||
|
||
- Add new standardized color variables in variables.css:
|
||
- Public/light theme: --bg-primary, --bg-secondary, etc.
|
||
- Admin/dark theme: --admin-bg, --admin-bg-alt, --admin-text, etc.
|
||
- Gradient colors: --gradient-start (#3C856C), --gradient-2 (#60ECB4), --gradient-3 (#E390FF), --gradient-4 (#9557B5)
|
||
- Shared: --success, --error, --warning, --accent-primary, --accent-secondary
|
||
- Update all CSS files to use new variables
|
||
- Keep admin-specific variables for dark theme sections
|
||
|
||
---
|
||
ae499e45b585 | 2026-04-02 17:23
|
||
refactor: unify CSS color variables across public and admin
|
||
|
||
- Add new standardized color variables in variables.css:
|
||
- Public/light theme: --bg-primary, --bg-secondary, etc.
|
||
- Admin/dark theme: --admin-bg, --admin-bg-alt, --admin-text, etc.
|
||
- Gradient colors: --gradient-start (#3C856C), --gradient-2 (#60ECB4), --gradient-3 (#E390FF), --gradient-4 (#9557B5)
|
||
- Shared: --success, --error, --warning, --accent-primary, --accent-secondary
|
||
- Update all CSS files to use new variables
|
||
- Keep admin-specific variables for dark theme sections
|
||
|
||
---
|
||
f7babf9e964b | 2026-04-02 17:22
|
||
refactor: unify CSS color variables across public and admin
|
||
|
||
- Add new standardized color variables in variables.css:
|
||
- Public/light theme: --bg-primary, --bg-secondary, etc.
|
||
- Admin/dark theme: --admin-bg, --admin-bg-alt, --admin-text, etc.
|
||
- Gradient colors: --gradient-start (#3C856C), --gradient-2 (#60ECB4), --gradient-3 (#E390FF), --gradient-4 (#9557B5)
|
||
- Shared: --success, --error, --warning, --accent-primary, --accent-secondary
|
||
- Update all CSS files to use new variables
|
||
- Keep admin-specific variables for dark theme sections
|
||
|
||
---
|
||
bf2594112bf6 | 2026-04-02 14:11
|
||
fix serve recipe: filter output to Development Server start + [200] requests only
|
||
|
||
refactor: unify CSS color variables across public and admin
|
||
|
||
- Replace old variable structure with new standardized naming:
|
||
- Background: --bg-primary, --bg-secondary, --bg-tertiary, --bg-active
|
||
- Text: --text-primary, --text-secondary, --text-tertiary
|
||
- Border: --border-primary, --border-secondary
|
||
- Status: --success, --error, --warning
|
||
- Accent: --accent-primary, --accent-secondary, --accent-foreground, --accent-muted
|
||
- Remove admin-specific color variables (--admin-*)
|
||
- Update all CSS files to use shared variables:
|
||
- variables.css, common.css, main.css, admin.css
|
||
- tfe.css, search.css, apropos.css, system.css, colors.css
|
||
|
||
---
|
||
ba7814c6dc13 | 2026-04-02 13:04
|
||
feat: system page caching via SystemCache + system_cache SQLite table
|
||
|
||
Add a TTL-based cache for the expensive checks on the admin system page,
|
||
eliminating repeated systemctl subprocess calls (~4×~100ms), curl self-pings
|
||
(~200-500ms), disk_*_space() and PHP ini reads on every page load.
|
||
|
||
Changes:
|
||
- storage/migrations/007_system_cache.sql: new migration creating the
|
||
system_cache table (key TEXT PK, value TEXT, updated_at INTEGER)
|
||
- storage/schema.sql: system_cache table added before pages table
|
||
- Applied migration to live storage/posterg.db
|
||
- src/SystemCache.php: new class with get/set/isStale/ageSeconds/invalidate;
|
||
uses SQLite INSERT … ON CONFLICT upsert; no external dependencies
|
||
- src/Database.php: added getDatabasePath(): string accessor
|
||
- public/admin/system.php:
|
||
- Bootstrap SystemCache at request start using the existing DB PDO handle
|
||
- system_status: cached with 2-min TTL (systemctl + curl checks)
|
||
- php_info: cached with 1-hour TTL (PHP ini values are runtime-constant)
|
||
- disk_info: cached with 5-min TTL (total/free/used/pct tuple)
|
||
- Logs section: unchanged — always reads live log tail per active tab
|
||
- ?refresh=1 GET param invalidates all three cache keys before rendering
|
||
- Status panel heading shows cache badge: '⚡ Cache — il y a Xs' (hit)
|
||
or '⟳ Actualisé' (miss/fresh), styled via new .sys-cache-badge rules
|
||
- public/assets/css/system.css: .sys-cache-badge / --hit / --miss styles
|
||
|
||
---
|
||
592b1183db86 | 2026-04-02 12:57
|
||
Unify flash messages: replace all legacy session key writes with App::flash()
|
||
|
||
All admin action files (account, tag, page, edit, visibility, maintenance,
|
||
publish, formulaire) now call App::flash('error'|'success', ...) instead of
|
||
writing to raw per-page session keys ($_SESSION['error'], 'admin_error',
|
||
'edit_error', 'admin_success', 'edit_success', 'form_error').
|
||
|
||
All admin display pages (add, edit, account, tags, pages, index) now include
|
||
templates/partials/flash-messages.php instead of manually reading and
|
||
unsetting the legacy session keys and inlining their own alert HTML.
|
||
|
||
App::consumeFlash() already drained all legacy key variants as a safety net,
|
||
so the partial works correctly whether called from pages that were already
|
||
migrated or any remaining stragglers. No behaviour change for end users.
|
||
|
||
---
|
||
77bfd2f8e343 | 2026-04-02 12:50
|
||
Extract status-badge.php partial; replace inline badge markup in index.php and account.php
|
||
|
||
Add templates/partials/status-badge.php — a single reusable partial that
|
||
renders the <span class="status-badge …"> element for three badge types:
|
||
|
||
'publish' — Publié / En attente derived from a boolean is_published value
|
||
'access' — access-type label (Libre / Interne / Interdit) with slug-based
|
||
CSS modifier class and appropriate symbol (○ ◑ ●)
|
||
'ok' — generic green/yellow boolean badge with caller-supplied labels
|
||
(used for 'Active'/'Non configurée' and 'Présent'/'Absent' in
|
||
account.php)
|
||
|
||
All three variants emit aria-label with a context prefix and wrap the
|
||
decorative symbol in aria-hidden="true" — behaviour identical to the
|
||
inline code they replace.
|
||
|
||
Callers set $badgeType + $badgeValue (+ optional $badgeOkLabel /
|
||
$badgeWarnLabel / $badgeContext) before the include; the partial unsets
|
||
all working variables after rendering so they do not bleed into the
|
||
including scope.
|
||
|
||
Files changed:
|
||
templates/partials/status-badge.php — new partial
|
||
public/admin/index.php — table status column now uses partial
|
||
(removes 15 lines of inline if/else/php)
|
||
public/admin/account.php — two credential status rows now use partial
|
||
(removes 8 lines of inline if/else)
|
||
|
||
---
|
||
2143869b1e8a | 2026-04-02 12:48
|
||
Add admin form field partials and apply to add/edit forms
|
||
|
||
Four reusable PHP partials extracted to templates/partials/form/:
|
||
|
||
- text-field.php — single-line input (text/number/url); wraps input+hint in div,
|
||
skips the inner wrapper when no hint is present. Supports $type,
|
||
$placeholder, $required, $attrs, $hint, $id overrides.
|
||
- select-field.php — <select> with leading empty option; matches $selected against
|
||
option id OR option name string (handles view-sourced data where
|
||
orientation/ap/finality come back as name strings, not FK ids).
|
||
- checkbox-list.php — checkbox group (languages, formats); renders .admin-checkbox-list
|
||
with typed-string comparison so int ids from DB match string values.
|
||
- file-field.php — file input with accept/multiple/hint; appends [] to name when
|
||
$multiple is true.
|
||
|
||
Both add.php and edit.php rewritten to use the partials:
|
||
- ~15 repeated text-field divs collapsed to single-line include calls
|
||
- ~6 repeated select divs collapsed to single-line include calls
|
||
- 4 checkbox-list blocks collapsed to 2 calls each
|
||
- 3 file input blocks collapsed to single-line include calls
|
||
- Textarea fields (synopsis, context_note) kept inline — no partial for <textarea>
|
||
- Banner preview block in edit.php kept inline — conditional UI not generalised
|
||
|
||
Line count: add.php 251→93 (-158), edit.php 289→171 (-118)
|
||
|
||
---
|
||
c8a3cc0ff208 | 2026-04-02 12:42
|
||
css: replace admin-form-row/admin-label/admin-input/select/textarea classes with semantic selectors
|
||
|
||
Remove five presentational classes from admin forms and replace with
|
||
structural CSS selectors scoped to .admin-form:
|
||
|
||
- .admin-form-row → .admin-form > div:not(.admin-submit-wrap)
|
||
Grid layout (260px label col + 1fr input col) applied directly to div
|
||
children of the form; submit-wrap div excluded via :not().
|
||
|
||
- .admin-label → .admin-form > div:not(.admin-submit-wrap) > label
|
||
Scoped to the direct label child of each form row div; does not bleed
|
||
into nested checkbox labels inside .admin-checkbox-list.
|
||
|
||
- .admin-input / .admin-select / .admin-textarea
|
||
→ .admin-form input:not([type=checkbox|radio|file|hidden|submit])
|
||
→ .admin-form select
|
||
→ .admin-form textarea
|
||
Also extended to .admin-inline-form input/select (tags page) so the
|
||
tags table inputs retain identical base styling and focus colour.
|
||
|
||
Templates updated: add.php, edit.php, login.php, account.php,
|
||
pages-edit.php, import.php, tags.php,
|
||
templates/partials/form/jury-fieldset.php — all class= attributes for
|
||
the five removed classes stripped.
|
||
|
||
import.php: added 'admin-form' class alongside 'admin-import-area' so
|
||
its single file-input row gets the grid row treatment; submit div was
|
||
already using admin-submit-wrap so it is correctly excluded.
|
||
|
||
No visual change — selectors target the same elements as before.
|
||
|
||
---
|
||
e9e012376de5 | 2026-04-02 12:35
|
||
Replace .admin-alert BEM classes with semantic role/data-type attributes
|
||
|
||
- admin.css: replace .admin-alert / .admin-alert--error / .admin-alert--success
|
||
selectors with [role="alert"][data-type="error"] and [role="status"][data-type="success"]
|
||
- All 10 admin templates updated: <div class="admin-alert admin-alert--{type}">
|
||
becomes <p role="alert|status" data-type="error|success"> (or <div> for the
|
||
import.php multi-item list that contains a <ul>)
|
||
- flash-messages.php partial updated to match
|
||
- WCAG benefit: role="alert" is an ARIA live region — errors are announced
|
||
immediately by screen readers without focus movement (fixes WCAG 3.3.1, 4.1.2)
|
||
- role="status" (polite live region) used for success messages — announced
|
||
without interrupting the user
|
||
- Removes two BEM modifier classes; CSS now targets element semantics directly
|
||
|
||
---
|
||
10b07393fea4 | 2026-04-02 12:26
|
||
Extract jury-fieldset.php partial; deduplicate jury section from add.php and edit.php
|
||
|
||
The jury composition fieldset (président·e, promoteur·ice + external checkbox, dynamic
|
||
lecteur·ices list with JS add/remove) was copy-pasted verbatim between the two longest
|
||
admin forms.
|
||
|
||
- Created templates/partials/form/jury-fieldset.php
|
||
- Consumes $juryPresident, $juryPromoteur, $juryPromoteurExt, $juryLecteurs[]
|
||
- Handles both add-mode (falls back to old()/wasSelected() flash helpers) and
|
||
edit-mode (pre-populates from DB-loaded variables)
|
||
- $juryIdx initialised from max(count($juryLecteurs), 1) — correct for both modes
|
||
- add.php: 311 → 251 lines (-60); entire fieldset + <script> replaced with one require
|
||
- edit.php: 359 → 289 lines (-70); PHP variable extraction kept inline before require
|
||
|
||
---
|
||
7834d88873df | 2026-04-02 12:20
|
||
Extract pagination into templates/partials/pagination.php
|
||
|
||
The pagination nav was duplicated between public/index.php and public/search.php
|
||
with structural differences: index.php used string concatenation for query params
|
||
and had first/last-page buttons (« »); search.php used http_build_query but had
|
||
only prev/next (‹ ›) and a flat <span> rather than a <ul>/<li> structure.
|
||
|
||
- Add templates/partials/pagination.php: accepts $page, $totalPages, $baseParams[]
|
||
(any array of query params to preserve); builds URLs with http_build_query;
|
||
renders a semantic <nav>/<ul>/<li> block with first/prev/info/next/last buttons,
|
||
correct aria-disabled + tabindex on disabled links, and aria-label on each button.
|
||
Returns immediately (no output) when $totalPages <= 1.
|
||
|
||
- Replace inline pagination block in index.php with:
|
||
$baseParams = array_filter(['year' => $year]);
|
||
include pagination.php
|
||
|
||
- Replace inline pagination block in search.php with:
|
||
$baseParams = array_diff_key($_GET, ['page' => '']);
|
||
include pagination.php
|
||
This also upgrades search.php to the full first/last button set it was missing.
|
||
|
||
Both callers verified with php -l. No functional change to existing behaviour.
|
||
|
||
---
|
||
0ab08f3aa053 | 2026-04-02 12:16
|
||
admin.css: replace .admin-main, .admin-page-title, .admin-table, .admin-fieldset with semantic selectors
|
||
|
||
Replace four presentational class names in admin.css with structural selectors
|
||
that target native HTML elements already present in every admin template:
|
||
|
||
.admin-main → .admin-body main
|
||
.admin-page-title → .admin-body main > h1
|
||
.admin-table → .admin-body table
|
||
.admin-fieldset → .admin-body fieldset
|
||
.admin-fieldset-legend → .admin-body legend
|
||
|
||
Also migrate the .admin-main > section / h2 / dl / dt / dd block to
|
||
.admin-body main > section so the thanks-page section styles survive.
|
||
|
||
Add .admin-body main > table { margin-top: 1.5rem } to absorb the inline
|
||
style="margin-top:1.5rem" that was on tags.php's <table class="admin-table">.
|
||
|
||
All 10 affected admin templates updated (add, edit, account, index, import,
|
||
pages, pages-edit, tags, system, thanks) — class attributes removed where
|
||
the element alone is now the selector. Zero visual changes.
|
||
|
||
---
|
||
cb1ced535bd6 | 2026-04-01 17:31
|
||
Replace .admin-hint / .admin-field-hint with .admin-body form small
|
||
|
||
- admin.css: remove .admin-hint and .admin-field-hint class rules; add
|
||
.admin-body form small with the same font-size/color/margin properties
|
||
plus display:block so it stacks below sibling inputs; stub comment left
|
||
where .admin-field-hint was to document the change
|
||
- add.php: 5× <p class="admin-hint"> → <small>
|
||
- edit.php: 3× <p class="admin-hint"> → <small>
|
||
- import.php: <div class="admin-hint"> → <small> (block hint below CSV input)
|
||
- pages-edit.php: class="admin-hint" removed from already-correct <small>
|
||
- account.php: <p class="admin-field-hint"> → <small>
|
||
|
||
Hint text is now styled purely via the semantic element selector; no class
|
||
required on any hint element in admin templates.
|
||
|
||
---
|
||
f208423e8db8 | 2026-04-01 17:24
|
||
Extract system.php inline <style> and <script> to system.css / $extraJsInline
|
||
|
||
- Create public/assets/css/system.css with all 280 lines of CSS that were
|
||
inline in system.php: tab bar, status cards, PHP info grid, disk bar,
|
||
log viewer, nginx config viewer, and syntax-highlight classes.
|
||
- Disk bar dynamic values (width %, colour) moved from PHP-interpolated CSS
|
||
rules to CSS custom properties (--disk-pct, --disk-color) set on the
|
||
element via an inline style attribute; static .disk-bar rule in system.css
|
||
consumes them via var().
|
||
- system.php JS block (tab-select auto-nav + copy-to-clipboard) moved to
|
||
$extraJsInline heredoc; footer.php emits it before </body> — keeps it
|
||
out of the document <head> and removes the bare <script> after </main>.
|
||
- system.php now sets $extraCss = ['/assets/css/system.css'] so head.php
|
||
emits a proper <link> in <head>, consistent with all other admin pages.
|
||
- No behaviour change; system.php is now zero inline CSS/JS.
|
||
|
||
---
|
||
cd58bc13e47c | 2026-04-01 17:08
|
||
css: replace presentational class selectors with semantic element selectors
|
||
|
||
Replace 6 CSS class selectors across tfe.css, main.css, and search.css with
|
||
semantic element-based selectors, removing the corresponding classes from the
|
||
HTML templates entirely.
|
||
|
||
tfe.css:
|
||
- .tfe-meta-list → article dl / article dl > div / article dl dt / article dl dd
|
||
- .tfe-media-block → aside figure (+ img, video, embed children)
|
||
- .tfe-file-caption → aside figcaption
|
||
|
||
main.css:
|
||
- .card__media → .home-body figure (+ img/video children and hover/motion rules)
|
||
- .card__caption → .home-body li > a > p
|
||
|
||
search.css:
|
||
- .repertoire-col > h2 → .repertoire-index section > h2
|
||
|
||
Template changes:
|
||
- tfe.php: removed class= from <dl>, <figure>, and <figcaption>
|
||
- index.php: removed class= from <figure> and <p class=card__caption>;
|
||
stripped orphaned card__media from the gradient <div> (only --gradient needed)
|
||
|
||
No visual change — selectors match the same elements as before since the
|
||
semantic HTML was already in place from prior refactoring work.
|
||
|
||
---
|
||
77576e966cdb | 2026-04-01 16:55
|
||
Remove inline styles from admin templates; extract to admin.css utility classes
|
||
|
||
- login.php: removed style= on .admin-form-row and .admin-label (already covered
|
||
by .admin-login-box scoped rules); extracted submit-wrap spacing and full-width
|
||
button to .admin-login-box .admin-submit-wrap and .admin-login-box .admin-btn
|
||
- account.php: style="margin-top:3rem" on danger-zone heading moved to
|
||
.admin-section-title--danger modifier; <span style="color:..."> replaced with
|
||
<small> element styled via .admin-danger-zone__description small
|
||
- add.php / edit.php / pages-edit.php: all style="align-items:start" removed from
|
||
.admin-form-row (redundant — already the CSS default at line 116 of admin.css);
|
||
banner preview inline styles extracted to .admin-banner-preview / .admin-banner-preview img;
|
||
add-jury button margin extracted to .admin-add-jury-btn; cancel links use .admin-cancel-link
|
||
|
||
Zero inline style= attributes remain in login, account, add, edit, pages-edit.
|
||
|
||
---
|
||
573747303f42 | 2026-04-01 16:50
|
||
admin: semantic HTML improvements — dl stats, section cards, th scope
|
||
|
||
- admin/index.php: replace <div class="admin-stats"> with <dl>; inner
|
||
<div class="admin-stat__number"> → <dd>, <div class="admin-stat__label"> → <dt>;
|
||
use CSS order to keep number visually first; add scope="col" to all 9 <th> cells
|
||
|
||
- admin/thanks.php: replace all four <div class="admin-thesis-info"> wrappers
|
||
with <section> elements; remove the class entirely; add scope="col" to
|
||
the files table <th> cells
|
||
|
||
- admin/tags.php: add scope="col" to all 3 <th> cells
|
||
|
||
- admin/pages.php: add scope="col" to all 4 <th> cells
|
||
|
||
- admin.css: rename .admin-thesis-info selectors to .admin-main > section
|
||
(element + context selector — no class needed); add display:flex +
|
||
flex-direction:column to .admin-stat so CSS order property works correctly
|
||
|
||
Addresses TODO items: section X (admin-stats dl, th scope), XI (tags th scope),
|
||
XII (admin-thesis-info → section), XIII (pages.php th scope)
|
||
|
||
---
|
||
8e36f981391d | 2026-04-01 16:44
|
||
Move RateLimit cache dir from src/cache/ to storage/cache/rate_limit/
|
||
|
||
The default cache directory for the file-based rate limiter was
|
||
src/cache/rate_limit/, placing transient JSON files inside the source tree.
|
||
This meant:
|
||
- The directory was deployed via rsync on every deploy (wasted I/O)
|
||
- .gitignore had to track a src/-internal path
|
||
- Developers running tests could leave stale cache state in the source tree
|
||
|
||
Changes:
|
||
- src/RateLimit.php: default $cacheDir changed from __DIR__.'/cache/rate_limit'
|
||
to dirname(__DIR__).'/storage/cache/rate_limit'; dirname(__DIR__) resolves to
|
||
the project root regardless of how the file is loaded (with or without bootstrap)
|
||
- .gitignore: replaced 'src/cache/rate_limit/' with 'storage/cache/' (broader,
|
||
covers any future cache subdirs under storage/)
|
||
- storage/cache/.gitkeep: added so the directory is tracked in VCS and created
|
||
on fresh clones/deploys, but its contents are ignored
|
||
- justfile: added '--exclude storage/cache/*' to the deploy rsync recipe so
|
||
rate-limit state is never transferred to the server
|
||
- src/cache/: removed (no longer needed)
|
||
|
||
All RateLimit unit tests pass.
|
||
|
||
---
|
||
9108c4069d82 | 2026-04-01 15:58
|
||
restore TODO.md: merge current active tasks with full historical TODO recovered from kkmmrrrkkyrs
|
||
|
||
---
|
||
a5ee9b162fcc | 2026-04-01 12:40
|
||
Replace site-search BEM classes with semantic header form[role="search"] selectors
|
||
|
||
CSS: .site-search → header form[role="search"],
|
||
.site-search__icon → header form[role="search"] svg,
|
||
.site-search__input → header form[role="search"] input,
|
||
.site-search__input::placeholder → header form[role="search"] input::placeholder
|
||
|
||
HTML: Removed class="site-search", class="site-search__icon", and
|
||
class="site-search__input" from header.php and search-bar.php.
|
||
The form already uses role="search" and contains a single svg + input,
|
||
so the semantic selectors are unambiguous.
|
||
|
||
---
|
||
92a07d0b99d3 | 2026-04-01 12:36
|
||
TODO: add targeted tasks for template simplification, PHP partials/components, and system page caching
|
||
|
||
---
|
||
eb67e6d4993a | 2026-04-01 12:25
|
||
Add src/App.php foundation class and flash-messages partial
|
||
|
||
Create the central App helper that eliminates ~170 lines of duplicated
|
||
bootstrap/auth/CSRF preamble across 24 page and action handler files.
|
||
|
||
src/App.php provides:
|
||
- boot(): loads Database + ensures CSRF token (public pages)
|
||
- adminGuard(): requires AdminAuth login + boot (admin pages)
|
||
- verifyCsrf() / rotateCsrf(): centralised CSRF lifecycle
|
||
- flash() / consumeFlash(): unified flash messages with legacy key drain
|
||
(error, success, admin_error, admin_success, edit_error, edit_success,
|
||
form_error all consumed transparently for incremental migration)
|
||
- redirect(): flash + Location header + exit in one call
|
||
- render(): head → header → content → footer pipeline with auto admin
|
||
footer selection
|
||
|
||
App.php is auto-loaded from config/bootstrap.php so all existing pages
|
||
get the class for free without any changes.
|
||
|
||
templates/partials/flash-messages.php uses App::consumeFlash() to replace
|
||
the 5+ copy-pasted flash blocks across admin templates.
|
||
|
||
All existing tests pass. No existing page files modified — this is a
|
||
non-breaking addition that enables incremental controller extraction.
|
||
|
||
---
|
||
7aace2a5517f | 2026-03-31 23:06
|
||
Add refactoring recommendations for controller/template/routing separation
|
||
|
||
---
|
||
8976e52d10f4 | 2026-03-31 22:50
|
||
Add PHP vs Flask architecture analysis
|
||
|
||
---
|
||
780b1b2a13a2 | 2026-03-31 22:22
|
||
merge head/nav templates into unified head.php + header.php; semantic CSS for nav
|
||
|
||
---
|
||
4ff959a72d6d | 2026-03-31 22:03
|
||
fix template consolidation: admin/head.php wraps public/head.php, footer.php wired to all public pages, remove duplicate font-family and body reset
|
||
|
||
---
|
||
3a42838cec4b | 2026-03-31 21:32
|
||
consolidate admin/public templates: common.css base in admin, nav partial, remove duplicate CSS
|
||
|
||
---
|
||
f3f1e0e5fc21 | 2026-03-31 16:57
|
||
Replace unicode left arrow with SVG icon in admin nav logo
|
||
|
||
---
|
||
a88e5562f8ea | 2026-03-31 16:47
|
||
fix(config): auto-route test.db locally, posterg.db on production
|
||
|
||
- config.php: getDatabasePath() detects php built-in CLI server
|
||
(php_sapi_name() === 'cli-server') and routes to test.db; all
|
||
other SAPIs (nginx/fpm) get posterg.db. DB_ENV env-var still
|
||
overrides either way.
|
||
|
||
- migrate.sh: auto-initialise the target DB from storage/schema.sql
|
||
when the file is absent or has no tables yet. Existing DBs with
|
||
data are left completely untouched (table_count check, no re-run
|
||
of schema on populated DB). Idempotent: safe to run repeatedly.
|
||
|
||
- justfile: serve still calls migrate (which now handles init too),
|
||
no DB_ENV prefix needed since sapi detection handles routing.
|
||
|
||
---
|
||
877e3225682d | 2026-03-31 16:37
|
||
fix(import): set is_published=1 and map access_type_id on CSV import
|
||
|
||
Imported theses were invisible on the public site because:
|
||
1. is_published defaulted to 0 (schema default) — the INSERT never
|
||
set it, so all imported rows stayed unpublished and were filtered
|
||
out by v_theses_public (WHERE is_published = 1) and every public
|
||
DB method.
|
||
2. The access column (CSV col 16 'Autorisation') was read into $access
|
||
but never written to access_type_id — silently dropped.
|
||
|
||
Fix: INSERT now includes is_published = 1 and access_type_id (resolved
|
||
from access_types.name via ucfirst/strtolower normalisation, defaulting
|
||
to 1/Libre when the CSV cell is empty or unrecognised).
|
||
|
||
---
|
||
72d48c49c3f9 | 2026-03-31 16:19
|
||
feat(db): auto-migrate both DBs on serve via scripts/migrate.sh
|
||
|
||
---
|
||
af06e09caa6f | 2026-03-31 16:17
|
||
fix(import): skip rows with duplicate identifier instead of crashing
|
||
|
||
---
|
||
e5d05982080d | 2026-03-31 16:15
|
||
fix: correct require_once path depth in admin action files
|
||
|
||
---
|
||
94f3fb67369d | 2026-03-31 16:12
|
||
feat(admin): nav logo links back to public site; all nav links right-aligned
|
||
|
||
templates/admin/head.php:
|
||
- admin-nav__logo now href="/" with target="_blank" rel="noopener noreferrer"
|
||
- Left arrow prefix (← via ←, aria-hidden) signals leaving admin
|
||
- sr-only suffix "(site public, nouvel onglet)" for screen readers
|
||
|
||
public/admin/login.php:
|
||
- Same treatment on the standalone login nav (was a bare <span>)
|
||
|
||
public/assets/css/admin.css:
|
||
- admin-nav__list: flex:1 removed; margin-left:auto added
|
||
→ entire link list now right-justified inside the nav bar,
|
||
mirroring the layout of the public site header
|
||
- .admin-nav__logout { margin-left:auto } removed (no longer needed;
|
||
logout is just the last item in a right-aligned list)
|
||
|
||
---
|
||
77cc3caa0a33 | 2026-03-31 16:10
|
||
fix(a11y): status badges no longer colour-only; fix aria on ✕ buttons (WCAG 1.4.1, 2.5.3)
|
||
|
||
admin/index.php — status badges (WCAG 1.4.1 Use of Colour):
|
||
- Published badge: prefix ● symbol (aria-hidden) + aria-label="Statut : Publié"
|
||
- Pending badge: prefix ◌ symbol (aria-hidden) + aria-label="Statut : En attente"
|
||
- Access badges (Libre/Interne/Interdit): prefix ○/◑/● symbol per type (aria-hidden)
|
||
+ aria-label="Accès : [type]"; symbol chosen from a PHP map keyed on the slug
|
||
Each badge now communicates its state through shape AND colour, not colour alone.
|
||
|
||
admin/index.php — ✕ Réinitialiser link (WCAG 2.5.3 / 1.1.1):
|
||
- ✕ wrapped in <span aria-hidden="true"> so the decorative symbol is skipped by
|
||
screen readers; accessible name remains "Réinitialiser"
|
||
|
||
admin/add.php + admin/edit.php — jury remove buttons (WCAG 2.5.3):
|
||
- All four ✕ remove buttons (2 static template rows + 2 JS-generated innerHTML strings)
|
||
given aria-label="Supprimer ce lecteur"; the bare ✕ Unicode character has no
|
||
speech equivalent so the aria-label replaces rather than supplements the label
|
||
|
||
---
|
||
338782947ca4 | 2026-03-31 15:35
|
||
chore: vendor all CDN assets locally; reorganise assets into css/ and js/
|
||
|
||
All third-party assets are now self-hosted — zero external requests at runtime.
|
||
|
||
CSS (assets/css/):
|
||
- modern-normalize.min.css (was assets/)
|
||
- common.css, admin.css, main.css, search.css, tfe.css, apropos.css (was assets/)
|
||
- easymde.min.css 2.20.0 (was cdn.jsdelivr.net)
|
||
- font-awesome.min.css 4.7.0 (was maxcdn.bootstrapcdn.com; injected at runtime by EasyMDE)
|
||
|
||
JS (assets/js/):
|
||
- easymde.min.js 2.20.0 (was cdn.jsdelivr.net)
|
||
|
||
Fonts (assets/fonts/fontawesome/):
|
||
- fontawesome-webfont.{eot,woff2,woff,ttf,svg}, FontAwesome.otf 4.7.0
|
||
|
||
Path fixes:
|
||
- common.css @font-face: ./fonts/ -> ../fonts/ (one level deeper)
|
||
- font-awesome.min.css @font-face: ../fonts/ -> ../fonts/fontawesome/ (dedicated subdir)
|
||
- pages-edit.php: autoDownloadFontAwesome:false added to EasyMDE init to
|
||
suppress the runtime CDN injection that was still present inside easymde.min.js
|
||
|
||
Reference updates (all now absolute /assets/css/* or /assets/js/*):
|
||
- templates/public/head.php: modern-normalize + common
|
||
- templates/admin/head.php: modern-normalize + admin
|
||
- public/admin/login.php: modern-normalize + admin (standalone head)
|
||
- public/index.php, tfe.php, search.php, apropos.php, licence.php: extraCss paths
|
||
- public/admin/pages-edit.php: extraCss + extraJs (font-awesome, easymde CSS/JS)
|
||
|
||
Nginx static-file location already covers .css/.js/.woff/.woff2/.ttf/.otf with
|
||
30-day cache headers — no nginx config change needed.
|
||
|
||
---
|
||
986945a347ae | 2026-03-31 15:28
|
||
fix(a11y): move pages-edit EasyMDE scripts to head/footer, add h1 to home, fix stale TODO items
|
||
|
||
- pages-edit.php: EasyMDE CDN JS URL moved to $extraJs (rendered by footer.php before </body>);
|
||
inline EasyMDE init block moved to $extraJsInline, emitted by footer.php via new
|
||
`<?php if (!empty($extraJsInline))` guard - fixes invalid <script> floating in <body> (WCAG 4.1.1)
|
||
- pages-edit.php: add <small> keyboard-trap hint below the editor textarea:
|
||
'Appuyez sur Échap pour quitter l'éditeur au clavier.' (WCAG 2.1.2)
|
||
- templates/admin/footer.php: extend to support $extraJsInline (raw inline script string)
|
||
- index.php: add <h1 class="sr-only">Mémoires de l'ERG</h1> inside <main> so the page has
|
||
a document heading (WCAG 2.4.6; h2 columns in search.php already had a sr-only h1)
|
||
- TODO.md: mark completed items as [x]: skip links (2.4.1), focus-visible / outline:none
|
||
removal (2.4.7), search.php h1 + index.php h1 (2.4.6), pages-edit.php invalid HTML (4.1.1),
|
||
EasyMDE keyboard trap hint (2.1.2)
|
||
|
||
---
|
||
59ae2151d070 | 2026-03-29 17:01
|
||
semantic HTML: apropos.php and licence.php (TODO section V & VI)
|
||
|
||
apropos.php:
|
||
- Remove redundant <div class="apropos-left"> wrapper; prose div is now a direct
|
||
grid child
|
||
- <div class="apropos-description apropos-page-content"> → <div class="prose">
|
||
(single canonical class for Markdown-rendered content)
|
||
- <div class="apropos-right"> → <aside class="apropos-aside"> (supplementary info
|
||
landmark; contacts and credits are secondary to the main description)
|
||
- Three bare <div> wrappers inside the aside → <section> (erg link, Contacts, Crédits)
|
||
- Three <div class="apropos-contact"> entries → <address> with font-style:normal
|
||
override; <span class="apropos-contact-name"> → <strong>;
|
||
<span class="apropos-contact-email"> → <a href="mailto:…">
|
||
Removes classes: apropos-left, apropos-right, apropos-contact, apropos-contact-name,
|
||
apropos-contact-role, apropos-contact-email, apropos-description, apropos-page-content
|
||
|
||
licence.php:
|
||
- <div class="apropos-description apropos-page-content apropos-single"> →
|
||
<div class="prose apropos-single"> (consistent with apropos.php rename)
|
||
|
||
apropos.css:
|
||
- Rename .apropos-description / .apropos-page-content → .prose; merge all prose
|
||
content rules under the single .prose selector
|
||
- Rename .apropos-right → .apropos-aside; remove .apropos-left (empty rule)
|
||
- Replace .apropos-contact, .apropos-contact-name etc. with element selectors:
|
||
.apropos-aside address, .apropos-aside address strong,
|
||
.apropos-aside address span, .apropos-aside address a
|
||
- Update responsive blocks to reference .prose instead of .apropos-description
|
||
|
||
---
|
||
f2c023e19ab8 | 2026-03-29 16:31
|
||
admin nav: replace bare <a> links with <ul>/<li>, use aria-current instead of .active class
|
||
|
||
- templates/admin/head.php: all 7 nav links (+ conditional Modifier + Déconnexion)
|
||
wrapped in <ul class="admin-nav__list">/<li>; .active class removed, replaced
|
||
with aria-current="page" on each <a> based on $currentPage match
|
||
- Déconnexion link: removed inline style="margin-left:auto;opacity:.6;"; moved to
|
||
new .admin-nav__logout <li> class in admin.css
|
||
- public/assets/admin.css: replaced .admin-nav__link rules with .admin-nav__list a
|
||
selectors; added .admin-nav__list (flex list, gap 2.5rem, flex:1); added
|
||
.admin-nav__list a[aria-current="page"] rule (border-bottom underline indicator);
|
||
added .admin-nav__logout / .admin-nav__logout a for the push-right logout item
|
||
- Removes .admin-nav__link class entirely from the codebase (was only used in
|
||
templates/admin/head.php and admin.css)
|
||
|
||
Fixes WCAG 2.4.6 (nav landmark content model), 1.4.1 (colour-only active indicator),
|
||
and section VIII of the semantic HTML admin audit.
|
||
|
||
---
|
||
ac872c1fe0ae | 2026-03-29 16:13
|
||
Semantic HTML: home page card grid — <ul>/<li>/<figure>/<nav> refactor
|
||
|
||
Replace presentational divs in index.php and main.css with elements that
|
||
carry correct semantic meaning, fixing multiple WCAG 2.1 AA issues:
|
||
|
||
index.php:
|
||
- <div class="cards-container"> → <ul class="cards-container"> (list of navigable items)
|
||
- <a class="card-link"><div class="card">…</div></a> → <li class="card"><a> (block link
|
||
is the <a>, <li> is the container; removes the redundant .card div wrapper)
|
||
- <div class="card__media"> → <figure class="card__media"> when wrapping an <img>;
|
||
gradient placeholder stays as <div> (presentational, aria-hidden)
|
||
- Improved alt text: "Couverture — [title] par [authors]" instead of bare title
|
||
- Removed <div class="card__info"> wrapper; caption is now a bare <p class="card__caption">
|
||
directly inside the <a>
|
||
- <div class="filter-info"> → <p class="filter-info" role="status"> (live-region
|
||
semantics; announces filter state to screen readers)
|
||
- ✕ symbol in clear-filter link wrapped in <span aria-hidden="true">
|
||
- Gradient placeholder div gets aria-hidden="true" (decorative; caption below carries text)
|
||
- Empty-state <p style="…"> → <li class="cards-empty"> (removes inline style)
|
||
- <div class="pagination-wrap"> → <nav class="pagination-wrap" aria-label="Pagination">
|
||
with <ul>/<li> children; page-info <span> → <li aria-current="page">
|
||
|
||
main.css:
|
||
- .cards-container: add list-style:none; margin:0; padding:0 (reset <ul> defaults)
|
||
- Remove .card-link rule; replace with .card > a (block flex link, no separate class)
|
||
- .card__media: add margin:0 to reset <figure> default margin
|
||
- Remove .card__info rules; rename .authors to .card__caption with same styles
|
||
- Add .cards-empty rule (removes last inline style from index.php)
|
||
- .pagination-wrap: restructured for <nav>/<ul>; inner <ul> carries the flex layout
|
||
- prefers-reduced-motion: add .card__media--gradient guard
|
||
|
||
WCAG criteria addressed: 1.1.1 (alt text), 1.3.1 (info & relationships via semantic
|
||
list/figure), 2.4.1 (filter-info now live region), role="status" on filter banner.
|
||
|
||
---
|
||
c352a392a1f9 | 2026-03-29 16:06
|
||
search.php: semantic HTML overhaul of répertoire index and results view
|
||
|
||
- Replace 4x <div class="repertoire-col"> with <section>; remove
|
||
.repertoire-col__header class, CSS now targets section > h2
|
||
- Wrap all index link groups in <ul>/<li>; delete the four per-column
|
||
link classes (year-index-item, cat-index-item, student-index-item,
|
||
keyword-index-item); active state switches from .active to
|
||
aria-current="page" on the <a>
|
||
- Add <h1 class="sr-only">Répertoire</h1> so the index view has a
|
||
page-level heading (WCAG 2.4.6)
|
||
- Remove redundant <div class="search-results-view"> wrapper; padding
|
||
moved to .results-grid and .search-results-header directly
|
||
- Replace <div class="results-grid"> with <ul class="results-grid">;
|
||
each result card becomes <li><a class="result-card">
|
||
- Replace <span class="result-card__meta"> with <small> (ancillary
|
||
metadata per HTML spec)
|
||
- Replace result-count <p> with <output role="status"> (computed value)
|
||
- Replace 3x <div class="search-filter-group"><label>…</label><select>
|
||
with <label> directly wrapping <select> (implicit association,
|
||
removes .search-filter-group divs); CSS updated to display:flex on
|
||
the label itself
|
||
- Pagination wrapper changed to <nav aria-label="Pagination">;
|
||
page-info span gets aria-current="page"
|
||
- search.css: delete .search-results-view, four index-item classes,
|
||
.cat-index-group, .search-filter-group; consolidate years/other
|
||
column link styles under .repertoire-col:first-child ul a and
|
||
.repertoire-col:not(:first-child) ul a selectors; add ul reset rule
|
||
|
||
---
|
||
6657c4fbbe6d | 2026-03-29 15:50
|
||
refactor(nav): replace div+BEM classes with semantic ul/li in public nav
|
||
|
||
templates/nav.php:
|
||
- Replace <div class="site-nav__links"> with <ul role="list"> + <li> children
|
||
- Move À Propos link inside the list (was a loose sibling <a>)
|
||
- Remove .site-nav__link and .site-nav__link--active classes from all <a> elements
|
||
- Active state now driven solely by aria-current="page" (already present)
|
||
|
||
public/assets/common.css:
|
||
- Remove .site-nav__links, .site-nav__link, .site-nav__link:hover, .site-nav__link--active rules
|
||
- Add .site-nav ul (flex, gap, list-style reset), .site-nav ul a, .site-nav ul a:hover
|
||
- Active indicator: .site-nav ul a[aria-current="page"] — self-documenting, screen-reader-announced
|
||
|
||
Fixes TODO section I (nav semantic HTML audit). All three BEM nav-link classes deleted;
|
||
zero references remain in the codebase.
|
||
|
||
---
|
||
7a4a47183864 | 2026-03-29 15:47
|
||
fix: search filter labels, 429 page styling, __wakeup PHP 8.x deprecation
|
||
|
||
- Replace three <span class='search-filter-label'> with proper <label for='...'> elements in
|
||
search.php filter bar; add id attributes to the corresponding <select> elements so the
|
||
label/control association is programmatic (WCAG 1.3.1, 3.3.2).
|
||
|
||
- Rewrite the rate-limit 429 early-exit in search.php from a bare one-liner echo to a full
|
||
HTML document with lang='fr', viewport meta, and inline dark styles matching maintenance.php;
|
||
inject the retry countdown into the user-facing message (Template audit F).
|
||
|
||
- Fix PHP 8.x __wakeup() deprecation in Database.php singleton guard: replace the throw
|
||
statement with trigger_error(..., E_USER_ERROR) and add an explicit void return type
|
||
(Refactor audit C).
|
||
|
||
---
|
||
3a8ffa6afede | 2026-03-29 15:43
|
||
Add Open Graph and Twitter Card meta tags to all public pages
|
||
|
||
- templates/public/head.php: add centralised OG/Twitter tag rendering via $ogTags array;
|
||
supports type, title, description, url, image, image_alt, site_name, article_author,
|
||
article_published_time; twitter:card switches between summary_large_image / summary
|
||
based on presence of og:image
|
||
|
||
- public/tfe.php: populate full article OG tags — og:type=article, canonical URL,
|
||
og:image resolved from banner_path → first image file in thesis_files → omitted,
|
||
og:image:alt, article:author, article:published_time (year-01-01); twitter:card
|
||
summary_large_image when image present
|
||
|
||
- public/index.php, search.php, apropos.php, licence.php: add basic og:type=website
|
||
tags (title, description, canonical url, site_name)
|
||
|
||
Sharing a thesis link on Slack, WhatsApp, iMessage, or any social platform will now
|
||
render a rich preview card with the thesis title, synopsis excerpt, and cover/banner image.
|
||
|
||
---
|
||
1dee1ea73fb4 | 2026-03-28 19:38
|
||
Add <meta name=description> to all public pages; improve page titles
|
||
|
||
- templates/public/head.php: emit <meta name="description"> when $metaDescription is set
|
||
- index.php: title → 'Posterg – Mémoires de l\'ERG'; description = site blurb
|
||
- tfe.php: title → '[Titre] – [Auteur] – Posterg'; description = synopsis excerpt (strip_tags, truncate 160)
|
||
- search.php: description = répertoire purpose blurb
|
||
- apropos.php: description = about-page blurb
|
||
- licence.php: description = licences blurb
|
||
|
||
Fixes WCAG 2.4.2 (Page Titled) for index.php and tfe.php.
|
||
All descriptions properly htmlspecialchars-escaped at render time.
|
||
|
||
---
|
||
5c00886db6c1 | 2026-03-28 19:13
|
||
fix fgetcsv deprecation and apply pending DB migrations
|
||
|
||
---
|
||
126703f34002 | 2026-03-28 19:12
|
||
tfe.php: full semantic HTML overhaul
|
||
|
||
- Replace <div class="tfe-layout"> with <article>, <div class="tfe-left"> with
|
||
<header>, <div class="tfe-right"> with <aside> (supplementary media column)
|
||
- Fix inverted heading hierarchy: <h1> is now the thesis title (primary topic);
|
||
author demoted to <p class="tfe-author"> (metadata, not a heading)
|
||
- Replace <div class="tfe-meta-list"> / <div class="tfe-meta-item"> / <span class="label">
|
||
/ <span class="value"> with <dl> / <dt> / <dd> (WCAG 1.3.1 info & relationships)
|
||
- Replace <div class="tfe-media-block"> with <figure>; <p class="tfe-file-caption">
|
||
with <figcaption>; PDF <embed> gets .tfe-pdf-fallback download link (WCAG 4.1.2)
|
||
- Move back link to top of left column; extract inline styles to .tfe-back-link,
|
||
.tfe-note-value, .tfe-restricted CSS classes
|
||
- Fix image alt text: description column used when populated, fallback to
|
||
"Title — Author" instead of raw filename (WCAG 1.1.1)
|
||
- Add sr-only new-tab warning on baiu_link (WCAG 1.3.1 / 2.4.4)
|
||
- Fix PDF embed height: clamp(300px, 80vh, 700px) prevents horizontal overflow
|
||
on small screens (WCAG 1.4.10 reflow)
|
||
- tfe.css: update all selectors to match new structure; remove inline styles;
|
||
unify .tfe-restricted and .tfe-no-files; add .tfe-pdf-fallback, .tfe-back-link
|
||
|
||
---
|
||
a84d6d560a08 | 2026-03-28 18:13
|
||
a11y: nav aria-label, search role=search + label, card hover motion guard
|
||
|
||
- templates/nav.php: add aria-label="Navigation principale" to <nav>; emit
|
||
aria-current="page" on the active link alongside the existing CSS class
|
||
so screen readers announce the current page without relying on colour/style alone
|
||
|
||
- templates/search-bar.php: add role="search" + aria-label="Recherche" to
|
||
the <form>; add a visually-hidden <label for="site-search-input"> linked to
|
||
the input via id="site-search-input", satisfying WCAG 3.3.2 (labels/instructions)
|
||
and 4.1.2 (name/role/value) — placeholder text alone is not a label
|
||
|
||
- public/assets/main.css: add @media (prefers-reduced-motion: reduce) block that
|
||
sets transition:none and transform:none on .card__media img/video hover, so the
|
||
scale(1.02) zoom is fully suppressed for users who opt out of motion (WCAG 2.3.3 /
|
||
prefers-reduced-motion); the global transition-duration guard in common.css already
|
||
covers all other transitions but does not zero the transform value itself
|
||
|
||
Fixes TODO sections: G (nav/search-bar landmark names), I (site-search form ARIA),
|
||
3.3.2 (search input label), prefers-reduced-motion (card hover transform gate)
|
||
|
||
---
|
||
4f5ff5a22c60 | 2026-03-28 18:08
|
||
refactor: extract edit.php POST handler to actions/edit.php
|
||
|
||
edit.php was a 530-line file mixing form display, POST handling, file
|
||
uploads, and reference-data loading. This refactor splits it along the
|
||
same action-file pattern already used by formulaire.php, tag.php, and
|
||
page.php.
|
||
|
||
Changes:
|
||
- public/admin/actions/edit.php (new): standalone POST handler; auth
|
||
guard, CSRF check, transaction, redirect with session flash messages
|
||
- public/admin/edit.php: display-only; reads edit_success/edit_error
|
||
flash keys from session; form action points to actions/edit.php via
|
||
a hidden thesis_id field instead of a query-string self-post
|
||
- src/Database.php: four new methods to remove all raw PDO from both
|
||
files:
|
||
- updateThesis(int, array): void — UPDATE theses core fields
|
||
- setThesisAuthors(int, array): void — delete-then-reinsert authors
|
||
- getThesisLanguageIds(int): array — SELECT language_id for form
|
||
- getThesisFormatIds(int): array — SELECT format_id for form
|
||
|
||
---
|
||
f20aab5f66b0 | 2026-03-28 17:00
|
||
css: deduplicate html/body reset; fix pages-edit.php invalid HTML
|
||
|
||
Move the repeated 'html, body { margin:0; padding:0; height:100% }' block from
|
||
main.css, search.css, tfe.css, and apropos.css into the single canonical location
|
||
in common.css. All four public page stylesheets already load common.css first, so
|
||
the rule applies identically — no visual change.
|
||
|
||
Fix pages-edit.php invalid HTML: the EasyMDE <link rel=stylesheet> was placed
|
||
inside <body> (after head.php was already closed), which is invalid. Add an
|
||
$extraCss hook to templates/admin/head.php so pages can inject <link> tags into
|
||
<head> via an array variable, matching the pattern already used by the public
|
||
templates/public/head.php. Also add a symmetric $extraJs hook to
|
||
templates/admin/footer.php for future use. pages-edit.php now sets
|
||
$extraCss = ['easymde.min.css'] before requiring head.php; the EasyMDE JS
|
||
<script> and its inline init remain in <body> in the correct load order.
|
||
|
||
---
|
||
b8529f7abeb7 | 2026-03-28 16:51
|
||
fix: WCAG 2.1 AA contrast, mobile répertoire layout, and pagination accessibility
|
||
|
||
Contrast failures (WCAG 1.4.3):
|
||
- common.css: remove opacity:0.92 from .site-nav__link (was 4.05:1, now 4.87:1 white-on-purple)
|
||
- common.css: placeholder colour #aaa → #767676 (2.32:1 → 4.54:1 on white)
|
||
- main.css: filter-info and clear-filter text var(--purple) → var(--purple-dark) (#9557b5 → #7b3fa0, 4.08 → 5.7:1)
|
||
- index.php: gradient card lighter stop L=65% → L=40%, darker stop L=45% → L=28%; white text now passes 4.5:1 across all hues
|
||
|
||
Non-text contrast (WCAG 1.4.11):
|
||
- search.css: search-filter <select> border #ddd → #949494 (1.6:1 → 3.0:1 on white)
|
||
- admin.css: --admin-border #333 → #555 (input bottom-border on #1a1a1a: 1.8:1 → 3.1:1)
|
||
- admin.css: --admin-text-muted #888 → #969696 (4.38:1 → 4.54:1 on #242424)
|
||
|
||
Mobile layout (WCAG 1.4.10 Reflow):
|
||
- search.css: add @media (max-width:768px) to collapse répertoire 4-column grid to single column;
|
||
columns switch from right-border to bottom-border separators
|
||
|
||
Keyboard / screen reader (WCAG 2.1.1, 2.4.4):
|
||
- index.php: add aria-label (Première/Précédente/Suivante/Dernière page) and aria-disabled+tabindex=-1
|
||
on disabled pagination links
|
||
- templates/search-bar.php: add aria-hidden=true and focusable=false to decorative SVG magnifier
|
||
|
||
Language (WCAG 3.1.1):
|
||
- search.php: add lang=fr to <html> in 429 rate-limit response
|
||
|
||
---
|
||
18197bd46854 | 2026-03-28 16:49
|
||
Extract shared public <head> partial
|
||
|
||
Create templates/public/head.php accepting $pageTitle and $extraCss (array of
|
||
stylesheet hrefs), mirroring the existing templates/admin/head.php pattern.
|
||
|
||
The partial emits: DOCTYPE, <html lang=fr>, charset/viewport meta, favicon,
|
||
modern-normalize, common.css, any extra CSS links, and the dev-only live-reload
|
||
script. The live-reload snippet was previously copy-pasted verbatim into all
|
||
five public pages.
|
||
|
||
Updated pages:
|
||
- public/index.php ($pageTitle='Posterg', $extraCss=['assets/main.css'])
|
||
- public/search.php ($pageTitle='Répertoire – Posterg', search.css)
|
||
- public/tfe.php ($pageTitle=thesis title + suffix, tfe.css)
|
||
- public/apropos.php ($pageTitle='À Propos – Posterg', apropos.css)
|
||
- public/licence.php ($pageTitle=DB title + suffix, apropos.css)
|
||
|
||
tfe.php: removed redundant htmlspecialchars() call on $pageTitle (the partial
|
||
applies it); licence.php: renamed conflicting $page variable to $dbPage to
|
||
avoid collision with the shared $pageTitle expected by the partial.
|
||
|
||
All syntax checks and test suite pass (4/4).
|
||
|
||
---
|
||
640d37936fd7 | 2026-03-28 16:44
|
||
css: fix nav active state, deduplicate .site-nav__right, add font-display, clean up search pagination
|
||
|
||
- common.css: add font-display: swap to Combinedd.otf @font-face (eliminates FOIT)
|
||
- common.css: remove duplicate .site-nav__right block (identical to .site-nav__link);
|
||
update nav.php to use .site-nav__link on the À Propos link
|
||
- common.css: add .site-nav__link--active rule (opacity:1 + white underline); the class
|
||
was already applied in nav.php but had no CSS definition, making it invisible
|
||
- search.php: replace fully inline-styled pagination with .pagination-wrap / .pagination-btn
|
||
/ .pagination-info classes; add aria-disabled + tabindex=-1 on disabled links;
|
||
add aria-label on prev/next links
|
||
- search.css: add pagination rule block to match, keeping styles co-located with the page
|
||
|
||
---
|
||
764edf912123 | 2026-03-28 16:42
|
||
Remove dead template/asset files; fix licence.php full-width layout
|
||
|
||
- Delete templates/header.php and templates/head.php — both were legacy
|
||
partials from a previous design iteration (lang="en", broken nav markup)
|
||
that were never included anywhere in the current codebase.
|
||
|
||
- Delete public/assets/icons.svg — the full TrumboWYG icon sprite (~15 KB)
|
||
referenced nowhere; the only active WYSIWYG editor (EasyMDE in
|
||
pages-edit.php) loads its own assets from CDN.
|
||
|
||
- Fix licence.php layout: the page was borrowing the two-column
|
||
.apropos-layout grid but leaving the right column always empty, wasting
|
||
~40% of the viewport. Removed the grid wrapper and the empty .apropos-right
|
||
div. Added .apropos-single utility class to apropos.css (max-width: 720px)
|
||
so licence content now spans the full available width with a readable
|
||
line length.
|
||
|
||
---
|
||
61ac3c002db2 | 2026-03-28 13:52
|
||
refactor: encapsulate thesis creation SQL in Database::createThesis()
|
||
|
||
Move the raw identifier-generation query and the INSERT INTO theses /
|
||
INSERT INTO thesis_authors statements out of formulaire.php into two new
|
||
Database methods:
|
||
|
||
generateThesisIdentifier(int $year): string
|
||
– counts existing theses for the year inside the open transaction so
|
||
concurrent workers cannot produce duplicate YYYY-NNN identifiers.
|
||
|
||
createThesis(array $data): int
|
||
– generates the identifier, INSERTs the thesis row, links the author
|
||
via thesis_authors (author_order=1), returns the new thesis ID.
|
||
|
||
getThesisIdentifier(int $id): string
|
||
– fetches the stored identifier for a thesis ID; used by formulaire.php
|
||
to reconstruct the upload path (storage/theses/YYYY/YYYY-NNN/).
|
||
|
||
formulaire.php now calls $db->createThesis([…]) + $db->getThesisIdentifier()
|
||
and no longer holds any raw PDO queries for the core thesis insert.
|
||
The $pdo local variable (previously $db->getPDO()) is removed entirely.
|
||
|
||
All four test suites (Unit, RateLimit, Integration, Security) pass.
|
||
|
||
---
|
||
2ec5a7f38f97 | 2026-03-28 13:48
|
||
docs: ORM assessment — verdict: keep raw PDO, no ORM needed
|
||
|
||
---
|
||
06488586aff4 | 2026-03-28 13:48
|
||
refactor: encapsulate junction-table writes and banner upload in Database
|
||
|
||
Add three delete-then-reinsert helpers to Database.php that follow the same
|
||
pattern already used by setThesisJury():
|
||
|
||
setThesisLanguages(int $thesisId, array $languageIds)
|
||
setThesisFormats(int $thesisId, array $formatIds)
|
||
setThesisTags(int $thesisId, array $tagNames)
|
||
|
||
setThesisTags() calls findOrCreateTag() internally and enforces the 10-tag cap,
|
||
keeping that rule in one place.
|
||
|
||
Also extract the duplicated banner-upload block (MIME check, size cap,
|
||
random filename, move_uploaded_file, chmod, setBannerPath) into:
|
||
|
||
handleBannerUpload(int $thesisId, ?array $uploadedFile): ?string
|
||
|
||
Both formulaire.php and edit.php are updated to call these methods instead of
|
||
open-coding the SQL loops and file-upload logic. The edit.php banner-removal
|
||
branch is unchanged (unlink + setBannerPath(null) stays inline as it is
|
||
logically distinct from an upload).
|
||
|
||
---
|
||
e126e1a3b010 | 2026-03-28 13:47
|
||
refactor: use encapsulated Database methods in formulaire.php and edit.php
|
||
|
||
---
|
||
71167b2cdf26 | 2026-03-28 13:43
|
||
fix: remove DB_ENV auto-detection; require explicit DB_ENV=test for tests
|
||
|
||
src/config.php: remove the file-existence fallback that silently redirected
|
||
all requests to test.db whenever that file was present on disk. getDatabasePath()
|
||
now always returns the production DB unless DB_ENV=test is explicitly set.
|
||
|
||
tests/run-tests.php: putenv('DB_ENV=test') at the top so the suite always
|
||
targets test.db regardless of what is set in the shell environment.
|
||
|
||
tests/Unit/DatabaseTest.php, tests/Integration/SearchTest.php,
|
||
tests/Security/SecurityTest.php: same putenv() guard added to each file so
|
||
they work correctly when run standalone (e.g. just test-unit).
|
||
|
||
justfile: all test and DB-development recipes now prefix DB_ENV=test to their
|
||
php/sqlite3 commands, making the intent explicit in the recipe itself.
|
||
|
||
Fixes: a developer who ran the test suite and kept test.db on disk would
|
||
silently hit test data when browsing the local site with no DB_ENV set.
|
||
|
||
---
|
||
7d96a0832436 | 2026-03-28 13:35
|
||
perf: replace fat-view student index query with lean getPublishedAuthors()
|
||
|
||
The répertoire page was loading the full v_theses_public view
|
||
(15 JOINs + 8 GROUP_CONCAT temp B-trees) via getAllPublishedTheses()
|
||
just to build the student name → thesis-id map on the index page.
|
||
Only two columns (id, authors) were ever consumed by the template.
|
||
|
||
Add Database::getPublishedAuthors(): array
|
||
- Queries thesis_authors JOIN authors directly on the theses base table
|
||
- Filters on theses.is_published = 1 using the existing index
|
||
- Returns only id + GROUP_CONCAT(authors) — no view expansion
|
||
- Results verified identical to the old getAllPublishedTheses() output
|
||
|
||
Update search.php to call getPublishedAuthors() instead.
|
||
Mark getAllPublishedTheses() @deprecated in Database.php.
|
||
|
||
All tests pass.
|
||
|
||
---
|
||
1181cfa88b8e | 2026-03-28 13:32
|
||
encapsulate raw PDO queries leaking from callers into Database.php methods
|
||
|
||
- Add getThesisAccessTypeId(int $id): ?int — replaces raw SELECT in tfe.php
|
||
- Add getCoverPathsForTheses(array $ids): array — replaces raw SELECT/IN query in index.php
|
||
- Add getFileVisibility(string $path): ?int — replaces raw join query in media.php
|
||
- Add getThesisBannerPath(int $id): ?string — replaces unparameterised SQL injection in
|
||
edit.php (SELECT banner_path FROM theses WHERE id = $thesisId was interpolating $thesisId
|
||
directly into the query string; now parameterised via prepared statement)
|
||
- Add getThesisRawFields(int $id): ?array — replaces raw SELECT license_id/access_type_id/
|
||
context_note in edit.php
|
||
- Add getThesisCount(): int — replaces raw SELECT COUNT(*) in system.php
|
||
|
||
Callers updated: public/tfe.php, public/index.php, public/media.php,
|
||
public/admin/edit.php, public/admin/system.php
|
||
|
||
---
|
||
20e5f71634ab | 2026-03-28 13:28
|
||
Fix two backend correctness issues
|
||
|
||
- Wrap setThesisJury() in a transaction: the method did a DELETE then multiple
|
||
INSERTs with no atomicity guarantee. A partial failure (e.g. findOrCreateSupervisor
|
||
throwing) would leave the jury table with orphaned rows. The fix uses
|
||
pdo->inTransaction() to avoid nesting when called from within an outer transaction,
|
||
and performs beginTransaction/commit/rollBack otherwise.
|
||
|
||
- Replace raw PDO query in admin/thanks.php with db->getThesisFiles(): the file
|
||
listing after TFE submission was manually preparing a SELECT on thesis_files
|
||
instead of calling the existing Database::getThesisFiles() method. Removes the
|
||
getPDO() call entirely from that file.
|
||
|
||
---
|
||
69e161ada3c9 | 2026-03-28 11:42
|
||
fix(admin): stats bar always shows whole-DB counts, not filtered counts
|
||
|
||
admin/index.php showed "TFE total / Publiés / En attente" by running
|
||
array_filter() over the already-filtered $theses array returned by
|
||
getThesesList(). When any search or year filter was active the three
|
||
numbers reflected only the matching subset, making the stats misleading
|
||
(e.g. searching for a single student would show "1 total, 0 publiés").
|
||
|
||
Add Database::getThesesStats(): array — a single SQL aggregation query:
|
||
SELECT COUNT(*), SUM(is_published), SUM(NOT is_published) FROM theses
|
||
|
||
This runs against the raw theses table with no filters, so the counters
|
||
always display the true whole-database figures regardless of what filter
|
||
the admin has active. admin/index.php now calls getThesesStats() and
|
||
reads $stats['total'], $stats['published'], $stats['pending'] instead
|
||
of the array_filter expressions.
|
||
|
||
---
|
||
2e277b104ef7 | 2026-03-28 11:35
|
||
refactor(Database): remove dead CRUD helpers and alias proliferation
|
||
|
||
Remove 5 unused ID-lookup helpers (getOrientationId, getAPProgramId,
|
||
getFinalityId, getLanguageId, getFormatId) — forms have always passed
|
||
FK ids directly from <select> elements; these methods were never called
|
||
outside import.php, which now uses inline PDO queries instead.
|
||
|
||
Collapse 13 alias methods down to the single canonical name for each:
|
||
getAllOrientations, getAllAPPrograms, getAllFinalityTypes,
|
||
getAllFormatTypes, getAllLanguages, getAllLicenseTypes,
|
||
getUsedTags, findOrCreateTag
|
||
|
||
The short-name variants (getOrientations, getApPrograms, etc.) and
|
||
compat aliases (getUsedKeywords, findOrCreateKeyword, getAllLicenseTypes
|
||
delegating to getLicenseTypes) are deleted. All call-sites updated:
|
||
- public/search.php: getOrientations→getAllOrientations, etc.
|
||
- public/admin/import.php: findOrCreateKeyword→findOrCreateTag,
|
||
thesis_keywords→thesis_tags, keyword_id→tag_id (fixes stale table
|
||
reference from pre-migration-001 that bypassed the M2M rename)
|
||
- tests/Unit/DatabaseTest.php: remove alias smoke-test (test 7)
|
||
|
||
Database.php: 948 → 848 lines (-100).
|
||
|
||
---
|
||
b0632b4772b3 | 2026-03-27 23:16
|
||
fix(formulaire): remove htmlspecialchars from sanitize_string + delete dead $problematique
|
||
|
||
HTML-escaping at write time stores &, < etc. in the DB, corrupting full-text
|
||
search, tag matching, exports, and any non-HTML consumer. PDO parameterised queries
|
||
already prevent SQL injection; templates call htmlspecialchars() on output.
|
||
|
||
sanitize_string() now does strip_tags(trim()) only — matching the pattern already
|
||
used by edit.php which never had this bug.
|
||
|
||
Also deleted the dead $problematique variable (read from POST[problématique] but
|
||
never passed to any INSERT or used anywhere in the codebase).
|
||
|
||
---
|
||
f37069720a59 | 2026-03-27 13:48
|
||
schema: add composite index (is_published, year DESC) + fix stale migration 005
|
||
|
||
- Add idx_theses_pub_year composite index on theses(is_published, year DESC) to
|
||
schema.sql; replaces the need for the query planner to pick between the two
|
||
separate idx_theses_published / idx_theses_year indexes and sort with a temp
|
||
B-tree. Every public query filters on is_published=1 and orders/filters by year,
|
||
so this covering index eliminates the sort pass for those queries.
|
||
|
||
- Create storage/migrations/006_add_composite_index.sql and apply to both
|
||
posterg.db and test.db.
|
||
|
||
- Fix storage/migrations/005_add_banner.sql: the view recreation in that file
|
||
still referenced the pre-migration-001 table/column names (thesis_keywords,
|
||
keywords.keyword). Updated to use thesis_tags / tags tg to match the canonical
|
||
schema.sql. The live DB was unaffected (migration 001 ran before 005), but the
|
||
file was misleading and would fail if ever re-run from scratch.
|
||
|
||
---
|
||
42af4644c5ba | 2026-03-27 13:45
|
||
perf+a11y: WAL mode for SQLite, skip links, :focus-visible, .sr-only
|
||
|
||
SQLite performance (Database::__construct):
|
||
- PRAGMA journal_mode = WAL: eliminates full-DB read locks on write, safe
|
||
for concurrent PHP-FPM workers
|
||
- PRAGMA synchronous = NORMAL: durable on commit without full fsync per write
|
||
- PRAGMA cache_size = -8000: ~8 MB page cache per connection
|
||
|
||
Accessibility foundation (WCAG 2.1 AA):
|
||
- common.css: add .sr-only utility, .skip-link (hidden until focused),
|
||
global :focus-visible (2px purple outline, 2px offset),
|
||
prefers-reduced-motion guard; remove bare outline:none from
|
||
.site-search__input
|
||
- admin.css: same :focus-visible, skip-link, and motion guard scoped to
|
||
admin purple; remove outline:none from .admin-input/.admin-select/
|
||
.admin-textarea and .admin-filters select (both had :focus border rules
|
||
already, so focus is still visually communicated)
|
||
- search.css: remove outline:none from .search-filter-select (already has
|
||
:focus border-color rule)
|
||
- All 5 public pages (index, search, tfe, apropos, licence): add
|
||
<a href="#main-content" class="skip-link"> as first child of <body>;
|
||
add id="main-content" to <main>
|
||
- templates/admin/head.php: same skip link; aria-label="Navigation admin"
|
||
on <nav>; id="main-content" on all 10 admin <main> elements
|
||
|
||
All 4 test suites pass (unit, integration, security, rate-limit).
|
||
|
||
---
|
||
a9877b1d1d44 | 2026-03-26 23:04
|
||
docs: accessibility audit — add WCAG 2.1 AA analysis to TODO.md
|
||
|
||
Measured contrast ratios, traced every interactive element, checked all four
|
||
WCAG principles across public and admin surfaces. Current state confirmed:
|
||
zero ARIA attributes, zero skip links, zero focus-visible styles, zero
|
||
prefers-reduced-motion guards in the live codebase.
|
||
|
||
Key failures by criterion:
|
||
|
||
1.1.1: TFE file images use raw filename as alt; search bar SVG not aria-hidden;
|
||
jury remove buttons have no accessible name (bare ✕)
|
||
|
||
1.3.1: TFE metadata is div/span soup with no programmatic label-value association;
|
||
search filter selects have no associated label; checkbox groups need fieldset/legend
|
||
|
||
1.4.1: Status badges distinguish state by colour only; active nav link has no
|
||
non-colour indicator and its CSS class has no rule at all
|
||
|
||
1.4.3 (measured failures):
|
||
- Nav links at opacity:0.92 on purple: 4.05:1 (fails AA 4.5:1)
|
||
- filter-info purple text on purple-light bg: 4.08:1 (fails AA)
|
||
- Placeholder #aaa on white: 2.32:1 (fails AA)
|
||
- Gradient cards: white text on L=65% HSL — every warm hue fails AA,
|
||
some as low as 1.46:1 (yellow). Only blue/indigo hues pass.
|
||
- admin-text-muted #888 on bg-alt #242424: 4.38:1 (fails AA)
|
||
- admin-purple on dark bg: 3.57:1 (fails for normal text size)
|
||
|
||
1.4.10: Répertoire 4-column grid has no mobile breakpoint
|
||
1.4.11: Search select border #ddd on white: 1.6:1; admin input border: 1.8:1
|
||
|
||
2.1.1: Disabled pagination links have pointer-events:none but remain keyboard-focusable
|
||
2.4.1: No skip-to-main link anywhere in the site
|
||
2.4.4: Pagination arrows (« ‹ › ») have no aria-label
|
||
2.4.6: tfe.php h1=author h2=title is inverted; index/search have no h1 at all
|
||
2.4.7: No :focus-visible defined anywhere; outline:none suppresses browser default
|
||
on search input with no replacement
|
||
|
||
3.1.1: 429 response has no lang attribute
|
||
3.3.1: Form errors not announced as live regions; no autofocus on invalid field
|
||
3.3.2: Search input has no label, only placeholder
|
||
|
||
4.1.1: pages-edit.php has <link> in <body>
|
||
4.1.2: <video> has no caption track; <embed> PDF has no fallback download link;
|
||
bulk action results not announced to AT
|
||
|
||
Motion: no prefers-reduced-motion guard on any transition or animation
|
||
|
||
Infrastructure gaps: no .sr-only class, no skip link, no :focus-visible,
|
||
four explicit outline:none suppressions with no replacement
|
||
|
||
---
|
||
22fabeb44768 | 2026-03-26 22:58
|
||
docs: semantic HTML audit admin section — add sections VIII–XVI to TODO.md
|
||
|
||
Analysed every admin template against semantic HTML:
|
||
|
||
templates/admin/head.php (VIII):
|
||
- Nav links are bare <a> in flat <nav>, no <ul>/<li> structure
|
||
- No aria-label on <nav>, active state uses .active class not aria-current=page
|
||
- Déconnexion link has inline style for positioning
|
||
|
||
add.php / edit.php (IX):
|
||
- ~20 <div class=admin-form-row> wrappers removable with CSS grid on form directly
|
||
- Inner anonymous <div> wrapping input+hint → <small> removes the wrapper
|
||
- <div class=admin-checkbox-list> + <label class=admin-checkbox-label> → <ul>/<li label>
|
||
- <div class=admin-submit-wrap> → remove, style button directly
|
||
- <div class=admin-alert> → <p role=alert> / <p role=status>
|
||
|
||
index.php (X):
|
||
- Stats <div> soup → <dl>/<dt>/<dd> (numbers as defined terms)
|
||
- Maintenance bar <div> → <aside role=status>
|
||
- Bulk toolbar → role=toolbar aria-label
|
||
- <th> cells missing scope=col
|
||
|
||
tags.php (XI):
|
||
- <th> cells missing scope=col; inline margins on sibling forms → CSS selector
|
||
|
||
thanks.php (XII):
|
||
- <div class=admin-thesis-info> already uses <dl> inside — wrap in <section> instead
|
||
|
||
account.php (XIII):
|
||
- Status rows <div> soup → <dl>/<dt>/<dd>
|
||
- Danger zone description <div> → <p>
|
||
- Inline margin on section title → CSS
|
||
|
||
login.php (XIV):
|
||
- No <main> landmark on the page
|
||
- Inline styles on form rows → modifier class
|
||
|
||
pages-edit.php (XV):
|
||
- <link> stylesheet injected inside <body> (invalid) — move to <head> via $extraCss
|
||
|
||
Summary table: 15 additional admin classes deletable via semantic replacements
|
||
|
||
---
|
||
bc5c50f1fb9a | 2026-03-26 22:53
|
||
docs: semantic HTML audit — add section I–VII to TODO.md
|
||
|
||
Full analysis of every public-facing page and partial against semantic HTML.
|
||
Currently only one semantic element exists across the entire public frontend (<nav>).
|
||
|
||
Key findings mapped to concrete replacements:
|
||
|
||
nav.php: <div class=site-nav__links> → <ul>/<li>; active class → aria-current=page
|
||
search <form> needs role=search, aria-label, hidden SVG icon
|
||
|
||
index.php: <div class=cards-container> → <ul>; card <div>s → <li>; <a> wraps directly
|
||
card__media → <figure> for image cards; pagination divs → <nav><ul>
|
||
disabled pagination links need aria-disabled + tabindex=-1, not just a class
|
||
|
||
search.php: filter label+div groups → <label> wrapping <select> (removes 2 classes per group)
|
||
.search-results-view wrapper → remove (redundant inside <main>)
|
||
results-grid <div> → <ul>; result-card__meta <span> → <small>
|
||
repertoire columns <div> → <section>; link lists → <ul>/<li>
|
||
active links → aria-current=page
|
||
|
||
tfe.php: heading hierarchy is backwards — author is h1, title is h2; should be reversed
|
||
.tfe-layout → <article>; .tfe-left → <header> inside article
|
||
.tfe-meta-list div+span soup → <dl>/<dt>/<dd> (removes ~30 wrapper divs + 5 classes)
|
||
.tfe-right → <aside>; .tfe-media-block → <figure>; caption → <figcaption>
|
||
.tfe-synopsis-text <div> → <p>; back link wrapper div → remove
|
||
|
||
apropos.php: .apropos-right <div> → <aside>; contact divs → <address>
|
||
section wrapper divs → <section>; two CSS classes → strong + a[href^=mailto:]
|
||
double-class .apropos-description.apropos-page-content → single .prose
|
||
|
||
licence.php: remove always-empty right column and two-column layout entirely
|
||
|
||
Summary table: 25+ classes that become deletable once semantic elements carry the meaning
|
||
|
||
---
|
||
7d836c165cab | 2026-03-26 22:51
|
||
docs: frontend & template audit — add sections D–H to TODO.md
|
||
|
||
Analysed all public pages, CSS files, and template partials. Found:
|
||
|
||
Template structure (D):
|
||
- <head> boilerplate duplicated across 5 public pages (no shared partial exists)
|
||
- Live-reload snippet copy-pasted into 6 files
|
||
- templates/header.php and templates/head.php are dead/orphaned files
|
||
- public/assets/icons.svg is a dead TrumboWYG sprite (never referenced, ~15 KB)
|
||
- admin_favicon.svg used as public favicon (misleading naming)
|
||
|
||
CSS (E):
|
||
- html/body reset block repeated in 4 page stylesheets; belongs in common.css
|
||
- @font-face missing font-display:swap (FOIT risk)
|
||
- Search pagination is fully inline-styled; home page already has .pagination-btn classes
|
||
- Multiple one-off inline styles across tfe.php, edit.php, index.php
|
||
- .site-nav__right is a CSS duplicate of .site-nav__link
|
||
- .site-nav__link--active applied in PHP but has no CSS rule (invisible active state)
|
||
|
||
Template logic (F):
|
||
- 429 rate-limit response is bare unstyled HTML
|
||
- apropos.php contacts/credits hardcoded (require code deploy to change)
|
||
- licence.php wastes half the viewport with an always-empty right column
|
||
|
||
Accessibility (G):
|
||
- <nav> has no aria-label; search <form> has no accessible name
|
||
- No <meta name=description> on any public page
|
||
- No Open Graph tags anywhere (blank previews when sharing thesis links)
|
||
|
||
Minor (H):
|
||
- thanks.php duplicates getThesisFiles() with a raw query
|
||
- admin/index.php stats broken when filters are active (PHP array_filter on subset)
|
||
|
||
---
|
||
72daf46c466b | 2026-03-26 22:42
|
||
docs: backend audit — add refactor & maintenance task list to TODO.md
|
||
|
||
Full analysis of PHP and SQLite layer covering:
|
||
|
||
Performance:
|
||
- WAL mode + cache_size pragma missing from Database constructor
|
||
- Separate is_published/year indexes force temp B-tree sort on every public query
|
||
- v_theses_full materialised as CTE on every query, indexes never used by view
|
||
- getAllPublishedTheses() runs full 15-join view just for the author name index
|
||
|
||
PHP / Database.php:
|
||
- 5 dead CRUD helpers (getOrientationId etc.) never called anywhere
|
||
- 13 alias methods doubling every lookup; pick canonical names and remove
|
||
- getPDO()/getConnection() leaking to 8 call-sites with raw SQL that belongs in DB layer
|
||
- Unparameterised query in edit.php line 155 (SQL injection, fix immediately)
|
||
- sanitize_string() HTML-escapes at write time — stores & in DB, breaks search/export
|
||
- Dead variable $problematique in formulaire.php (read from POST, never used)
|
||
- setThesisJury() has no transaction guard of its own
|
||
- DB config auto-detection silently uses test.db if file exists locally
|
||
|
||
Maintainability:
|
||
- edit.php (530 lines) mixes display + POST + file upload — extract action file
|
||
- Banner upload logic copy-pasted between formulaire.php and edit.php
|
||
- Junction-table loops open-coded in every action; add setThesisLanguages/Formats/Tags
|
||
- RateLimit writes a JSON file on every public request
|
||
- __wakeup() throws from public method (PHP 8 deprecation)
|
||
|
||
---
|
||
b12ae73e9104 | 2026-03-26 18:54
|
||
tests: fix SecurityTest fatal TypeError — update searchTheses call to use array params
|
||
|
||
SecurityTest::Test1 was calling $db->searchTheses($string) with a plain
|
||
string, but searchTheses() was refactored to require array $params when
|
||
the tag M2M work landed. This caused an immediate PHP fatal TypeError
|
||
before any SQL ever ran, killing the entire Security test suite with
|
||
exit code 255 and masking all three tests.
|
||
|
||
Fix: pass each malicious payload via ['query' => $string] which is the
|
||
correct API and properly exercises the parameterised query path through
|
||
validateSearchParams() + buildSearchConditions(). Added a clarifying
|
||
comment explaining why the array form is required.
|
||
|
||
All 4 test suites now pass:
|
||
- Database (Unit): 7/7
|
||
- Rate Limit (Unit): 5/5
|
||
- Search (Integration): 6/6
|
||
- Security: 3/3
|
||
|
||
---
|
||
e4be230a0480 | 2026-03-26 11:23
|
||
admin/system: add nginx config viewer tab
|
||
|
||
Add a 'nginx — config' tab to the Système admin page (system.php).
|
||
|
||
- Reads /etc/nginx/sites-available/posterg (live deployed config) first;
|
||
falls back to nginx/posterg.conf (local reference copy) when the live
|
||
path is inaccessible (e.g. in dev, or wrong permissions).
|
||
- Displays a colour-coded badge: green '● Config déployée' for live,
|
||
amber '⚠ Référence locale' for the fallback.
|
||
- Renders the full config in the shared .log-output code block with
|
||
line numbers (data-n gutter via CSS ::before) and lightweight nginx
|
||
syntax colouring (comments grey, block keywords purple, directives blue).
|
||
- Reuses the existing copy-to-clipboard button.
|
||
- Tab routing: activeTab validation extended to accept 'nginx_config';
|
||
log pre-loading guards skip when activeTab is 'nginx_config'.
|
||
- No remote execution: read-only, zero new attack surface.
|
||
|
||
---
|
||
45acadaa0af2 | 2026-03-24 18:23
|
||
Instructions pour DEV sur MACOS → DEV.md
|
||
|
||
---
|
||
37f3a07c6e51 | 2026-03-24 15:55
|
||
admin: merge status + logs into unified system.php with instant tabs
|
||
|
||
Replace the separate /admin/status.php and /admin/logs.php pages with a
|
||
single /admin/system.php page organised around a tab bar.
|
||
|
||
- system.php — top-level tab bar: 'Statut' + one tab per log file
|
||
(nginx accès, nginx erreurs, PHP-FPM). Switching tabs is a plain
|
||
href (?tab=…) so no JS required for navigation; the lines-selector
|
||
SELECT triggers a location change on 'change' for instant reload
|
||
without a submit button.
|
||
- Status tab preserves all existing service cards, PHP runtime grid,
|
||
and disk-usage bar from the old status.php.
|
||
- Log tabs preserve line-count selector, file metadata bar, and
|
||
per-line colour coding from the old logs.php.
|
||
- New: copy-to-clipboard button on each log output block (Clipboard
|
||
API with textarea execCommand fallback).
|
||
- status.php / logs.php replaced with 301 redirect stubs so existing
|
||
bookmarks and links keep working.
|
||
- templates/admin/head.php: 'Statut' + 'Journaux' nav items replaced
|
||
with a single 'Système' item; active state covers all three page
|
||
names for redirect compatibility.
|
||
|
||
---
|
||
ea48f4a5f582 | 2026-03-24 15:52
|
||
todo: add system page merge + logs tab/copy tasks
|
||
|
||
---
|
||
20a633c0e2f7 | 2026-03-24 15:52
|
||
Add admin account page for PHP password management
|
||
|
||
Implements the admin user management UI as a self-contained PHP password
|
||
change/set flow — no SSH or sudo required.
|
||
|
||
- public/admin/account.php: shows auth status (PHP hash present, credentials
|
||
file path), password change form (requires current password when one exists,
|
||
min 12 chars, confirm field), and a danger-zone form to delete the
|
||
credentials file entirely
|
||
- public/admin/actions/account.php: CSRF-guarded POST handler; verifies
|
||
current password via AdminAuth::login() before accepting a new one;
|
||
generates bcrypt (cost 12) hash; writes config/admin_credentials.php
|
||
atomically via a temp file + rename; regenerates session on success;
|
||
redirects to /admin/login.php when credentials are deleted
|
||
- templates/admin/head.php: 'Compte' nav link added (active on account.php)
|
||
- public/assets/admin.css: .admin-account-status, .admin-section-title,
|
||
.admin-field-hint, .admin-danger-zone component styles added
|
||
|
||
Note: the nginx htpasswd flow (manage-admin-users.sh) requires root on the
|
||
server and is intentionally kept as a CLI-only operation.
|
||
|
||
---
|
||
020bfa5a3362 | 2026-03-24 15:47
|
||
admin: add server log viewer; fix curl_close() PHP 8.5 deprecation in status.php
|
||
|
||
- public/admin/logs.php: new page tailing nginx error/access + PHP-FPM logs.
|
||
Selector for log file and line count (50/100/200/500, default 100).
|
||
Lines reversed (newest first), colour-coded by severity, numbered gutter.
|
||
Graceful degradation when exec() unavailable or file unreadable (dev msg).
|
||
|
||
- templates/admin/head.php: 'Journaux' nav link added after 'Statut'.
|
||
|
||
- public/admin/status.php: remove curl_close() call deprecated in PHP 8.5
|
||
(no-op since PHP 8.0); replace with unset($ch) to silence the warning
|
||
that was leaking raw text above the page output.
|
||
|
||
---
|
||
c678b7549471 | 2026-03-24 15:41
|
||
Add admin server status page
|
||
|
||
New page /admin/status.php gives a real-time health dashboard:
|
||
|
||
- Services panel: nginx (systemctl), php-fpm (auto-detects versioned unit names),
|
||
site HTTP ping (curl HEAD with latency), SQLite DB (exists/writable/row count/size),
|
||
storage directory (writable, banner/cover file counts), maintenance-mode flag.
|
||
- PHP runtime panel: version, SAPI, memory_limit, upload_max_filesize, post_max_size,
|
||
max_execution_time.
|
||
- Disk usage bar for the partition containing APP_ROOT (colour-coded: green/amber/red).
|
||
- All shell calls go through safeExec() which suppresses stderr and checks exit code;
|
||
systemctl/curl unavailability degrades gracefully to 'unknown' without fatal errors.
|
||
- 'Statut' nav link added to templates/admin/head.php (active state on status.php).
|
||
|
||
---
|
||
ed2b06a34c5a | 2026-03-24 15:39
|
||
feat: cover image fallback for home grid cards
|
||
|
||
- index.php: batch-load thesis_files covers for theses without banner_path
|
||
- Resolution order: banner_path → cover file → gradient placeholder
|
||
- Uses single IN() query to avoid N+1 problem
|
||
|
||
---
|
||
372abb5cd624 | 2026-03-24 15:38
|
||
feat: tag management tests, maintenance mode polish, répertoire pagination fix
|
||
|
||
- tests/Unit/DatabaseTest.php: tests 5-7 for findOrCreateTag round-trip, getUsedTags column, alias
|
||
- tests/Integration/SearchTest.php: tests 4-6 for tag subquery, full-text query, count consistency
|
||
- Database: getAllPublishedTheses() bypasses 100-row search cap for student index
|
||
- search.php: uses getAllPublishedTheses() for étudiantes column; all tests pass
|
||
|
||
---
|
||
92e344b757cf | 2026-03-24 15:35
|
||
feat: admin tag management, maintenance mode, TFE visibility states
|
||
|
||
Tags admin:
|
||
- Database: getAllTagsWithCount(), renameTag(), mergeTag(), deleteTag()
|
||
- public/admin/tags.php: table with inline rename/merge/delete forms, CSRF-guarded
|
||
- public/admin/actions/tag.php: routes on action=rename|merge|delete
|
||
- templates/admin/head.php: 'Mots-clés' nav link
|
||
- admin.css: admin-inline-form, admin-btn--sm/warning/danger variants
|
||
|
||
Maintenance mode:
|
||
- config/bootstrap.php: gate on MAINTENANCE_FLAG file; admin/ and maintenance.php exempt
|
||
- public/maintenance.php: 503 dark minimal page
|
||
- public/admin/actions/maintenance.php: enable/disable toggle
|
||
- public/admin/index.php: status bar with toggle button
|
||
- admin.css: admin-maintenance-bar styles
|
||
|
||
TFE Visibility (Libre/Interne/Interdit via existing access_type_id):
|
||
- migration 002_add_visibility.sql: seeds access_types if missing
|
||
- Database: setVisibility(), bulkSetVisibility(), getAccessTypes()
|
||
- public/media.php: blocks thesis files for access_type_id=3
|
||
- public/tfe.php: shows access_type, context_note; hides file panel for Interdit
|
||
- public/admin/edit.php: access_type_id select + context_note textarea; saves both
|
||
- public/admin/index.php: three-state badge (Libre/Interne/Interdit) per row
|
||
- public/admin/actions/visibility.php: single + bulk visibility action handler
|
||
- admin.css: status-access badge variants
|
||
|
||
---
|
||
0933137540a6 | 2026-03-24 13:30
|
||
refactor: rename keywords→tags M2M (migration 001)
|
||
|
||
- migration 001_rename_keywords_to_tags.sql: CREATE tags/thesis_tags from keywords/thesis_keywords,
|
||
copy data, drop old tables, rebuild indexes and views
|
||
- schema.sql: tags table, thesis_tags junction, updated indexes and v_theses_full/v_theses_public
|
||
- Database.php: findOrCreateTag(), getUsedTags() with proper JOIN; backwards-compat aliases;
|
||
buildSearchConditions uses EXISTS subquery on thesis_tags+tags with vp. alias throughout
|
||
- admin/actions/formulaire.php: INSERT OR IGNORE INTO thesis_tags
|
||
- admin/edit.php: DELETE FROM thesis_tags + findOrCreateTag
|
||
- search.php: $kw['name'] (was $kw['keyword'])
|
||
- fixtures/CreateTestDatabase.php: tags/thesis_tags table names
|
||
|
||
---
|
||
cefceb046c7a | 2026-03-24 13:25
|
||
feat: jury composition + banner image upload
|
||
|
||
- migration 004: thesis_supervisors.role + is_external; view adds jury_president/jury_promoteurs/jury_lecteurs
|
||
- migration 005: theses.banner_path; view exposes t.banner_path and t.license_id
|
||
- Database: getThesisJury(), setThesisJury(), setBannerPath()
|
||
- admin/add.php: jury fieldset (président/promoteur/lecteurs + externe checkboxes, JS add/remove rows); banner file input
|
||
- admin/edit.php: jury fieldset pre-populated from DB; banner preview + remove checkbox + upload; multipart form
|
||
- admin/actions/formulaire.php: parse jury fields → setThesisJury(); banner upload to banners/
|
||
- tfe.php: three conditional jury rows (président·e, promoteur·ice, lecteur·ices)
|
||
- schema.sql: updated thesis_supervisors, theses, v_theses_full, v_theses_public definitions
|
||
- admin.css: fieldset, jury-row, jury-entry, btn-remove styles
|
||
|
||
---
|
||
d87348c388c4 | 2026-03-24 13:12
|
||
feat: licence page, admin pages editor, license types, gradient card placeholders, latest-year home view
|
||
|
||
- Feature 1: public /licence.php fetches 'licenses' page from DB, renders Markdown
|
||
- Feature 1: nav.php adds 'Licence' link with active state
|
||
- Feature 2: Database::getPage(), savePage(), getAllPages() methods
|
||
- Feature 2: bundled src/Parsedown.php (MIT, zero-dependency)
|
||
- Feature 2: apropos.php now renders 'about' page content from DB via Parsedown
|
||
- Feature 2: admin/pages.php (list) + admin/pages-edit.php (EasyMDE editor)
|
||
- Feature 2: admin/actions/page.php (auth+CSRF+validation+save)
|
||
- Feature 2: admin/head.php adds 'Pages statiques' nav link
|
||
- Feature 3: storage/schema.sql seeds 8 CC license types
|
||
- Feature 3: storage/migrations/003_seed_license_types.sql (applied to live DB)
|
||
- Feature 3: Database::getLicenseTypes() / getAllLicenseTypes()
|
||
- Feature 3: admin/add.php + formulaire.php: license_id field on add form
|
||
- Feature 3: admin/edit.php: license_id field on edit form with raw FK lookup
|
||
- Feature 3: tfe.php: shows 'Licence :' meta row when non-null
|
||
- Feature 6: main.css: .card__media--gradient styles
|
||
- Feature 6: index.php: deterministic HSL gradient placeholder cards
|
||
- Feature 6: Database::getLatestYearTheses() + getLatestPublishedYear()
|
||
- Feature 6: index.php default home = random latest-year theses with info label
|
||
|
||
---
|
||
86a2082edcfe | 2026-03-24 12:55
|
||
docs: add feature tasks for licence page, admin WYSIWYG, jury section, banner upload, and home randomisation
|
||
|
||
---
|
||
f8a4bfb612f0 | 2026-03-24 12:40
|
||
docs: add maintenance mode + TFE visibility tasks to TODO
|
||
|
||
---
|
||
4131fc07e9bc | 2026-03-23 11:03
|
||
docs: add admin tag management UI task to TODO
|
||
|
||
---
|
||
6d2c50f0b983 | 2026-03-23 11:00
|
||
docs: add M2M tags refactor task proposal to TODO
|
||
|
||
---
|
||
46040328a42a | 2026-03-11 12:39
|
||
add flake.nix for Nix PHP dev shell
|
||
|
||
---
|
||
7208292c0e47 | 2026-03-02 15:51
|
||
deploy-nginx: add recipe, upload scripts to /tmp, print sudo instructions
|
||
|
||
---
|
||
5e1543e9a8ac | 2026-03-02 15:46
|
||
nginx: relax admin rate limit to 60r/m burst=20 (was 10r/m burst=5)
|
||
|
||
---
|
||
1fb9644d5a7c | 2026-03-02 15:43
|
||
fix favicon 404s: add <link rel=icon> to all pages, nginx 204 for /favicon.ico
|
||
|
||
---
|
||
e4b2205eac65 | 2026-03-02 15:32
|
||
fix rsync permissions: setup-server.sh with setgid dirs, exclude .claude/.pi
|
||
|
||
---
|
||
52978aa658ec | 2026-03-02 15:24
|
||
ops: simplify justfile, guard deploy-db, extract scripts, fix .gitignore
|
||
|
||
---
|
||
2110d2b916f5 | 2026-02-24 23:34
|
||
Redesign UI to match target design images
|
||
|
||
- Flat purple-gradient nav bar with POSTERG/RÉPERTOIRE/À PROPOS links
|
||
- Full-width search bar with icon, bottom-border only, below nav
|
||
- Home: white bg, media card grid (thumbnail + author/title label below)
|
||
- Répertoire: 4-column index (Années/Catégories/Étudiantes/Mots-clés)
|
||
- TFE: 2-column layout (large text left, media right)
|
||
- À Propos: 2-column, large monospace text, new apropos.php page
|
||
- Admin: dark theme (#1a1a1a), purple gradient nav, bottom-border inputs
|
||
- New shared partials: templates/nav.php, templates/search-bar.php
|
||
- Rewrote all CSS: common, main, search, tfe, apropos, admin
|
||
|
||
---
|
||
eaad74057464 | 2026-02-24 23:21
|
||
refactor: extract buildSearchConditions, add getThesesList, remove dead code, fix SearchTest
|
||
|
||
- Database: extract private buildSearchConditions(array $params): array shared by
|
||
searchTheses() and countSearchResults(), eliminating ~80 lines of duplication;
|
||
add array type hints to both public methods
|
||
- Database: add getThesesList(array $filters) and getAllYears() so admin/index.php
|
||
no longer builds raw SQL inline
|
||
- admin/index.php: replace inline PDO query block with $db->getThesesList() /
|
||
$db->getAllYears(); drop the now-unused $pdo local
|
||
- config/bootstrap.php: remove dead include_template() helper and the
|
||
vendor/autoload.php Composer stub (no vendor/ directory exists)
|
||
- apps/: delete entire directory (leftover artefact, no code references it)
|
||
- tests/Integration/SearchTest.php: fix three searchTheses() calls from bare
|
||
strings to proper array params to match the method signature (prevented TypeError)
|
||
|
||
---
|
||
d30153871fe5 | 2026-02-24 23:19
|
||
fix: resolve broken lib/ require paths in admin and normalise modern-normalize to .min.css
|
||
|
||
---
|
||
da53d567443a | 2026-02-24 23:11
|
||
analysis: dependency audit and refactoring task proposals in TODO.md
|
||
|
||
---
|
||
73c27a067dac | 2026-02-12 13:23
|
||
Make search page header more compact and fix layout structure
|
||
|
||
- Reduce all spacing and padding in header for more compact fit
|
||
- Fix back button overflow by removing width: 100% and adding overflow handling
|
||
- Make filter section more compact with smaller fonts and spacing
|
||
- Add main-wrapper div to group main and footer
|
||
- Keep rounded corners (40px) on all three sections like main.css
|
||
- Footer stays at bottom of main content area
|
||
- Fix HTML structure: footer outside main, both inside wrapper
|
||
|
||
---
|
||
bc98df4993cd | 2026-02-12 13:22
|
||
Improve search page with denser header and filter layout
|
||
|
||
- Transform header into compact search bar with back button
|
||
- Move filters panel underneath search bar (collapsible)
|
||
- Display results in grid layout matching main.css style
|
||
- Add pagination controls in main section
|
||
- Show result count in footer
|
||
- Prevent overflow with responsive design and proper flex constraints
|
||
- Reduce padding and font sizes for denser layout
|
||
|
||
---
|
||
061b2b540e82 | 2026-02-12 13:12
|
||
Improve card layout: move pagination inside main, add responsive grid (3 rows × 4 cols = 12 items), display keywords as tags, optimize text sizes and spacing
|
||
|
||
---
|
||
73b0093b26ea | 2026-02-12 12:46
|
||
feat: rename memoire to tfe and improve styling
|
||
|
||
- Rename memoire.php to tfe.php throughout codebase
|
||
- Create dedicated tfe.css with rounded header/main/footer layout
|
||
- Move metadata (orientation, AP program, finality, keywords) to header
|
||
- Move back button from header to footer
|
||
- Create shared templates/head.php for common HTML head section
|
||
- Maintain rounded borders (40px) matching main site design
|
||
- Keep purple header (#9557b5), green main (#3c856b), dark footer (#222)
|
||
- Improve content readability with centered max-width layout
|
||
- Add responsive design for mobile devices
|
||
|
||
---
|
||
9f6147577bb0 | 2026-02-12 12:30
|
||
refactor: improve layout ratios and pagination UI
|
||
|
||
Layout improvements:
|
||
- Fixed header/main/footer ratios to 2:5, 3:5, 1:5 using flex
|
||
- Default to sans-serif font system stack
|
||
- Made sections properly flex-based instead of viewport height
|
||
|
||
Pagination improvements:
|
||
- First/previous/next/last navigation buttons (‹‹ ‹ › ››)
|
||
- Current page highlighted in colored badge
|
||
- Disabled state for unavailable actions
|
||
- Clean rounded button design with hover effects
|
||
- Proper spacing and visual hierarchy
|
||
|
||
Card styling:
|
||
- Better typography hierarchy
|
||
- Hover effects (lift + shadow)
|
||
- Improved spacing and readability
|
||
- Year displayed in brand color
|
||
|
||
Tests passing ✅
|
||
|
||
---
|
||
9511bb93b501 | 2026-02-12 12:26
|
||
feat: add year filter to main index
|
||
|
||
- Footer now displays all available years horizontally with scroll
|
||
- Click on year filters thesis list to that year
|
||
- Active year highlighted in footer
|
||
- 'Tous' link to reset filter
|
||
- Filter info banner shows when year selected with reset button
|
||
- Pagination preserves year filter
|
||
- Styled with horizontal scroll, smooth scrollbar
|
||
- Tests passing ✅
|
||
|
||
---
|
||
942a93a3ad14 | 2026-02-12 12:20
|
||
refactor: update nginx config for new structure
|
||
|
||
- Updated posterg.conf with new directory structure
|
||
- Document root: /var/www/posterg/public
|
||
- Explicitly deny access to: /src, /templates, /config, /storage, /tests, /scripts, /docs
|
||
- Added structure diagram in comments
|
||
- Updated deploy scripts security checks
|
||
- Replaced outdated posterg.conf.reference
|
||
|
||
All non-public directories outside webroot for security.
|
||
Defense-in-depth: explicit deny rules even though paths outside /public.
|
||
|
||
---
|
||
87971f9c2318 | 2026-02-12 12:15
|
||
refactor: extract templates from public/
|
||
|
||
- Created /templates for main site (header.php, footer.php)
|
||
- Created /templates/admin for admin section (head.php, footer.php)
|
||
- Removed /public/includes and /public/admin/inc
|
||
- Updated all references in code and docs
|
||
- Tests passing ✅
|
||
|
||
Cleaner separation: /public only contains web-accessible files (PHP entry points + assets)
|
||
|
||
---
|
||
7fca85d1c12a | 2026-02-12 12:12
|
||
refactor: rename database → storage
|
||
|
||
More semantically accurate: contains SQLite files, schema, fixtures, test data.
|
||
Updated all references in code, scripts, docs.
|
||
|
||
---
|
||
0e4921583e45 | 2026-02-12 12:11
|
||
refactor: reorganize to standard PHP structure
|
||
|
||
- Moved /lib → /src (PHP source code)
|
||
- Moved /includes → /public/includes (main site templates)
|
||
- Admin section remains self-contained in /public/admin with its own /inc
|
||
- Updated all require/include paths across codebase
|
||
- Updated config/bootstrap.php, justfile, tests, docs
|
||
- All tests passing ✅
|
||
|
||
Structure now follows PHP best practices:
|
||
/config - Configuration files
|
||
/database - SQLite database + schema
|
||
/docs - Documentation (intact)
|
||
/nginx - Server config (intact)
|
||
/public - Web-accessible files (entry point)
|
||
/admin - Self-contained admin interface
|
||
/assets - CSS, fonts, icons
|
||
/includes - Main site templates (header/footer)
|
||
/scripts - Deployment scripts (intact)
|
||
/src - PHP source classes (Database, AdminAuth, RateLimit)
|
||
/tests - Test suites
|
||
|
||
---
|
||
0b650cd3e727 | 2026-02-12 10:37
|
||
Work on the admin section styling
|
||
|
||
---
|
||
8613f7111253 | 2026-02-08 14:12
|
||
security: add PHP session auth guard for admin panel (item #2, CRITICAL)
|
||
|
||
- lib/AdminAuth.php: new class with requireLogin(), login(), logout(),
|
||
isAuthenticated(); starts session with hardened cookie params
|
||
(HttpOnly, SameSite=Strict, Secure, Path=/admin) — also resolves
|
||
item #8 (session cookie hardening)
|
||
- requireLogin() auto-authenticates from nginx Basic Auth credentials
|
||
($_SERVER['PHP_AUTH_PW']) so the user only sees one browser prompt;
|
||
falls back to /admin/login.php if the proxy is absent/misconfigured
|
||
- config/admin_credentials.php: gitignored credential store; define
|
||
ADMIN_PASSWORD_HASH with a bcrypt hash to enable PHP auth
|
||
- config/admin_credentials.example.php: template for the above
|
||
- config/bootstrap.php: auto-loads admin_credentials.php if present
|
||
- .gitignore: exclude config/admin_credentials.php
|
||
- public/admin/login.php: fallback login form (shown only when nginx
|
||
Basic Auth is bypassed / proxy absent)
|
||
- public/admin/logout.php: session destruction + redirect to login
|
||
- All 7 admin PHP files: replace session_start() with
|
||
AdminAuth::requireLogin() (defence-in-depth behind nginx Basic Auth)
|
||
- public/admin/inc/head.php: Déconnexion button when ADMIN_PASSWORD_HASH
|
||
is defined
|
||
- nginx/PHP_AUTH_LAYER.md: documents dual-auth architecture, UX flow,
|
||
and setup instructions
|
||
- docs/TODO.SECURITY.md: items #2 and #8 moved to Resolved; priority
|
||
order updated (all CRITICAL done)
|
||
|
||
---
|
||
a2b1ff5f4148 | 2026-02-08 14:01
|
||
security: fix all HIGH priority items from TODO.SECURITY.md
|
||
|
||
Items resolved:
|
||
- #3 (HIGH): Move file uploads outside webroot to STORAGE_ROOT (/var/www/posterg/storage).
|
||
Uploads were previously stored in public/admin/actions/data/ which is web-accessible.
|
||
- #4 (HIGH): Align file paths and add media.php controller.
|
||
DB paths are now storage-relative (theses/YEAR/ID/file, covers/file).
|
||
New public/media.php serves files with path-traversal jail, MIME allow-list,
|
||
and proper caching headers. memoire.php and search.php updated to use /media.php?path=.
|
||
Also fixed: cover images were never recorded in thesis_files (broken INSERT).
|
||
- #5 (HIGH): RateLimit::getClientIdentifier() now uses REMOTE_ADDR only.
|
||
HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP are attacker-controlled headers that
|
||
allowed unlimited rate-limit bypass by rotating spoofed IPs.
|
||
- #6 (HIGH): Port public/admin/.htaccess security rules to nginx/posterg.conf.
|
||
Apache .htaccess directives are silently ignored by nginx; none were active.
|
||
CSP added to /admin/ location block, .log file denial added globally,
|
||
autoindex off made explicit. Documented in nginx/HTACCESS_TO_NGINX.md.
|
||
|
||
Supporting changes:
|
||
- config/bootstrap.php: add STORAGE_ROOT constant
|
||
- nginx/SECURITY_HEADERS.md: updated to reflect admin CSP and pending public CSP
|
||
- docs/TODO.SECURITY.md: items #3-6 moved to resolved; priority order updated
|
||
|
||
---
|
||
f5d3281c4301 | 2026-02-08 11:58
|
||
security: fix all LOW priority items from TODO.SECURITY.md
|
||
|
||
Item 13 — Remove deprecated X-XSS-Protection header
|
||
- nginx/posterg.conf: header removed (was '1; mode=block')
|
||
- nginx/SECURITY_HEADERS.md: new file documenting header decisions
|
||
and explaining why X-XSS-Protection is counterproductive
|
||
|
||
Item 14 — Add rel="noreferrer" to external target="_blank" link
|
||
- public/admin/thanks.php: rel="noopener" → rel="noopener noreferrer"
|
||
|
||
Item 15 — Explicit (int) casts on all integer HTML outputs
|
||
- public/index.php: (int) on item id, page numbers
|
||
- public/search.php: (int) on totalItems, year options, item id, pagination
|
||
|
||
Item 16 — Remove unused DATABASE_PATH constant
|
||
- config/bootstrap.php: define('DATABASE_PATH', ...) removed
|
||
|
||
docs/TODO.SECURITY.md updated: items 13-16 marked resolved and
|
||
moved to the ✅ Resolved section.
|
||
|
||
---
|
||
94d110438fd8 | 2026-02-06 14:31
|
||
docs: rewrite admin panel README
|
||
|
||
---
|
||
df611b0333fe | 2026-02-06 14:31
|
||
admin: unify templates, dynamic navigation, and PHP cleanup
|
||
|
||
---
|
||
52decc3e5fea | 2026-02-06 14:31
|
||
admin css: expand stylesheet with reusable component classes
|
||
|
||
---
|
||
87f0838b5d43 | 2026-02-06 14:31
|
||
dev: add live-reload development server
|
||
|
||
---
|
||
52f8e267e5a6 | 2026-02-06 14:31
|
||
admin: restructure action scripts to actions/ subdirectory
|
||
|
||
---
|
||
f7132ecb7d0f | 2026-02-06 13:26
|
||
CSS fixup
|
||
|
||
---
|
||
4bbbc58e24ee | 2026-02-06 12:14
|
||
Fix admin CSS not loading and quirks mode issues
|
||
|
||
Fixed multiple issues in admin panel:
|
||
|
||
1. CSS path: modern-normalize.css → modern-normalize.min.css
|
||
(File is actually named .min.css)
|
||
|
||
2. Icon path: assets/icon.svg → /assets/admin_favicon.svg
|
||
(Was relative, now absolute; correct filename)
|
||
|
||
3. Navigation: /admin/list.php → /admin/
|
||
(list.php was renamed to index.php)
|
||
|
||
4. Short PHP tags: <? → <?php
|
||
(Better compatibility, some servers don't enable short_open_tag)
|
||
|
||
5. Quirks mode warning was due to CSS not loading, not DOCTYPE
|
||
(DOCTYPE was already present)
|
||
|
||
Files modified:
|
||
- public/admin/inc/head.php (main fixes)
|
||
- public/admin/index.php (short tags)
|
||
- public/admin/add.php (short tags)
|
||
- public/admin/import.php (short tags)
|
||
|
||
Need to redeploy for production: just deploy
|
||
|
||
---
|
||
e789c286de1b | 2026-02-06 11:33
|
||
Refactor admin panel and add migration documentation
|
||
|
||
- Add comprehensive migration guides (DEPLOYMENT_MIGRATION.md, DIRECTORY_STRUCTURE.md, MIGRATION_CHECKLIST.md)
|
||
- Refactor admin panel: split add.php, create reusable header/footer
|
||
- Update styles: admin.css, common.css, main.css
|
||
- Improve public pages: index.php, memoire.php
|
||
- Reorganize database documentation into database/docs/
|
||
- Update .gitignore and justfile
|
||
|
||
This prepares for migration to public/ directory structure
|
||
|
||
---
|
||
d2b3c6ca6711 | 2026-02-05 20:07
|
||
Major refactor
|
||
|
||
- update the structure to have monolithic setup
|
||
- updated deployments
|
||
- added live-reloading for devops
|
||
|
||
---
|
||
f23fbb481b64 | 2026-02-05 17:33
|
||
Nginx config, working deploy, basic theme, repo cleanup
|
||
|
||
---
|
||
2cb543664730 | 2026-02-02 18:56
|
||
Added Claude assessements
|
||
|
||
---
|
||
467aced73476 | 2026-01-28 10:24
|
||
Restructure repository and implement secure search feature
|
||
|
||
Phase 1: Consolidate shared infrastructure
|
||
- Create shared/ directory for common code
|
||
- Consolidate Database.php from front-backend and formulaire into unified shared/Database.php
|
||
- Smart path detection for test.db vs posterg.db
|
||
- Secure search with wildcard escaping and input validation
|
||
- Support both singleton and direct instantiation patterns
|
||
- Full CRUD methods for admin functionality
|
||
- Move RateLimit.php to shared/ (30 requests/min)
|
||
- Update all require paths across apps to use shared/
|
||
|
||
Phase 2: Reorganize directory structure
|
||
- Rename front-backend/ → apps/public/
|
||
- Rename formulaire/ → apps/admin/
|
||
- Rename db/ → database/
|
||
- Update all file paths for new structure
|
||
- Create root .gitignore excluding databases, cache, logs
|
||
|
||
Implement secure search feature
|
||
- Add apps/public/search.php with full-text search across theses
|
||
- Search filters: query, year, orientation, AP program, keywords
|
||
- Security features:
|
||
- SQL injection prevention (prepared statements)
|
||
- Wildcard injection prevention (escape % and _)
|
||
- Input validation (max 200 chars, year range 1900-2100)
|
||
- Rate limiting (30 req/min per IP)
|
||
- Pagination limited to 100 results/page
|
||
- XSS protection (htmlspecialchars on output)
|
||
|
||
Add comprehensive test suite
|
||
- Create apps/public/tests/ with proper structure
|
||
- tests/Integration/SearchTest.php - 12 search scenarios
|
||
- tests/Security/SecurityTest.php - vulnerability testing
|
||
- tests/Unit/RateLimitTest.php - rate limit behavior
|
||
- Create database/fixtures/CreateTestDatabase.php
|
||
- Add apps/public/run-tests.php test runner
|
||
- All tests passing (4/4 suites)
|
||
|
||
Update deployment configuration
|
||
- Rename justfile 'sync' recipe to 'deploy'
|
||
- Create deploy group with separate deploy-public and deploy-admin
|
||
- Add test-deploy recipe for test database
|
||
- Exclude *.db, tests/, cache/, *.md from production deploy
|
||
- Deploy shared/ to both public and admin locations
|
||
|
||
Stats: +4482 insertions, -654 deletions across 72 files
|
||
|
||
---
|
||
95f52d549e85 | 2026-01-27 15:43
|
||
Add comprehensive thesis management system with database migration
|
||
|
||
This commit introduces a complete thesis management interface and migrates
|
||
the system from YAML-based storage to SQLite:
|
||
|
||
Core Changes:
|
||
- Add Database.php helper class with PDO connection and entity management
|
||
- Add list.php for viewing all theses with filtering and sorting
|
||
- Add edit.php for modifying existing thesis records
|
||
- Add import.php for migrating legacy YAML data to SQLite
|
||
- Add justfile with development tasks (serve, init-test-db, etc.)
|
||
|
||
Documentation:
|
||
- Add MIGRATION.md with complete migration guide and architecture docs
|
||
- Update README.md with database setup and Just recipe instructions
|
||
- Update .gitignore to exclude test databases and error logs
|
||
|
||
Modified Forms:
|
||
- Enhanced formulaire.php with transaction-based SQLite processing
|
||
- Updated index.php with database-driven form options
|
||
- Improved thanks.php to read from database views
|
||
|
||
The new architecture provides:
|
||
- Normalized database schema (19 tables, 2 views)
|
||
- Transaction safety and referential integrity
|
||
- CRUD operations for thesis management
|
||
- Filtering by year, orientation, AP program, publication status
|
||
- Secure file handling with metadata tracking
|
||
|
||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
||
---
|
||
99ccd60f9007 | 2026-01-27 15:19
|
||
Add SQLite database schema and documentation
|
||
|
||
Added complete database schema for Post-ERG thesis archive:
|
||
- schema.sql with full relational database structure
|
||
- README.md with schema documentation and usage examples
|
||
- SETUP.md with comprehensive setup and maintenance guide
|
||
- posterg_fiche-technique.md with technical specifications
|
||
- Database_TFE_test.csv and .ods with example data
|
||
|
||
Database features:
|
||
- Normalized relational schema (3NF)
|
||
- Support for multiple authors, supervisors, languages, formats, keywords
|
||
- Publication workflow (submission → defense → jury review → publication)
|
||
- Access control (Libre/Interne/Interdit)
|
||
- File attachments tracking
|
||
- Predefined reference tables for orientations, AP programs, finalities
|
||
- Views for simplified querying
|
||
- Automatic timestamps and cascade deletes
|
||
|
||
---
|
||
0d3fc3ab9a9a | 2026-01-26 20:02
|
||
Added justfile recipe file and the .gitignore
|
||
|
||
---
|
||
14c82cfeed27 | 2026-01-26 15:44
|
||
Mise à jours et création de READMEs dédier.
|
||
|
||
---
|
||
2d343eee06b2 | 2026-01-26 15:24
|
||
Organize into monorepo structure with resolved conflicts
|
||
|
||
---
|
||
37ec8aa5c1a2 | 2026-01-26 15:45
|
||
Prepare formulaire branch for merge
|
||
|
||
---
|
||
0697753dd345 | 2026-01-26 15:24
|
||
Move formulaire files to formulaire/
|