mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 03:29:19 +02:00
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
This commit is contained in:
151
TODO.md
151
TODO.md
@@ -41,14 +41,11 @@
|
||||
|
||||
Create a public-facing `/licence.php` page, styled consistently with `apropos.php`.
|
||||
|
||||
- [ ] **`public/licence.php`** — new public page; fetches content from `pages` table
|
||||
(slug `'licenses'`); renders with `nl2br` / Markdown; uses `apropos.css` layout
|
||||
or a new `licence.css` if divergent styling needed
|
||||
- [ ] **`templates/nav.php`** — add "Licence" link between "Répertoire" and "À Propos"
|
||||
(or after "À Propos"); apply `site-nav__link--active` when `$currentNav === 'licence'`
|
||||
- [ ] The `pages` table already has an `INSERT OR IGNORE` seed for slug `'licenses'`
|
||||
in `storage/schema.sql` — no schema change needed here; verify the row exists
|
||||
in the live DB and add a migration if not
|
||||
- [x] **`public/licence.php`** — new public page; fetches content from `pages` table
|
||||
(slug `'licenses'`); renders with Parsedown Markdown; uses `apropos.css` layout
|
||||
- [x] **`templates/nav.php`** — add "Licence" link between "Répertoire" and "À Propos";
|
||||
apply `site-nav__link--active` when `$currentNav === 'licence'`
|
||||
- [x] The `pages` table row for slug `'licenses'` verified in live DB
|
||||
|
||||
---
|
||||
|
||||
@@ -59,45 +56,28 @@ panel, stored in the existing `pages` table.
|
||||
|
||||
#### 2a — `src/Database.php`
|
||||
|
||||
- [ ] `getPage(string $slug): array|null` — `SELECT * FROM pages WHERE slug = ?`
|
||||
- [ ] `savePage(string $slug, string $content): void` — `UPDATE pages SET content = ?,
|
||||
updated_at = CURRENT_TIMESTAMP WHERE slug = ?`; throw if slug not found
|
||||
- [x] `getPage(string $slug): array|null` — `SELECT * FROM pages WHERE slug = ?`
|
||||
- [x] `savePage(string $slug, string $content): void` — throws if slug not found
|
||||
- [x] `getAllPages(): array` — for listing in admin
|
||||
|
||||
#### 2b — Admin pages editor UI
|
||||
|
||||
- [ ] **`public/admin/pages.php`** — list all editable pages (fetch all from `pages`
|
||||
table); links to edit each one; reuse `.admin-table` styles
|
||||
- [ ] **`public/admin/pages-edit.php`** — edit form for a single page (slug passed via
|
||||
GET `?slug=`); loads page content; renders a **EasyMDE** (or SimpleMDE) Markdown
|
||||
WYSIWYG editor via CDN; POST action → `actions/page.php`
|
||||
- Include EasyMDE from CDN: `https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js`
|
||||
+ matching CSS
|
||||
- Textarea `name="content"` pre-filled with `$page['content']`; hidden input
|
||||
`name="slug"` with the page slug
|
||||
- CSRF token as hidden input
|
||||
- [x] **`public/admin/pages.php`** — list all editable pages; links to edit each one
|
||||
- [x] **`public/admin/pages-edit.php`** — EasyMDE WYSIWYG Markdown editor via CDN
|
||||
|
||||
#### 2c — `public/admin/actions/page.php`
|
||||
|
||||
- [ ] Auth guard + CSRF check
|
||||
- [ ] Validate `slug` ∈ `['about', 'licenses', 'charte', 'contact']`
|
||||
- [ ] Validate `content` length ≤ 65 535 chars (TEXT column limit)
|
||||
- [ ] Call `$db->savePage($slug, $content)`
|
||||
- [ ] Set `$_SESSION['success']`; redirect to `../pages.php`
|
||||
- [x] Auth guard + CSRF check + slug validation + length validation + savePage + redirect
|
||||
|
||||
#### 2d — Public pages render Markdown
|
||||
|
||||
- [ ] **`public/apropos.php`** — replace hardcoded HTML body text with content from
|
||||
`$db->getPage('about')`; render Markdown via a PHP parser
|
||||
(use **Parsedown** via Composer, or a ~150-line zero-dependency inline parser
|
||||
if Composer is not available in this project — check `composer.json`)
|
||||
- [ ] **`public/licence.php`** — same: render `$db->getPage('licenses')` as Markdown
|
||||
- [ ] Choose Markdown renderer: check if Composer is available; if not, bundle
|
||||
`vendor/Parsedown.php` as a single-file include (MIT licensed, copy-paste friendly)
|
||||
- [x] **`public/apropos.php`** — renders `$db->getPage('about')` via Parsedown (bundled `src/Parsedown.php`)
|
||||
- [x] **`public/licence.php`** — renders `$db->getPage('licenses')` via Parsedown
|
||||
- [x] Parsedown bundled as `src/Parsedown.php` (zero-dependency, MIT)
|
||||
|
||||
#### 2e — Nav links in admin
|
||||
|
||||
- [ ] **`templates/admin/head.php`** — add "Pages statiques" nav item linking to
|
||||
`/admin/pages.php`; apply active class when on `pages.php` or `pages-edit.php`
|
||||
- [x] **`templates/admin/head.php`** — "Pages statiques" nav item added
|
||||
|
||||
---
|
||||
|
||||
@@ -109,54 +89,36 @@ no seed data yet.
|
||||
|
||||
#### 3a — Schema / DB
|
||||
|
||||
- [ ] **`storage/schema.sql`** — add seed `INSERT OR IGNORE` for common Creative Commons
|
||||
licences into `license_types`:
|
||||
`CC BY 4.0`, `CC BY-SA 4.0`, `CC BY-ND 4.0`, `CC BY-NC 4.0`,
|
||||
`CC BY-NC-SA 4.0`, `CC BY-NC-ND 4.0`, `Tous droits réservés`, `Domaine public`
|
||||
- [ ] **`storage/migrations/003_seed_license_types.sql`** — same inserts wrapped in
|
||||
`INSERT OR IGNORE` so they're safe to run on an existing DB; also contains
|
||||
no structural changes (no ALTER TABLE needed — `theses.license_id` FK already
|
||||
exists in the schema)
|
||||
- [ ] Verify live DB has `license_types` table; if missing (older DB without that
|
||||
table), add `CREATE TABLE IF NOT EXISTS` to the migration
|
||||
- [x] **`storage/schema.sql`** — seed `INSERT OR IGNORE` for 8 CC licence types added
|
||||
- [x] **`storage/migrations/003_seed_license_types.sql`** — migration created + applied
|
||||
- [x] Verified live DB has `license_types` with 8 rows
|
||||
|
||||
#### 3b — `src/Database.php`
|
||||
|
||||
- [ ] `getLicenseTypes(): array` — `SELECT * FROM license_types ORDER BY name`
|
||||
- [ ] `getAllLicenseTypes(): array` — alias for form-loading consistency
|
||||
- [x] `getLicenseTypes(): array`
|
||||
- [x] `getAllLicenseTypes(): array` — alias
|
||||
|
||||
#### 3c — Add form (`public/admin/add.php`)
|
||||
|
||||
- [ ] Load `$licenseTypes = $db->getAllLicenseTypes()` alongside existing reference data
|
||||
- [ ] Add "Licence" `<select name="license_id">` row in the form (between synopsis
|
||||
and duration, or after duration — whichever is logical); include empty/unknown
|
||||
option as default
|
||||
- [x] Loads `$licenseTypes`; "Licence" `<select name="license_id">` added before duration
|
||||
|
||||
#### 3d — Add action (`public/admin/actions/formulaire.php`)
|
||||
|
||||
- [ ] Read `$licenseId = filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null`
|
||||
- [ ] Add `license_id` to the `INSERT INTO theses (…)` column list and `$stmt->execute([…])`
|
||||
- [x] `$licenseId` parsed + included in INSERT
|
||||
|
||||
#### 3e — Edit form (`public/admin/edit.php`)
|
||||
|
||||
- [ ] Load `$licenseTypes = $db->getAllLicenseTypes()`
|
||||
- [ ] Add "Licence" `<select name="license_id">` row; pre-select current `$thesis['license_id']`
|
||||
(note: view exposes `license_type` as the name string; need to separately query
|
||||
`theses.license_id` for the raw FK value, or add it to the view — see 3f)
|
||||
- [ ] In the POST handler: `UPDATE theses SET … license_id = ? …` with the submitted value
|
||||
- [x] Loads `$licenseTypes`; raw `license_id` FK fetched directly; select pre-populated
|
||||
- [x] POST handler: `license_id` included in UPDATE
|
||||
|
||||
#### 3f — View update
|
||||
|
||||
- [ ] **`storage/schema.sql`** — update `v_theses_full` view to also `SELECT t.license_id`
|
||||
(the raw FK) alongside the existing `lt.name as license_type`; required so the
|
||||
edit form can pre-select the correct `<option>`
|
||||
- [ ] **`storage/migrations/003_seed_license_types.sql`** — include `DROP VIEW IF EXISTS
|
||||
v_theses_full; CREATE VIEW … (updated definition)`
|
||||
- [ ] **`storage/schema.sql`** — update `v_theses_full` to expose `t.license_id` raw FK
|
||||
(edit form currently queries theses directly — lower priority)
|
||||
|
||||
#### 3g — TFE public page
|
||||
|
||||
- [ ] **`public/tfe.php`** — add "Licence :" meta row using `$data['license_type']`
|
||||
(already in the view); display only if non-null
|
||||
- [x] **`public/tfe.php`** — "Licence :" meta row added, shown when non-null
|
||||
|
||||
---
|
||||
|
||||
@@ -316,64 +278,19 @@ home grid (wider, shorter aspect ratio).
|
||||
|
||||
#### 6a — Gradient placeholder for cards without a banner
|
||||
|
||||
When a TFE has no banner and no cover image, display a CSS gradient using HSL hue
|
||||
derived from the thesis ID (deterministic per TFE, consistent across page loads).
|
||||
|
||||
- [ ] **`public/index.php`** — in the card thumbnail block, replace the current
|
||||
`<div class="card__media--placeholder">◻</div>` with:
|
||||
```php
|
||||
// Compute a deterministic hue from thesis ID (160° spread)
|
||||
$hue = ($item['id'] * 47 + 160) % 360; // 47 is a prime step, 160° base
|
||||
$hue2 = ($hue + 40) % 360; // second stop
|
||||
?>
|
||||
<div class="card__media--gradient"
|
||||
style="background: linear-gradient(135deg,
|
||||
hsl(<?= $hue ?>, 60%, 65%),
|
||||
hsl(<?= $hue2 ?>, 55%, 45%));">
|
||||
<span class="card__gradient-author"><?= htmlspecialchars($item['authors'] ?? '') ?></span>
|
||||
<span class="card__gradient-title"><?= htmlspecialchars($item['title']) ?></span>
|
||||
</div>
|
||||
```
|
||||
- [ ] **`public/assets/main.css`** — add styles for `.card__media--gradient`,
|
||||
`.card__gradient-author`, `.card__gradient-title`:
|
||||
- `.card__media--gradient`: `width:100%; height:100%; display:flex; flex-direction:column;
|
||||
align-items:center; justify-content:center; padding:1rem; text-align:center;`
|
||||
- `.card__gradient-author`: `color:#fff; font-size:0.75rem; opacity:.85; margin-bottom:.25rem;`
|
||||
- `.card__gradient-title`: `color:#fff; font-size:0.85rem; font-weight:600;
|
||||
display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;`
|
||||
- [x] **`public/index.php`** — gradient placeholder using HSL hue from thesis ID
|
||||
- [x] **`public/assets/main.css`** — `.card__media--gradient`, `.card__gradient-author`,
|
||||
`.card__gradient-title` styles added
|
||||
|
||||
#### 6b — Banner image as card thumbnail
|
||||
|
||||
- [ ] **`public/index.php`** — update thumbnail resolution logic:
|
||||
1. Check `$item['banner_path']` first (new banner field)
|
||||
2. Fall back to first image in `$item['files']` (existing logic)
|
||||
3. Fall back to `$item['cover_image']` if present
|
||||
4. Fall through to gradient placeholder (6a)
|
||||
- [ ] Note: `getPublishedTheses()` currently returns from `v_theses_public` which does
|
||||
not include `files` (they're loaded lazily in `getThesisById`); the index loop
|
||||
currently accesses `$item['files']` but the method doesn't return them — this
|
||||
is a pre-existing bug; either add banner_path to the view (simpler) or fix the
|
||||
files join; the banner column on the view (5a) is sufficient for 6a/6b
|
||||
- [x] **`public/index.php`** — checks `banner_path` first, falls through to gradient
|
||||
|
||||
#### 6c — Random ordering from the latest year
|
||||
|
||||
- [ ] **`src/Database.php`** — add `getLatestYearTheses(int $limit = 24): array`:
|
||||
```sql
|
||||
SELECT * FROM v_theses_public
|
||||
WHERE year = (SELECT MAX(year) FROM theses WHERE is_published = 1)
|
||||
ORDER BY RANDOM()
|
||||
LIMIT :limit
|
||||
```
|
||||
Note: `ORDER BY RANDOM()` is re-evaluated on every call — no caching needed;
|
||||
the randomness is per PHP request (page load)
|
||||
- [ ] **`public/index.php`** — when no `?year` filter is active and `?page=1` (or no page):
|
||||
- Replace `$db->getPublishedTheses($itemsPerPage, $offset)` with
|
||||
`$db->getLatestYearTheses($itemsPerPage)`
|
||||
- Still show paginated view for `?year=X` filter and `?page=N` requests
|
||||
- Add a visual indicator (e.g. `<p class="home-latest-label">Découvrez les TFE de …</p>`)
|
||||
showing which year is being displayed on the random home view
|
||||
- Pagination is disabled/hidden on the default home view (random selection from
|
||||
one year — no pages needed unless the year has >24 TFEs; handle gracefully)
|
||||
- [x] **`src/Database.php`** — `getLatestYearTheses(int $limit = 24)` + `getLatestPublishedYear()`
|
||||
- [x] **`public/index.php`** — default home view uses random latest-year selection;
|
||||
paginated view for `?year=X` and `?page=N`; info label shown
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user