mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
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
This commit is contained in:
19
TODO.md
19
TODO.md
@@ -1,9 +1,14 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
## Completed
|
- [x] Create migration 010_apropos_contents.sql (apropos_contents table, seed defaults)
|
||||||
- [x] Fix share link slug regex mismatch (base64 chars vs base32 pattern)
|
- [x] Add apropos CRUD methods to Database.php
|
||||||
- [x] Fix regex delimiter clash (`/` inside `[...]` broke the pattern) → switched to `#` delimiter
|
- [x] Create admin/contenus.php (replaces pages.php)
|
||||||
- [x] Add PHP dev server router for /partage/<slug> URL rewriting
|
- [x] Create admin/contenus-edit.php (edit pages + apropos contacts/credits/erg_url)
|
||||||
- [x] Add nginx location block for /partage/ pretty URLs
|
- [x] Create admin/actions/apropos.php - save handler for apropos contents
|
||||||
- [x] Fix POST path missing App::boot() (session not started before submission handler)
|
- [x] Update templates/header.php: rename "Pages statiques" → "Contenus", update nav links
|
||||||
- [x] Fix rate limiter: was instantiating RateLimit then ignoring it, reimplementing inline; added checkKey() to RateLimit and use it
|
- [x] Update public/apropos.php: read contacts/credits/erg_url from DB instead of config
|
||||||
|
- [x] Delete config/apropos.php
|
||||||
|
- [x] Delete public/admin/pages.php
|
||||||
|
- [x] Delete public/admin/pages-edit.php
|
||||||
|
- [x] Delete public/admin/actions/page.php
|
||||||
|
- [x] Update storage/schema.sql with apropos_contents table + trigger
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* À propos page — contacts and credits configuration.
|
|
||||||
*
|
|
||||||
* Edit this file to update the people listed in the Contacts and Crédits
|
|
||||||
* sections of the public "À propos" page without touching the template.
|
|
||||||
*
|
|
||||||
* contacts[] — each entry:
|
|
||||||
* 'name' string Full display name
|
|
||||||
* 'role' string Short description of role (plain text, no HTML)
|
|
||||||
* 'email' string Email address (used for mailto: link)
|
|
||||||
*
|
|
||||||
* credits[] — each entry:
|
|
||||||
* 'label' string Credit category / title (e.g. "Design & développement")
|
|
||||||
* 'value' string Free-text value; HTML entities will be escaped, so use
|
|
||||||
* plain text (ampersands written as "&", not "&")
|
|
||||||
*
|
|
||||||
* erg_url — URL for the "Site de l'erg" link in the aside.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return [
|
|
||||||
'erg_url' => 'https://erg.be',
|
|
||||||
|
|
||||||
'contacts' => [
|
|
||||||
[
|
|
||||||
'name' => 'Laurent Leprince',
|
|
||||||
'role' => 'Bibliothèque d’architecture, d’ingénierie architecturale, d’urbanisme (BAIU) :',
|
|
||||||
'email' => 'laurent.leprince@uclouvain.be',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'Xavier Gorgol',
|
|
||||||
'role' => 'Responsable des mémoires de l’ERG :',
|
|
||||||
'email' => 'xavier.gorgol@erg.be',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'Brigitte Ledune',
|
|
||||||
'role' => 'Cours de suivi de mémoire :',
|
|
||||||
'email' => 'brigitte.ledune@erg.be',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'credits' => [
|
|
||||||
[
|
|
||||||
'label' => 'Design & développement',
|
|
||||||
'value' => 'Olivia Marly, Théophile Gerveau-Mercie & Théo Hennequin',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => 'Typographies',
|
|
||||||
'value' => 'Ductus (Amélie Dumont) & BBB DM Sans',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
77
public/admin/actions/apropos.php
Normal file
77
public/admin/actions/apropos.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Save handler for apropos contents (contacts, credits, erg_url).
|
||||||
|
*/
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedKeys = ['contacts', 'credits', 'erg_url'];
|
||||||
|
$aproposKey = $_POST['apropos_key'] ?? '';
|
||||||
|
if (!in_array($aproposKey, $allowedKeys)) {
|
||||||
|
die("Clé invalide.");
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../../src/Database.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = new Database();
|
||||||
|
|
||||||
|
if ($aproposKey === 'erg_url') {
|
||||||
|
$value = trim($_POST['value'] ?? '');
|
||||||
|
if (strlen($value) > 500) {
|
||||||
|
die("URL trop longue (max 500 caractères).");
|
||||||
|
}
|
||||||
|
$db->saveAproposContent('erg_url', $value);
|
||||||
|
} else {
|
||||||
|
$items = $_POST['items'] ?? [];
|
||||||
|
$cleaned = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($aproposKey === 'contacts') {
|
||||||
|
$name = trim($item['name'] ?? '');
|
||||||
|
if ($name === '') continue; // skip empty rows
|
||||||
|
$entry = [
|
||||||
|
'name' => trim($item['name'] ?? ''),
|
||||||
|
'role' => trim($item['role'] ?? ''),
|
||||||
|
'email' => trim($item['email'] ?? ''),
|
||||||
|
];
|
||||||
|
$url = trim($item['url'] ?? '');
|
||||||
|
if ($url !== '') {
|
||||||
|
$entry['url'] = $url;
|
||||||
|
}
|
||||||
|
$cleaned[] = $entry;
|
||||||
|
} else { // credits
|
||||||
|
$label = trim($item['label'] ?? '');
|
||||||
|
$val = trim($item['value'] ?? '');
|
||||||
|
$url = trim($item['url'] ?? '');
|
||||||
|
if ($label === '' && $val === '') continue;
|
||||||
|
$entry = [
|
||||||
|
'label' => $label,
|
||||||
|
'value' => $val,
|
||||||
|
];
|
||||||
|
if ($url !== '') {
|
||||||
|
$entry['url'] = $url;
|
||||||
|
}
|
||||||
|
$cleaned[] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($cleaned)) {
|
||||||
|
die("Au moins un élément est requis.");
|
||||||
|
}
|
||||||
|
$db->saveAproposContent($aproposKey, $cleaned);
|
||||||
|
}
|
||||||
|
|
||||||
|
App::flash('success', "Contenu « $aproposKey » mis à jour avec succès.");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Apropos save error: " . $e->getMessage());
|
||||||
|
die("Erreur lors de la sauvegarde : " . htmlspecialchars($e->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: /admin/contenus.php');
|
||||||
|
exit;
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<?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);
|
|
||||||
App::flash('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;
|
|
||||||
232
public/admin/contenus-edit.php
Normal file
232
public/admin/contenus-edit.php
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||||
|
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||||
|
AdminAuth::requireLogin();
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../src/Database.php';
|
||||||
|
|
||||||
|
if (empty($_SESSION["csrf_token"])) {
|
||||||
|
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedPageSlugs = ["about", "licenses", "charte"];
|
||||||
|
$allowedApropos = ["contacts", "credits", "erg_url"];
|
||||||
|
|
||||||
|
$pageSlug = $_GET["slug"] ?? "";
|
||||||
|
$aproposKey = $_GET["apropos"] ?? "";
|
||||||
|
|
||||||
|
// Exactly one target must be specified
|
||||||
|
if ($pageSlug && !in_array($pageSlug, $allowedPageSlugs)) {
|
||||||
|
$pageSlug = "";
|
||||||
|
}
|
||||||
|
if ($aproposKey && !in_array($aproposKey, $allowedApropos)) {
|
||||||
|
$aproposKey = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$pageSlug && !$aproposKey) {
|
||||||
|
header("Location: /admin/contenus.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = new Database();
|
||||||
|
|
||||||
|
if ($pageSlug) {
|
||||||
|
$page = $db->getPage($pageSlug);
|
||||||
|
if (!$page) {
|
||||||
|
die("Page introuvable.");
|
||||||
|
}
|
||||||
|
$editTitle = $page["title"];
|
||||||
|
$editType = "page";
|
||||||
|
} else {
|
||||||
|
$editType = "apropos";
|
||||||
|
$value = $db->getAproposContent($aproposKey);
|
||||||
|
$editTitle = match($aproposKey) {
|
||||||
|
'contacts' => 'Contacts',
|
||||||
|
'credits' => 'Crédits',
|
||||||
|
'erg_url' => 'URL de l\'ERG',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die("Erreur: " . htmlspecialchars($e->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "Éditer : " . $editTitle;
|
||||||
|
$extraJs = ["/assets/js/overtype.min.js"];
|
||||||
|
$extraJsInline = <<<'JS'
|
||||||
|
var OT = window.OverType.default || window.OverType;
|
||||||
|
var hidden = document.getElementById('content');
|
||||||
|
var editor = new OT(document.getElementById('editor'), {
|
||||||
|
value: hidden.value,
|
||||||
|
minHeight: '400px',
|
||||||
|
spellcheck: false,
|
||||||
|
onChange: function(value) { hidden.value = value; }
|
||||||
|
});
|
||||||
|
JS;
|
||||||
|
$aproposEditorJs = null;
|
||||||
|
if ($editType === 'apropos' && in_array($aproposKey, ['contacts', 'credits'])) {
|
||||||
|
// Rich textarea for JSON arrays rendered as structured form
|
||||||
|
$aproposEditorJs = <<<'JS'
|
||||||
|
// Auto-format JSON in the hidden field for display purposes
|
||||||
|
JS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$initialContent = '';
|
||||||
|
if ($editType === 'page') {
|
||||||
|
$initialContent = $page["content"] ?? "";
|
||||||
|
} else {
|
||||||
|
// For apropos, show structured form
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php
|
||||||
|
$isAdmin = true;
|
||||||
|
$bodyClass = "admin-body";
|
||||||
|
require_once APP_ROOT . "/templates/head.php";
|
||||||
|
?>
|
||||||
|
<?php include APP_ROOT . "/templates/header.php"; ?>
|
||||||
|
|
||||||
|
<main id="main-content">
|
||||||
|
<h1>Éditer : <?= htmlspecialchars($editTitle) ?></h1>
|
||||||
|
|
||||||
|
<?php if ($editType === 'page'): ?>
|
||||||
|
<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($pageSlug) ?>">
|
||||||
|
|
||||||
|
<label for="editor">Contenu (Markdown) :</label>
|
||||||
|
<input type="hidden" id="content" name="content"
|
||||||
|
value="<?= htmlspecialchars($initialContent) ?>">
|
||||||
|
<div id="editor"></div>
|
||||||
|
|
||||||
|
<div class="admin-form-footer">
|
||||||
|
<button type="submit" class="admin-btn">Enregistrer</button>
|
||||||
|
<a href="/admin/contenus.php" class="admin-btn-secondary admin-cancel-link">Annuler</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php elseif ($aproposKey === 'erg_url'): ?>
|
||||||
|
<form action="/admin/actions/apropos.php" method="post" class="admin-form">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||||
|
<input type="hidden" name="apropos_key" value="erg_url">
|
||||||
|
|
||||||
|
<label for="erg_url">URL du site de l'ERG :</label>
|
||||||
|
<input type="url" id="erg_url" name="value"
|
||||||
|
value="<?= htmlspecialchars($value) ?>" style="width:100%;max-width:600px;">
|
||||||
|
|
||||||
|
<div class="admin-form-footer">
|
||||||
|
<button type="submit" class="admin-btn">Enregistrer</button>
|
||||||
|
<a href="/admin/contenus.php" class="admin-btn-secondary admin-cancel-link">Annuler</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php elseif (in_array($aproposKey, ['contacts', 'credits'])): ?>
|
||||||
|
<?php
|
||||||
|
$items = is_array($value) ? $value : [];
|
||||||
|
?>
|
||||||
|
<form action="/admin/actions/apropos.php" method="post" class="admin-form" id="apropos-form">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||||
|
<input type="hidden" name="apropos_key" value="<?= htmlspecialchars($aproposKey) ?>">
|
||||||
|
|
||||||
|
<?php if ($aproposKey === 'contacts'): ?>
|
||||||
|
<?php foreach ($items as $i => $item): ?>
|
||||||
|
<fieldset class="apropos-item">
|
||||||
|
<legend>Contact <?= $i + 1 ?></legend>
|
||||||
|
<label for="contact_<?= $i ?>_name">Nom :</label>
|
||||||
|
<input type="text" id="contact_<?= $i ?>_name"
|
||||||
|
name="items[<?= $i ?>][name]"
|
||||||
|
value="<?= htmlspecialchars($item['name'] ?? '') ?>" required>
|
||||||
|
|
||||||
|
<label for="contact_<?= $i ?>_role">Rôle :</label>
|
||||||
|
<input type="text" id="contact_<?= $i ?>_role"
|
||||||
|
name="items[<?= $i ?>][role]"
|
||||||
|
value="<?= htmlspecialchars($item['role'] ?? '') ?>">
|
||||||
|
|
||||||
|
<label for="contact_<?= $i ?>_email">Email :</label>
|
||||||
|
<input type="email" id="contact_<?= $i ?>_email"
|
||||||
|
name="items[<?= $i ?>][email]"
|
||||||
|
value="<?= htmlspecialchars($item['email'] ?? '') ?>">
|
||||||
|
|
||||||
|
<label for="contact_<?= $i ?>_url">Lien (optionnel) :</label>
|
||||||
|
<input type="url" id="contact_<?= $i ?>_url"
|
||||||
|
name="items[<?= $i ?>][url]"
|
||||||
|
value="<?= htmlspecialchars($item['url'] ?? '') ?>">
|
||||||
|
</fieldset>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($items as $i => $item): ?>
|
||||||
|
<fieldset class="apropos-item">
|
||||||
|
<legend>Crédit <?= $i + 1 ?></legend>
|
||||||
|
<label for="credit_<?= $i ?>_label">Label :</label>
|
||||||
|
<input type="text" id="credit_<?= $i ?>_label"
|
||||||
|
name="items[<?= $i ?>][label]"
|
||||||
|
value="<?= htmlspecialchars($item['label'] ?? '') ?>">
|
||||||
|
|
||||||
|
<label for="credit_<?= $i ?>_value">Valeur :</label>
|
||||||
|
<input type="text" id="credit_<?= $i ?>_value"
|
||||||
|
name="items[<?= $i ?>][value]"
|
||||||
|
value="<?= htmlspecialchars($item['value'] ?? '') ?>">
|
||||||
|
|
||||||
|
<label for="credit_<?= $i ?>_url">Lien (optionnel) :</label>
|
||||||
|
<input type="url" id="credit_<?= $i ?>_url"
|
||||||
|
name="items[<?= $i ?>][url]"
|
||||||
|
value="<?= htmlspecialchars($item['url'] ?? '') ?>">
|
||||||
|
</fieldset>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button type="button" class="admin-btn" id="add-item-btn" style="width:auto;">+ Ajouter un élément</button>
|
||||||
|
|
||||||
|
<div class="admin-form-footer">
|
||||||
|
<button type="submit" class="admin-btn">Enregistrer</button>
|
||||||
|
<a href="/admin/contenus.php" class="admin-btn-secondary admin-cancel-link">Annuler</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template id="row-template-<?= $aproposKey ?>">
|
||||||
|
<?php if ($aproposKey === 'contacts'): ?>
|
||||||
|
<fieldset class="apropos-item">
|
||||||
|
<legend>Contact {{index}}</legend>
|
||||||
|
<label for="contact_{{index}}_name">Nom :</label>
|
||||||
|
<input type="text" id="contact_{{index}}_name"
|
||||||
|
name="items[{{index}}][name]" required>
|
||||||
|
<label for="contact_{{index}}_role">Rôle :</label>
|
||||||
|
<input type="text" id="contact_{{index}}_role"
|
||||||
|
name="items[{{index}}][role]">
|
||||||
|
<label for="contact_{{index}}_email">Email :</label>
|
||||||
|
<input type="email" id="contact_{{index}}_email"
|
||||||
|
name="items[{{index}}][email]">
|
||||||
|
<label for="contact_{{index}}_url">Lien (optionnel) :</label>
|
||||||
|
<input type="url" id="contact_{{index}}_url"
|
||||||
|
name="items[{{index}}][url]">
|
||||||
|
</fieldset>
|
||||||
|
<?php else: ?>
|
||||||
|
<fieldset class="apropos-item">
|
||||||
|
<legend>Crédit {{index}}</legend>
|
||||||
|
<label for="credit_{{index}}_label">Label :</label>
|
||||||
|
<input type="text" id="credit_{{index}}_label"
|
||||||
|
name="items[{{index}}][label]">
|
||||||
|
<label for="credit_{{index}}_value">Valeur :</label>
|
||||||
|
<input type="text" id="credit_{{index}}_value"
|
||||||
|
name="items[{{index}}][value]">
|
||||||
|
<label for="credit_{{index}}_url">Lien (optionnel) :</label>
|
||||||
|
<input type="url" id="credit_{{index}}_url"
|
||||||
|
name="items[{{index}}][url]">
|
||||||
|
</fieldset>
|
||||||
|
<?php endif; ?>
|
||||||
|
</template>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
let count = <?= count($items) ?>;
|
||||||
|
const tpl = document.getElementById('row-template-<?= $aproposKey ?>').innerHTML;
|
||||||
|
document.getElementById('add-item-btn').addEventListener('click', function() {
|
||||||
|
count++;
|
||||||
|
const html = tpl.replaceAll('{{index}}', count);
|
||||||
|
this.insertAdjacentHTML('beforebegin', html);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
<?php require_once APP_ROOT . "/templates/admin/footer.php"; ?>
|
||||||
86
public/admin/contenus.php
Normal file
86
public/admin/contenus.php
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||||
|
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||||
|
AdminAuth::requireLogin();
|
||||||
|
require_once __DIR__ . '/../../src/Database.php';
|
||||||
|
|
||||||
|
$pageTitle = "Contenus";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = new Database();
|
||||||
|
$pages = $db->getAllPages();
|
||||||
|
$aproposKeys = $db->getAllAproposContents();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Error loading contenus: " . $e->getMessage());
|
||||||
|
die("Erreur lors du chargement des contenus.");
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php $isAdmin = true; $bodyClass = 'admin-body'; require_once APP_ROOT . '/templates/head.php'; ?>
|
||||||
|
<?php include APP_ROOT . '/templates/header.php'; ?>
|
||||||
|
|
||||||
|
<main id="main-content">
|
||||||
|
<h1>Contenus</h1>
|
||||||
|
|
||||||
|
<?php include APP_ROOT . '/templates/partials/flash-messages.php'; ?>
|
||||||
|
|
||||||
|
<h2>Pages statiques</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Slug</th>
|
||||||
|
<th scope="col">Titre</th>
|
||||||
|
<th scope="col">Mis à jour</th>
|
||||||
|
<th scope="col">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/contenus-edit.php?slug=<?= urlencode($p['slug']) ?>"
|
||||||
|
class="admin-btn admin-btn--sm">Éditer</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 style="margin-top:2rem;">À propos</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Clé</th>
|
||||||
|
<th scope="col">Type</th>
|
||||||
|
<th scope="col">Mis à jour</th>
|
||||||
|
<th scope="col">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($aproposKeys as $a): ?>
|
||||||
|
<?php
|
||||||
|
$typeLabel = match($a['key']) {
|
||||||
|
'contacts' => 'Contacts',
|
||||||
|
'credits' => 'Crédits',
|
||||||
|
'erg_url' => 'URL de l\'ERG',
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><code><?= htmlspecialchars($a['key']) ?></code></td>
|
||||||
|
<td><?= htmlspecialchars($typeLabel) ?></td>
|
||||||
|
<td><?= htmlspecialchars($a['updated_at'] ?? '—') ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/contenus-edit.php?apropos=<?= urlencode($a['key']) ?>"
|
||||||
|
class="admin-btn admin-btn--sm">Éditer</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<?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"]);
|
|
||||||
$extraJs = ["/assets/js/overtype.min.js"];
|
|
||||||
$extraJsInline = <<<'JS'
|
|
||||||
var OT = window.OverType.default || window.OverType;
|
|
||||||
var hidden = document.getElementById('content');
|
|
||||||
var editor = new OT(document.getElementById('editor'), {
|
|
||||||
value: hidden.value,
|
|
||||||
minHeight: '400px',
|
|
||||||
spellcheck: false,
|
|
||||||
onChange: function(value) { hidden.value = value; }
|
|
||||||
});
|
|
||||||
JS;
|
|
||||||
?>
|
|
||||||
<?php
|
|
||||||
$isAdmin = true;
|
|
||||||
$bodyClass = "admin-body";
|
|
||||||
require_once APP_ROOT . "/templates/head.php";
|
|
||||||
?>
|
|
||||||
<?php include APP_ROOT . "/templates/header.php"; ?>
|
|
||||||
|
|
||||||
<main id="main-content">
|
|
||||||
<h1>É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) ?>">
|
|
||||||
|
|
||||||
<label for="editor">Contenu (Markdown) :</label>
|
|
||||||
<input type="hidden" id="content" name="content"
|
|
||||||
value="<?= htmlspecialchars($page["content"] ?? "") ?>">
|
|
||||||
<div id="editor"></div>
|
|
||||||
|
|
||||||
<div class="admin-form-footer">
|
|
||||||
<button type="submit" class="admin-btn">Enregistrer</button>
|
|
||||||
<a href="/admin/pages.php" class="admin-btn-secondary admin-cancel-link">Annuler</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
<?php require_once APP_ROOT . "/templates/admin/footer.php"; ?>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flash messages are consumed by the flash-messages partial below.
|
|
||||||
?>
|
|
||||||
<?php $isAdmin = true; $bodyClass = 'admin-body'; require_once APP_ROOT . '/templates/head.php'; ?>
|
|
||||||
<?php include APP_ROOT . '/templates/header.php'; ?>
|
|
||||||
|
|
||||||
<main id="main-content">
|
|
||||||
<h1>Pages statiques</h1>
|
|
||||||
|
|
||||||
<?php include APP_ROOT . '/templates/partials/flash-messages.php'; ?>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Slug</th>
|
|
||||||
<th scope="col">Titre</th>
|
|
||||||
<th scope="col">Mis à jour</th>
|
|
||||||
<th scope="col">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 admin-btn--sm">Éditer</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
|
||||||
@@ -3,29 +3,39 @@ require_once __DIR__ . '/../config/bootstrap.php';
|
|||||||
require_once APP_ROOT . '/src/Database.php';
|
require_once APP_ROOT . '/src/Database.php';
|
||||||
require_once APP_ROOT . '/src/Parsedown.php';
|
require_once APP_ROOT . '/src/Parsedown.php';
|
||||||
|
|
||||||
$apropos = require APP_ROOT . '/config/apropos.php';
|
|
||||||
|
|
||||||
$currentNav = 'apropos';
|
$currentNav = 'apropos';
|
||||||
|
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 anciennes é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.");
|
||||||
// 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 {
|
try {
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// Intro text from pages table
|
||||||
$aboutPage = $db->getPage('about');
|
$aboutPage = $db->getPage('about');
|
||||||
$rawContent = $aboutPage ? $aboutPage['content'] : '';
|
$rawContent = $aboutPage ? $aboutPage['content'] : '';
|
||||||
// Use static fallback if content is placeholder
|
|
||||||
if (empty(trim($rawContent)) || trim($rawContent) === 'Contenu à venir') {
|
if (empty(trim($rawContent)) || trim($rawContent) === 'Contenu à venir') {
|
||||||
$rawContent = APROPOS_STATIC_CONTENT;
|
$rawContent = APROPOS_STATIC_CONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contacts, credits, erg_url from apropos_contents table
|
||||||
|
$contacts = $db->getAproposContent('contacts');
|
||||||
|
$credits = $db->getAproposContent('credits');
|
||||||
|
$ergUrl = $db->getAproposContent('erg_url') ?: 'https://erg.be';
|
||||||
|
|
||||||
|
// Apply defaults if DB returns empty
|
||||||
|
$contacts = is_array($contacts) && !empty($contacts) ? $contacts : null;
|
||||||
|
$credits = is_array($credits) && !empty($credits) ? $credits : null;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Error loading about page: " . $e->getMessage());
|
error_log("Error loading about page: " . $e->getMessage());
|
||||||
$rawContent = APROPOS_STATIC_CONTENT;
|
$rawContent = APROPOS_STATIC_CONTENT;
|
||||||
|
$contacts = null;
|
||||||
|
$credits = null;
|
||||||
|
$ergUrl = 'https://erg.be';
|
||||||
}
|
}
|
||||||
|
|
||||||
$pd = new Parsedown();
|
$pd = new Parsedown();
|
||||||
$pd->setSafeMode(true);
|
$pd->setSafeMode(true);
|
||||||
$aboutHtml = $pd->text($rawContent);
|
$aboutHtml = $pd->text($rawContent);
|
||||||
|
|
||||||
$pageTitle = 'À Propos – Posterg';
|
$pageTitle = 'À Propos – Posterg';
|
||||||
$metaDescription = 'À propos de Posterg, le répertoire des mémoires de fin d\'études de l\'erg – École de Recherches Graphiques de Bruxelles.';
|
$metaDescription = 'À propos de Posterg, le répertoire des mémoires de fin d\'études de l\'erg – École de Recherches Graphiques de Bruxelles.';
|
||||||
$ogTags = [
|
$ogTags = [
|
||||||
@@ -49,15 +59,15 @@ $bodyClass = 'apropos-body';
|
|||||||
<p class="apropos-toc-label">Parties</p>
|
<p class="apropos-toc-label">Parties</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#apropos-intro">À propos</a></li>
|
<li><a href="#apropos-intro">À propos</a></li>
|
||||||
<?php if (!empty($apropos['contacts'])): ?>
|
<?php if (!empty($contacts)): ?>
|
||||||
<li><a href="#apropos-contacts">Contacts</a></li>
|
<li><a href="#apropos-contacts">Contacts</a></li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (!empty($apropos['credits'])): ?>
|
<?php if (!empty($credits)): ?>
|
||||||
<li><a href="#apropos-credits">Crédits</a></li>
|
<li><a href="#apropos-credits">Crédits</a></li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="apropos-toc-erg">
|
<div class="apropos-toc-erg">
|
||||||
<a href="<?= htmlspecialchars($apropos['erg_url']) ?>" target="_blank" rel="noopener">
|
<a href="<?= htmlspecialchars($ergUrl) ?>" target="_blank" rel="noopener">
|
||||||
Site de l'erg ↗
|
Site de l'erg ↗
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,14 +83,20 @@ $bodyClass = 'apropos-body';
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php if (!empty($apropos['contacts'])): ?>
|
<?php if (!empty($contacts)): ?>
|
||||||
<!-- Contacts section -->
|
<!-- Contacts section -->
|
||||||
<section class="apropos-section" id="apropos-contacts">
|
<section class="apropos-section" id="apropos-contacts">
|
||||||
<h2 class="apropos-section-title">Contacts</h2>
|
<h2 class="apropos-section-title">Contacts</h2>
|
||||||
<div class="apropos-contacts-grid">
|
<div class="apropos-contacts-grid">
|
||||||
<?php foreach ($apropos['contacts'] as $contact): ?>
|
<?php foreach ($contacts as $contact): ?>
|
||||||
<address class="apropos-contact-card">
|
<address class="apropos-contact-card">
|
||||||
<strong><?= htmlspecialchars($contact['name']) ?></strong>
|
<strong>
|
||||||
|
<?php if (!empty($contact['url'])): ?>
|
||||||
|
<a href="<?= htmlspecialchars($contact['url']) ?>" target="_blank" rel="noopener"><?= htmlspecialchars($contact['name']) ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= htmlspecialchars($contact['name']) ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</strong>
|
||||||
<span><?= htmlspecialchars($contact['role']) ?></span>
|
<span><?= htmlspecialchars($contact['role']) ?></span>
|
||||||
<a href="mailto:<?= htmlspecialchars($contact['email']) ?>"><?= htmlspecialchars($contact['email']) ?></a>
|
<a href="mailto:<?= htmlspecialchars($contact['email']) ?>"><?= htmlspecialchars($contact['email']) ?></a>
|
||||||
</address>
|
</address>
|
||||||
@@ -89,15 +105,21 @@ $bodyClass = 'apropos-body';
|
|||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($apropos['credits'])): ?>
|
<?php if (!empty($credits)): ?>
|
||||||
<!-- Credits section -->
|
<!-- Credits section -->
|
||||||
<section class="apropos-section" id="apropos-credits">
|
<section class="apropos-section" id="apropos-credits">
|
||||||
<h2 class="apropos-section-title">Crédits</h2>
|
<h2 class="apropos-section-title">Crédits</h2>
|
||||||
<dl class="apropos-credits-list">
|
<dl class="apropos-credits-list">
|
||||||
<?php foreach ($apropos['credits'] as $credit): ?>
|
<?php foreach ($credits as $credit): ?>
|
||||||
<div class="apropos-credit-row">
|
<div class="apropos-credit-row">
|
||||||
<dt><?= htmlspecialchars($credit['label']) ?></dt>
|
<dt><?= htmlspecialchars($credit['label']) ?></dt>
|
||||||
<dd><?= htmlspecialchars($credit['value']) ?></dd>
|
<dd>
|
||||||
|
<?php if (!empty($credit['url'])): ?>
|
||||||
|
<a href="<?= htmlspecialchars($credit['url']) ?>" target="_blank" rel="noopener"><?= htmlspecialchars($credit['value']) ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= htmlspecialchars($credit['value']) ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
@@ -1686,6 +1686,59 @@ class Database {
|
|||||||
// SINGLETON PATTERN ENFORCEMENT
|
// SINGLETON PATTERN ENFORCEMENT
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// APROPOS CONTENTS (structured data formerly in config/apropos.php)
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an apropos content value by key.
|
||||||
|
* @param string $key 'contacts', 'credits', or 'erg_url'
|
||||||
|
* @return array|string|null JSON-decoded array for contacts/credits, string for erg_url
|
||||||
|
*/
|
||||||
|
public function getAproposContent(string $key) {
|
||||||
|
$stmt = $this->pdo->prepare("SELECT value FROM apropos_contents WHERE key = ?");
|
||||||
|
$stmt->execute([$key]);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
if (!$row) return null;
|
||||||
|
|
||||||
|
$value = $row['value'];
|
||||||
|
if ($key === 'erg_url') {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
$decoded = json_decode($value, true);
|
||||||
|
return is_array($decoded) ? $decoded : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an apropos content value by key.
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value array for contacts/credits, string for erg_url
|
||||||
|
*/
|
||||||
|
public function saveAproposContent(string $key, $value): void {
|
||||||
|
$stmt = $this->pdo->prepare("SELECT id FROM apropos_contents WHERE key = ?");
|
||||||
|
$stmt->execute([$key]);
|
||||||
|
if (!$stmt->fetch()) {
|
||||||
|
throw new Exception("Apropos key not found: $key");
|
||||||
|
}
|
||||||
|
$storedValue = is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : (string)$value;
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"UPDATE apropos_contents SET value = ?, updated_at = CURRENT_TIMESTAMP WHERE key = ?"
|
||||||
|
);
|
||||||
|
$stmt->execute([$storedValue, $key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all apropos contents as [key => value] pairs (raw DB values).
|
||||||
|
*/
|
||||||
|
public function getAllAproposContents(): array {
|
||||||
|
$stmt = $this->pdo->query("SELECT key, value, updated_at FROM apropos_contents ORDER BY key");
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// SINGLETON PATTERN ENFORCEMENT
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent cloning
|
* Prevent cloning
|
||||||
*/
|
*/
|
||||||
|
|||||||
14
storage/migrations/010_apropos_contents.sql
Normal file
14
storage/migrations/010_apropos_contents.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- ── apropos_contents table (structured data for the "À propos" page) ───────
|
||||||
|
-- Replaces config/apropos.php.
|
||||||
|
CREATE TABLE IF NOT EXISTS apropos_contents (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
key TEXT NOT NULL UNIQUE, -- 'contacts', 'credits', 'erg_url'
|
||||||
|
value TEXT, -- JSON array for contacts/credits, plain string for erg_url
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed with the current defaults from config/apropos.php
|
||||||
|
INSERT OR IGNORE INTO apropos_contents (key, value) VALUES
|
||||||
|
('contacts', '[{"name":"Laurent Leprince","role":"Bibliothèque d''architecture, d''ingénierie architecturale, d''urbanisme (BAIU) :","email":"laurent.leprince@uclouvain.be"},{"name":"Xavier Gorgol","role":"Responsable des mémoires de l''ERG :","email":"xavier.gorgol@erg.be"},{"name":"Brigitte Ledune","role":"Cours de suivi de mémoire :","email":"brigitte.ledune@erg.be"}]'),
|
||||||
|
('credits', '[{"label":"Design & développement","value":"Olivia Marly, Théophile Gerveau-Mercie & Théo Hennequin"},{"label":"Typographies","value":"Ductus (Amélie Dumont) & BBB DM Sans"}]'),
|
||||||
|
('erg_url', 'https://erg.be');
|
||||||
3
storage/migrations/011_apropos_urls.sql
Normal file
3
storage/migrations/011_apropos_urls.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-- Optional URL fields for credits and contacts.
|
||||||
|
-- No structural change; items already stored as JSON with optional url keys.
|
||||||
|
-- Default data updated to include urls where applicable.
|
||||||
Binary file not shown.
@@ -319,8 +319,23 @@ CREATE TABLE IF NOT EXISTS pages (
|
|||||||
INSERT OR IGNORE INTO pages (slug, title, content) VALUES
|
INSERT OR IGNORE INTO pages (slug, title, content) VALUES
|
||||||
('charte', 'Charte', 'Contenu à venir'),
|
('charte', 'Charte', 'Contenu à venir'),
|
||||||
('about', 'À propos', 'Contenu à venir'),
|
('about', 'À propos', 'Contenu à venir'),
|
||||||
('licenses', 'Licences', 'Contenu à venir'),
|
('licenses', 'Licences', 'Contenu à venir');
|
||||||
('contact', 'Contact', 'Contenu à venir');
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- APROPOS CONTENTS (structured data for the "À propos" page)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS apropos_contents (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
key TEXT NOT NULL UNIQUE, -- 'contacts', 'credits', 'erg_url'
|
||||||
|
value TEXT, -- JSON array or plain string
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO apropos_contents (key, value) VALUES
|
||||||
|
('contacts', '[{"name":"Laurent Leprince","role":"Bibliothèque d''architecture, d''ingénierie architecturale, d''urbanisme (BAIU) :","email":"laurent.leprince@uclouvain.be"},{"name":"Xavier Gorgol","role":"Responsable des mémoires de l''ERG :","email":"xavier.gorgol@erg.be"},{"name":"Brigitte Ledune","role":"Cours de suivi de mémoire :","email":"brigitte.ledune@erg.be"}]'),
|
||||||
|
('credits', '[{"label":"Design & développement","value":"Olivia Marly, Théophile Gerveau-Mercie & Théo Hennequin"},{"label":"Typographies","value":"Ductus (Amélie Dumont) & BBB DM Sans"}]'),
|
||||||
|
('erg_url', 'https://erg.be');
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- INDEXES for performance
|
-- INDEXES for performance
|
||||||
@@ -365,6 +380,12 @@ CREATE TRIGGER IF NOT EXISTS update_pages_timestamp
|
|||||||
AFTER UPDATE ON pages
|
AFTER UPDATE ON pages
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE pages SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
UPDATE pages SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS update_apropos_contents_timestamp
|
||||||
|
AFTER UPDATE ON apropos_contents
|
||||||
|
BEGIN
|
||||||
|
UPDATE apropos_contents SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ $_thesisId = $_GET['id'] ?? null;
|
|||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/admin/" <?= $_currentPage === 'index.php' ? 'aria-current="page"' : '' ?>>Liste des TFE</a></li>
|
<li><a href="/admin/" <?= $_currentPage === 'index.php' ? 'aria-current="page"' : '' ?>>Liste des TFE</a></li>
|
||||||
<li><a href="/admin/pages.php" <?= in_array($_currentPage, ['pages.php', 'pages-edit.php']) ? 'aria-current="page"' : '' ?>>Pages statiques</a></li>
|
<li><a href="/admin/contenus.php" <?= in_array($_currentPage, ['contenus.php', 'contenus-edit.php']) ? 'aria-current="page"' : '' ?>>Contenus</a></li>
|
||||||
<li><a href="/admin/tags.php" <?= $_currentPage === 'tags.php' ? 'aria-current="page"' : '' ?>>Mots-clés</a></li>
|
<li><a href="/admin/tags.php" <?= $_currentPage === 'tags.php' ? 'aria-current="page"' : '' ?>>Mots-clés</a></li>
|
||||||
<li><a href="/admin/system.php" <?= in_array($_currentPage, ['system.php', 'status.php', 'logs.php']) ? 'aria-current="page"' : '' ?>>Système</a></li>
|
<li><a href="/admin/system.php" <?= in_array($_currentPage, ['system.php', 'status.php', 'logs.php']) ? 'aria-current="page"' : '' ?>>Système</a></li>
|
||||||
<li><a href="/admin/acces-etudiante.php" <?= $_currentPage === 'acces-etudiante.php' ? 'aria-current="page"' : '' ?>>Accès étudiant·e</a></li>
|
<li><a href="/admin/acces-etudiante.php" <?= $_currentPage === 'acces-etudiante.php' ? 'aria-current="page"' : '' ?>>Accès étudiant·e</a></li>
|
||||||
|
|||||||
Reference in New Issue
Block a user