Commit Graph

22 Commits

Author SHA1 Message Date
Pontoporeia
4ff959a72d fix template consolidation: admin/head.php wraps public/head.php, footer.php wired to all public pages, remove duplicate font-family and body reset 2026-04-01 15:55:12 +02:00
Pontoporeia
338782947c 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.
2026-03-31 15:44:48 +02:00
Pontoporeia
986945a347 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)
2026-03-31 15:28:47 +02:00
Pontoporeia
ac872c1fe0 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.
2026-03-29 16:13:02 +02:00
Pontoporeia
3a8ffa6afe 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.
2026-03-29 15:43:21 +02:00
Pontoporeia
1dee1ea73f 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.
2026-03-28 19:38:21 +01:00
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
1181cfa88b 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
2026-03-28 13:32:34 +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
ed2b06a34c 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
2026-03-24 15:39:23 +01:00
Pontoporeia
d87348c388 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
2026-03-24 13:12:48 +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
Théophile Gervreau-Mercier
061b2b540e 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 2026-02-12 13:12:00 +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
9f6147577b 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 
2026-02-12 12:30:40 +01:00
Théophile Gervreau-Mercier
9511bb93b5 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 
2026-02-12 12:26:32 +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
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