Commit Graph

19 Commits

Author SHA1 Message Date
Pontoporeia
b8529f7abe 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
2026-03-28 16:52:45 +01:00
Pontoporeia
18197bd468 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).
2026-03-28 16:49:09 +01:00
Pontoporeia
640d37936f 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
2026-03-28 16:44:35 +01:00
Pontoporeia
7d96a08324 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.
2026-03-28 13:35:43 +01:00
Pontoporeia
2e277b104e 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).
2026-03-28 11:35:23 +01:00
Pontoporeia
42af4644c5 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).
2026-03-27 13:45:01 +01:00
Pontoporeia
372abb5cd6 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
2026-03-24 15:38:36 +01:00
Pontoporeia
0933137540 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
2026-03-24 13:30:53 +01:00
Pontoporeia
1fb9644d5a fix favicon 404s: add <link rel=icon> to all pages, nginx 204 for /favicon.ico 2026-03-02 16:08:45 +01:00
Pontoporeia
2110d2b916 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
2026-02-24 23:34:17 +01:00
Pontoporeia
d30153871f fix: resolve broken lib/ require paths in admin and normalise modern-normalize to .min.css 2026-02-24 23:19:18 +01:00
Théophile Gervreau-Mercier
73c27a067d 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
2026-02-12 13:41:17 +01:00
Théophile Gervreau-Mercier
bc98df4993 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
2026-02-12 13:22:09 +01:00
Théophile Gervreau-Mercier
73b0093b26 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
2026-02-12 12:46:51 +01:00
Théophile Gervreau-Mercier
87971f9c23 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)
2026-02-12 12:15:41 +01:00
Théophile Gervreau-Mercier
0e4921583e 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
2026-02-12 12:11:16 +01:00
Théophile Gervreau-Mercier
a2b1ff5f41 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
2026-02-08 14:01:45 +01:00
Théophile Gervreau-Mercier
f5d3281c43 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.
2026-02-08 13:54:02 +01:00
Théophile Gervreau-Mercier
4bbbc58e24 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
2026-02-06 13:26:24 +01:00