mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19: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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -105,6 +105,9 @@ try {
|
||||
// Formats (optional, multiple selection)
|
||||
$formatIds = isset($_POST["formats"]) ? array_map('intval', $_POST["formats"]) : [];
|
||||
|
||||
// License
|
||||
$licenseId = filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
|
||||
|
||||
// External link
|
||||
$lien = $_POST["lien"] ?? '';
|
||||
if (!empty($lien)) {
|
||||
@@ -135,9 +138,9 @@ try {
|
||||
identifier, title, subtitle, year,
|
||||
orientation_id, ap_program_id, finality_id,
|
||||
synopsis, file_size_info,
|
||||
baiu_link,
|
||||
baiu_link, license_id,
|
||||
submitted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
@@ -150,7 +153,8 @@ try {
|
||||
$finalityId,
|
||||
$synopsis,
|
||||
!empty($durationInfo) ? $durationInfo : null,
|
||||
!empty($lien) ? $lien : null
|
||||
!empty($lien) ? $lien : null,
|
||||
$licenseId
|
||||
]);
|
||||
|
||||
$thesisId = $pdo->lastInsertId();
|
||||
|
||||
35
public/admin/actions/page.php
Normal file
35
public/admin/actions/page.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// CSRF check
|
||||
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
|
||||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
die("Erreur de sécurité : token invalide.");
|
||||
}
|
||||
|
||||
$allowedSlugs = ['about', 'licenses', 'charte', 'contact'];
|
||||
$slug = $_POST['slug'] ?? '';
|
||||
if (!in_array($slug, $allowedSlugs)) {
|
||||
die("Slug invalide.");
|
||||
}
|
||||
|
||||
$content = $_POST['content'] ?? '';
|
||||
if (strlen($content) > 65535) {
|
||||
die("Contenu trop long (max 65 535 caractères).");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../src/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$db->savePage($slug, $content);
|
||||
$_SESSION['success'] = "Page «" . $slug . "» mise à jour avec succès.";
|
||||
} catch (Exception $e) {
|
||||
error_log("Page save error: " . $e->getMessage());
|
||||
die("Erreur lors de la sauvegarde : " . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
|
||||
header('Location: /admin/pages.php');
|
||||
exit;
|
||||
@@ -18,6 +18,7 @@ try {
|
||||
$finalityTypes = $db->getAllFinalityTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
$licenseTypes = $db->getAllLicenseTypes();
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to load form data: " . $e->getMessage());
|
||||
die("Erreur lors du chargement du formulaire.");
|
||||
@@ -191,6 +192,20 @@ function wasSelected($key, $value) {
|
||||
rows="7" required><?= old('synopsis') ?></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Licence -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="license_id">Licence :</label>
|
||||
<select class="admin-select" id="license_id" name="license_id">
|
||||
<option value="">— Inconnue —</option>
|
||||
<?php foreach ($licenseTypes as $lt): ?>
|
||||
<option value="<?= htmlspecialchars($lt['id']) ?>"
|
||||
<?= wasSelected('license_id', $lt['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($lt['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Durée/Taille -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="duration_info">Durée / Taille :</label>
|
||||
|
||||
@@ -36,6 +36,8 @@ try {
|
||||
$db->beginTransaction();
|
||||
|
||||
// Update thesis basic info
|
||||
$editLicenseId = filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE theses SET
|
||||
title = ?,
|
||||
@@ -47,6 +49,7 @@ try {
|
||||
synopsis = ?,
|
||||
file_size_info = ?,
|
||||
baiu_link = ?,
|
||||
license_id = ?,
|
||||
is_published = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
@@ -62,6 +65,7 @@ try {
|
||||
trim($_POST['synopsis']),
|
||||
!empty($_POST['duration_info']) ? trim($_POST['duration_info']) : null,
|
||||
!empty($_POST['lien']) ? trim($_POST['lien']) : null,
|
||||
$editLicenseId,
|
||||
isset($_POST['is_published']) ? 1 : 0,
|
||||
$thesisId
|
||||
]);
|
||||
@@ -164,6 +168,12 @@ try {
|
||||
$finalityTypes = $db->getAllFinalityTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
$licenseTypes = $db->getAllLicenseTypes();
|
||||
|
||||
// Fetch raw license_id FK (view only exposes license_type name string)
|
||||
$licenseStmt = $pdo->prepare("SELECT license_id FROM theses WHERE id = ?");
|
||||
$licenseStmt->execute([$thesisId]);
|
||||
$currentLicenseId = $licenseStmt->fetchColumn();
|
||||
|
||||
// Set page title for header
|
||||
$pageTitle = "Éditer TFE - " . htmlspecialchars($thesis['title']);
|
||||
@@ -247,6 +257,19 @@ try {
|
||||
value="<?= htmlspecialchars($thesis['supervisors'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="license_id">Licence :</label>
|
||||
<select class="admin-select" id="license_id" name="license_id">
|
||||
<option value="">— Inconnue —</option>
|
||||
<?php foreach ($licenseTypes as $lt): ?>
|
||||
<option value="<?= $lt['id'] ?>"
|
||||
<?= ($currentLicenseId == $lt['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($lt['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="titre">Titre :</label>
|
||||
<input class="admin-input" type="text" id="titre" name="titre"
|
||||
|
||||
67
public/admin/pages-edit.php
Normal file
67
public/admin/pages-edit.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../src/Database.php';
|
||||
|
||||
$allowedSlugs = ['about', 'licenses', 'charte', 'contact'];
|
||||
$slug = $_GET['slug'] ?? '';
|
||||
|
||||
if (!in_array($slug, $allowedSlugs)) {
|
||||
header('Location: /admin/pages.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$page = $db->getPage($slug);
|
||||
if (!$page) {
|
||||
die("Page introuvable.");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
die("Erreur: " . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
|
||||
$pageTitle = "Éditer : " . htmlspecialchars($page['title']);
|
||||
?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
|
||||
|
||||
<main class="admin-main">
|
||||
<h1 class="admin-page-title">Éditer : <?= htmlspecialchars($page['title']) ?></h1>
|
||||
|
||||
<form action="/admin/actions/page.php" method="post" class="admin-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="slug" value="<?= htmlspecialchars($slug) ?>">
|
||||
|
||||
<div class="admin-form-row" style="align-items:start;">
|
||||
<label class="admin-label" for="content">Contenu (Markdown) :</label>
|
||||
<textarea class="admin-textarea" id="content" name="content"
|
||||
rows="20"><?= htmlspecialchars($page['content'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="admin-submit-wrap">
|
||||
<button type="submit" class="admin-btn">Enregistrer</button>
|
||||
<a href="/admin/pages.php" class="admin-btn-secondary" style="margin-left:.75rem;">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
|
||||
<script>
|
||||
var easyMDE = new EasyMDE({
|
||||
element: document.getElementById('content'),
|
||||
spellChecker: false,
|
||||
status: ['lines', 'words'],
|
||||
minHeight: '400px',
|
||||
toolbarTips: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
55
public/admin/pages.php
Normal file
55
public/admin/pages.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
require_once __DIR__ . '/../../src/Database.php';
|
||||
|
||||
$pageTitle = "Pages statiques";
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$pages = $db->getAllPages();
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading pages: " . $e->getMessage());
|
||||
die("Erreur lors du chargement des pages.");
|
||||
}
|
||||
|
||||
$success = $_SESSION['success'] ?? null;
|
||||
unset($_SESSION['success']);
|
||||
?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
||||
|
||||
<main class="admin-main">
|
||||
<h1 class="admin-page-title">Pages statiques</h1>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="admin-alert admin-alert--success">✓ <?= htmlspecialchars($success) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slug</th>
|
||||
<th>Titre</th>
|
||||
<th>Mis à jour</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($pages as $p): ?>
|
||||
<tr>
|
||||
<td><code><?= htmlspecialchars($p['slug']) ?></code></td>
|
||||
<td><?= htmlspecialchars($p['title']) ?></td>
|
||||
<td><?= htmlspecialchars($p['updated_at'] ?? '—') ?></td>
|
||||
<td>
|
||||
<a href="/admin/pages-edit.php?slug=<?= urlencode($p['slug']) ?>"
|
||||
class="admin-btn" style="font-size:.8rem;padding:.3rem .75rem;">Éditer</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
@@ -1,6 +1,29 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/bootstrap.php';
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
require_once APP_ROOT . '/src/Parsedown.php';
|
||||
|
||||
$currentNav = 'apropos';
|
||||
|
||||
// Fallback static content used when DB content is the placeholder
|
||||
define('APROPOS_STATIC_CONTENT', "Ce site POSTERG a été créé pour répertorier et valoriser les mémoires de l'erg – École de Recherches Graphique de Bruxelles.\n\nL'objectif est à la fois d'offrir une vitrine aux projets des anciens étudiantes et de mettre en lumière la diversité des disciplines et des parcours qui façonnent l'histoire de l'école à travers les âges, depuis près de 100 ans.");
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$aboutPage = $db->getPage('about');
|
||||
$rawContent = $aboutPage ? $aboutPage['content'] : '';
|
||||
// Use static fallback if content is placeholder
|
||||
if (empty(trim($rawContent)) || trim($rawContent) === 'Contenu à venir') {
|
||||
$rawContent = APROPOS_STATIC_CONTENT;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading about page: " . $e->getMessage());
|
||||
$rawContent = APROPOS_STATIC_CONTENT;
|
||||
}
|
||||
|
||||
$pd = new Parsedown();
|
||||
$pd->setSafeMode(true);
|
||||
$aboutHtml = $pd->text($rawContent);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
@@ -30,16 +53,10 @@ $currentNav = 'apropos';
|
||||
<main class="apropos-main">
|
||||
<div class="apropos-layout">
|
||||
|
||||
<!-- LEFT: main text -->
|
||||
<!-- LEFT: main text (from DB, Markdown-rendered) -->
|
||||
<div class="apropos-left">
|
||||
<div class="apropos-description">
|
||||
<p>Ce site POSTERG a été créé pour répertorier et valoriser les mémoires de l'erg – École de Recherches Graphique de Bruxelles.</p>
|
||||
<p>L'objectif est à la fois d'offrir une vitrine aux projets des anciens étudiantes et de mettre en lumière la diversité des disciplines et des parcours qui façonnent l'histoire de l'école à travers les âges, depuis près de 100 ans.</p>
|
||||
</div>
|
||||
|
||||
<div class="apropos-licences">
|
||||
<h2>Licences</h2>
|
||||
<p>Les contenus de ce site sont publiés avec l'accord des auteur·ices et de l'erg. La reproduction des œuvres est soumise à l'autorisation de leurs auteur·ices respectif·ives.</p>
|
||||
<div class="apropos-description apropos-page-content">
|
||||
<?= $aboutHtml ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -93,6 +93,42 @@ html, body {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Markdown-rendered page content */
|
||||
.apropos-page-content p {
|
||||
margin: 0 0 1.2em 0;
|
||||
}
|
||||
.apropos-page-content h1,
|
||||
.apropos-page-content h2,
|
||||
.apropos-page-content h3 {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-weight: 400;
|
||||
margin: 1.5em 0 0.5em 0;
|
||||
}
|
||||
.apropos-page-content h1 { font-size: 1.55rem; }
|
||||
.apropos-page-content h2 { font-size: 1.3rem; }
|
||||
.apropos-page-content h3 { font-size: 1.1rem; }
|
||||
.apropos-page-content a {
|
||||
color: var(--purple);
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.apropos-page-content ul,
|
||||
.apropos-page-content ol {
|
||||
padding-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.apropos-page-content li {
|
||||
margin-bottom: .3em;
|
||||
}
|
||||
.apropos-page-content strong { font-weight: 700; }
|
||||
.apropos-page-content em { font-style: italic; }
|
||||
.apropos-page-content code {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 0.9em;
|
||||
background: rgba(0,0,0,.06);
|
||||
padding: .1em .3em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Licences section */
|
||||
.apropos-licences {
|
||||
margin-top: 2rem;
|
||||
|
||||
@@ -91,6 +91,41 @@ html, body {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.card__media--gradient {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card__gradient-author {
|
||||
color: #fff;
|
||||
font-size: 0.75rem;
|
||||
opacity: .85;
|
||||
margin-bottom: .25rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* Latest-year label */
|
||||
.home-latest-label {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.card__info {
|
||||
padding: 0.55rem 0.5rem 0.65rem;
|
||||
font-size: 0.88rem;
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
require_once __DIR__ . '/../config/bootstrap.php';
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
|
||||
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
|
||||
$page = isset($_GET["page"]) ? max(1, intval($_GET["page"])) : 1;
|
||||
$year = isset($_GET["year"]) ? intval($_GET["year"]) : null;
|
||||
$itemsPerPage = 24; // bigger grid
|
||||
$itemsPerPage = 24;
|
||||
|
||||
// Default home view: random theses from latest year (no year filter, no explicit page)
|
||||
$isDefaultView = (!$year && $page === 1);
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
@@ -14,19 +17,25 @@ try {
|
||||
|
||||
if ($year) {
|
||||
$itemsToLoad = $db->searchTheses(['year' => $year], $itemsPerPage, $offset);
|
||||
$totalItems = $db->countSearchResults(['year' => $year]);
|
||||
$totalItems = $db->countSearchResults(['year' => $year]);
|
||||
} elseif ($isDefaultView) {
|
||||
$latestYear = $db->getLatestPublishedYear();
|
||||
$itemsToLoad = $db->getLatestYearTheses($itemsPerPage);
|
||||
$totalItems = count($itemsToLoad); // no pagination on default view
|
||||
} else {
|
||||
$itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset);
|
||||
$totalItems = $db->countPublishedTheses();
|
||||
$totalItems = $db->countPublishedTheses();
|
||||
}
|
||||
|
||||
$totalPages = ceil($totalItems / $itemsPerPage);
|
||||
$totalPages = $isDefaultView ? 1 : (int)ceil($totalItems / $itemsPerPage);
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading theses: " . $e->getMessage());
|
||||
$itemsToLoad = [];
|
||||
$totalPages = 0;
|
||||
$totalPages = 0;
|
||||
$availableYears = [];
|
||||
$totalItems = 0;
|
||||
$totalItems = 0;
|
||||
$latestYear = null;
|
||||
$isDefaultView = false;
|
||||
}
|
||||
|
||||
$currentNav = '';
|
||||
@@ -61,6 +70,10 @@ $currentNav = '';
|
||||
Année : <?= htmlspecialchars($year) ?>
|
||||
<a href="index.php" class="clear-filter">✕ Réinitialiser</a>
|
||||
</div>
|
||||
<?php elseif ($isDefaultView && !empty($latestYear)): ?>
|
||||
<div class="filter-info home-latest-label">
|
||||
Découvrez les TFE de <?= (int)$latestYear ?> — sélection aléatoire
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<main class="home-main">
|
||||
@@ -70,28 +83,31 @@ $currentNav = '';
|
||||
<div class="card">
|
||||
<div class="card__media">
|
||||
<?php
|
||||
// Use first image/video file as thumbnail
|
||||
// Resolve thumbnail: banner_path → cover file → gradient placeholder
|
||||
$thumb = null;
|
||||
if (!empty($item['files'])) {
|
||||
foreach ($item['files'] as $f) {
|
||||
$ext = strtolower(pathinfo($f['file_path'], PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ['jpg','jpeg','png','gif','webp'])) {
|
||||
$thumb = $f['file_path'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also check cover image
|
||||
if (!$thumb && !empty($item['cover_image'])) {
|
||||
$thumb = $item['cover_image'];
|
||||
|
||||
// 1. Banner path (dedicated home thumbnail)
|
||||
if (!empty($item['banner_path'])) {
|
||||
$thumb = $item['banner_path'];
|
||||
}
|
||||
|
||||
// 2. Cover image from thesis_files (not returned by view — skip for now)
|
||||
// 3. Fall through to gradient
|
||||
?>
|
||||
<?php if ($thumb): ?>
|
||||
<img src="/media.php?path=<?= urlencode($thumb) ?>"
|
||||
alt="<?= htmlspecialchars($item['title']) ?>"
|
||||
loading="lazy">
|
||||
<?php else: ?>
|
||||
<div class="card__media--placeholder">◻</div>
|
||||
<?php
|
||||
$hue = ($item['id'] * 47 + 160) % 360;
|
||||
$hue2 = ($hue + 40) % 360;
|
||||
?>
|
||||
<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>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card__info">
|
||||
|
||||
64
public/licence.php
Normal file
64
public/licence.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/bootstrap.php';
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
require_once APP_ROOT . '/src/Parsedown.php';
|
||||
|
||||
$currentNav = 'licence';
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$page = $db->getPage('licenses');
|
||||
$content = $page ? $page['content'] : '';
|
||||
$pageTitle = $page ? $page['title'] : 'Licences';
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading licence page: " . $e->getMessage());
|
||||
$content = '';
|
||||
$pageTitle = 'Licences';
|
||||
}
|
||||
|
||||
$pd = new Parsedown();
|
||||
$pd->setSafeMode(true);
|
||||
$html = $pd->text($content);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= htmlspecialchars($pageTitle) ?> – Posterg</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/admin_favicon.svg">
|
||||
<link rel="stylesheet" href="assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="assets/common.css">
|
||||
<link rel="stylesheet" href="assets/apropos.css">
|
||||
<?php if (php_sapi_name() === 'cli-server'): ?>
|
||||
<script>
|
||||
(function poll(){
|
||||
fetch('/live-reload.php').then(r=>r.json()).then(d=>{
|
||||
if(d.changed) location.reload(); else setTimeout(poll,1000);
|
||||
}).catch(()=>setTimeout(poll,2000));
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
<body class="apropos-body">
|
||||
|
||||
<?php include APP_ROOT . '/templates/nav.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/search-bar.php'; ?>
|
||||
|
||||
<main class="apropos-main">
|
||||
<div class="apropos-layout">
|
||||
<div class="apropos-left">
|
||||
<div class="apropos-description apropos-page-content">
|
||||
<?php if (!empty(trim($content))): ?>
|
||||
<?= $html ?>
|
||||
<?php else: ?>
|
||||
<p>Contenu à venir.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="apropos-right"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -114,6 +114,13 @@ $currentNav = '';
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['license_type'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Licence :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['license_type']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['baiu_link'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Contact :</span>
|
||||
|
||||
@@ -115,6 +115,30 @@ class Database {
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theses from the latest published year, in random order (per request).
|
||||
* Used for the default home page view.
|
||||
*/
|
||||
public function 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";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest year that has published theses
|
||||
*/
|
||||
public function getLatestPublishedYear(): ?int {
|
||||
$stmt = $this->pdo->query("SELECT MAX(year) FROM theses WHERE is_published = 1");
|
||||
$val = $stmt->fetchColumn();
|
||||
return $val ? (int)$val : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all published theses
|
||||
*/
|
||||
@@ -648,6 +672,65 @@ class Database {
|
||||
return $result ? $result['id'] : null;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// STATIC PAGES METHODS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Get a static page by slug
|
||||
* @param string $slug Page slug (e.g. 'about', 'licenses')
|
||||
* @return array|null
|
||||
*/
|
||||
public function getPage(string $slug): ?array {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM pages WHERE slug = ?");
|
||||
$stmt->execute([$slug]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update content for a static page by slug
|
||||
* @throws Exception if slug not found
|
||||
*/
|
||||
public function savePage(string $slug, string $content): void {
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM pages WHERE slug = ?");
|
||||
$stmt->execute([$slug]);
|
||||
if (!$stmt->fetch()) {
|
||||
throw new Exception("Page slug not found: $slug");
|
||||
}
|
||||
$stmt = $this->pdo->prepare(
|
||||
"UPDATE pages SET content = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?"
|
||||
);
|
||||
$stmt->execute([$content, $slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all static pages
|
||||
*/
|
||||
public function getAllPages(): array {
|
||||
$stmt = $this->pdo->query("SELECT * FROM pages ORDER BY slug");
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// LICENSE TYPE METHODS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Get all license types ordered by name
|
||||
*/
|
||||
public function getLicenseTypes(): array {
|
||||
$stmt = $this->pdo->query("SELECT * FROM license_types ORDER BY name");
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for form-loading consistency
|
||||
*/
|
||||
public function getAllLicenseTypes(): array {
|
||||
return $this->getLicenseTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a thesis file record
|
||||
*/
|
||||
|
||||
1995
src/Parsedown.php
Normal file
1995
src/Parsedown.php
Normal file
File diff suppressed because it is too large
Load Diff
19
storage/migrations/003_seed_license_types.sql
Normal file
19
storage/migrations/003_seed_license_types.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Migration 003: Seed license_types table
|
||||
-- Safe to run on existing databases (INSERT OR IGNORE)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS license_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO license_types (name) VALUES
|
||||
('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');
|
||||
Binary file not shown.
@@ -133,6 +133,16 @@ CREATE TABLE IF NOT EXISTS license_types (
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO license_types (name) VALUES
|
||||
('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');
|
||||
|
||||
-- ============================================================================
|
||||
-- MAIN THESIS TABLE
|
||||
-- ============================================================================
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<a href="/admin/" class="admin-nav__link <?= $currentPage === 'index.php' ? 'active' : '' ?>">Liste des TFE</a>
|
||||
<a href="/admin/add.php" class="admin-nav__link <?= $currentPage === 'add.php' ? 'active' : '' ?>">Ajouter un TFE</a>
|
||||
<a href="/admin/import.php" class="admin-nav__link <?= $currentPage === 'import.php' ? 'active' : '' ?>">Importer une liste de TFE</a>
|
||||
<a href="/admin/pages.php" class="admin-nav__link <?= in_array($currentPage, ['pages.php','pages-edit.php']) ? 'active' : '' ?>">Pages statiques</a>
|
||||
<?php if ($thesisId && in_array($currentPage, ['edit.php', 'thanks.php'])): ?>
|
||||
<a href="/admin/edit.php?id=<?= intval($thesisId) ?>" class="admin-nav__link <?= $currentPage === 'edit.php' ? 'active' : '' ?>">Modifier</a>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
// nav.php — shared public navigation bar
|
||||
// Usage: include this partial from any public page
|
||||
// Provide $currentPage variable to mark active links (optional)
|
||||
// Provide $currentNav variable to mark active links (optional)
|
||||
$_navCurrent = $currentNav ?? '';
|
||||
?>
|
||||
<nav class="site-nav">
|
||||
@@ -9,6 +9,8 @@ $_navCurrent = $currentNav ?? '';
|
||||
<div class="site-nav__links">
|
||||
<a class="site-nav__link <?= ($_navCurrent === 'repertoire') ? 'site-nav__link--active' : '' ?>"
|
||||
href="/search.php">Répertoire</a>
|
||||
<a class="site-nav__link <?= ($_navCurrent === 'licence') ? 'site-nav__link--active' : '' ?>"
|
||||
href="/licence.php">Licence</a>
|
||||
</div>
|
||||
<a class="site-nav__right <?= ($_navCurrent === 'apropos') ? 'site-nav__link--active' : '' ?>"
|
||||
href="/apropos.php">À Propos</a>
|
||||
|
||||
Reference in New Issue
Block a user