mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Refactor about.php
- Hardcode source code URL and credits in about template, remove from DB/admin interface; only contacts remains editable - Merge apropos editables into one À propos section, remove charte, add editable source code URL
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,6 +8,8 @@ app/storage/test.db
|
|||||||
|
|
||||||
### Logs ###
|
### Logs ###
|
||||||
error.log
|
error.log
|
||||||
|
app/storage/logs/*.log
|
||||||
|
!app/storage/logs/.gitkeep
|
||||||
app/storage/maintenance.flag
|
app/storage/maintenance.flag
|
||||||
app/storage/cache/*
|
app/storage/cache/*
|
||||||
!app/storage/cache/.gitkeep
|
!app/storage/cache/.gitkeep
|
||||||
|
|||||||
15
TODO.md
15
TODO.md
@@ -1,5 +1,20 @@
|
|||||||
# XAMXAM TODO
|
# XAMXAM TODO
|
||||||
|
|
||||||
|
## Merge apropos editables into À propos page + remove charte + source code URL
|
||||||
|
- [x] `actions/apropos.php` — only `contacts`; removed credits, erg_url
|
||||||
|
- [x] `actions/page.php` — remove `charte` from allowed slugs
|
||||||
|
- [x] `contenus.php` (front controller) — filter pages to only show `about` + `licenses`
|
||||||
|
- [x] `templates/admin/contenus.php` — restored "Pages statiques" table
|
||||||
|
- [x] `contenus-edit.php` (front controller) — `about` slug loads contacts for unified edit
|
||||||
|
- [x] `templates/admin/contenus-edit.php` — `about_page` type: Markdown editor + contacts on one page
|
||||||
|
- [x] `templates/admin/apropos-groups-form.php` — reusable partial for contacts
|
||||||
|
- [x] `Database.php` — simplified getAproposContent/saveAproposContent (contacts-only JSON)
|
||||||
|
- [x] `storage/schema.sql` — removed credits, erg_url; only contacts remains
|
||||||
|
- [x] `AboutController.php` — removed credits, sourceCode DB loading
|
||||||
|
- [x] `templates/public/about.php` — hardcoded source code URL, hardcoded credits HTML
|
||||||
|
- [x] `apropos.css` — `.apropos-toc-source` styles
|
||||||
|
- [x] `.gitignore` — ignore `app/storage/logs/*.log`
|
||||||
|
|
||||||
## Duplicate TFE submission prevention (fixes)
|
## Duplicate TFE submission prevention (fixes)
|
||||||
- [x] `DuplicateThesisException` — typed exception carrying existing thesis metadata
|
- [x] `DuplicateThesisException` — typed exception carrying existing thesis metadata
|
||||||
- [x] `Database::findDuplicateThesis()` — year + author + normalised-title matching (exact, prefix, Levenshtein ≤10%)
|
- [x] `Database::findDuplicateThesis()` — year + author + normalised-title matching (exact, prefix, Levenshtein ≤10%)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Save handler for apropos contents (contacts, credits).
|
* Save handler for apropos contacts.
|
||||||
* Structure: groups[] with label/role, each having entries[] of {text, url, email}.
|
* Structure: groups[] with role, each having entries[] of {text, url, email}.
|
||||||
*/
|
*/
|
||||||
require_once __DIR__ . "/../../../bootstrap.php";
|
require_once __DIR__ . "/../../../bootstrap.php";
|
||||||
require_once __DIR__ . '/../../../src/AdminAuth.php';
|
require_once __DIR__ . '/../../../src/AdminAuth.php';
|
||||||
@@ -13,7 +13,7 @@ if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
|
|||||||
die("Erreur de sécurité : token invalide.");
|
die("Erreur de sécurité : token invalide.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowedKeys = ['contacts', 'credits'];
|
$allowedKeys = ['contacts'];
|
||||||
$aproposKey = $_POST['apropos_key'] ?? '';
|
$aproposKey = $_POST['apropos_key'] ?? '';
|
||||||
if (!in_array($aproposKey, $allowedKeys)) {
|
if (!in_array($aproposKey, $allowedKeys)) {
|
||||||
die("Clé invalide.");
|
die("Clé invalide.");
|
||||||
@@ -28,21 +28,6 @@ try {
|
|||||||
$cleaned = [];
|
$cleaned = [];
|
||||||
|
|
||||||
foreach ($groups as $group) {
|
foreach ($groups as $group) {
|
||||||
if ($aproposKey === 'credits') {
|
|
||||||
$label = trim($group['label'] ?? '');
|
|
||||||
if ($label === '') continue;
|
|
||||||
$entries = [];
|
|
||||||
foreach ($group['entries'] ?? [] as $entry) {
|
|
||||||
$text = trim($entry['text'] ?? '');
|
|
||||||
if ($text === '') continue;
|
|
||||||
$e = ['text' => $text];
|
|
||||||
$url = trim($entry['url'] ?? '');
|
|
||||||
if ($url !== '') $e['url'] = $url;
|
|
||||||
$entries[] = $e;
|
|
||||||
}
|
|
||||||
if (empty($entries)) continue;
|
|
||||||
$cleaned[] = ['label' => $label, 'entries' => $entries];
|
|
||||||
} else { // contacts
|
|
||||||
$role = trim($group['role'] ?? '');
|
$role = trim($group['role'] ?? '');
|
||||||
if ($role === '') continue;
|
if ($role === '') continue;
|
||||||
$entries = [];
|
$entries = [];
|
||||||
@@ -60,7 +45,6 @@ try {
|
|||||||
if (empty($entries)) continue;
|
if (empty($entries)) continue;
|
||||||
$cleaned[] = ['role' => $role, 'entries' => $entries];
|
$cleaned[] = ['role' => $role, 'entries' => $entries];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($cleaned)) {
|
if (empty($cleaned)) {
|
||||||
die("Au moins un groupe avec des entrées est requis.");
|
die("Au moins un groupe avec des entrées est requis.");
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowedSlugs = ['about', 'licenses', 'charte'];
|
$allowedSlugs = ['about', 'licenses'];
|
||||||
$slug = $_POST['slug'] ?? '';
|
$slug = $_POST['slug'] ?? '';
|
||||||
$content = $_POST['content'] ?? '';
|
$content = $_POST['content'] ?? '';
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ if (empty($_SESSION["csrf_token"])) {
|
|||||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowedPageSlugs = ["about", "licenses", "charte"];
|
$allowedPageSlugs = ["about", "licenses"];
|
||||||
$allowedApropos = ["contacts", "credits"];
|
$allowedApropos = ["contacts"];
|
||||||
|
|
||||||
$pageSlug = $_GET["slug"] ?? "";
|
$pageSlug = $_GET["slug"] ?? "";
|
||||||
$aproposKey = $_GET["apropos"] ?? "";
|
$aproposKey = $_GET["apropos"] ?? "";
|
||||||
@@ -40,7 +40,13 @@ try {
|
|||||||
die("Page introuvable.");
|
die("Page introuvable.");
|
||||||
}
|
}
|
||||||
$editTitle = $page["title"];
|
$editTitle = $page["title"];
|
||||||
|
if ($pageSlug === 'about') {
|
||||||
|
$editType = 'about_page';
|
||||||
|
$aboutContacts = $db->getAproposContent('contacts');
|
||||||
|
$aboutContacts = is_array($aboutContacts) ? $aboutContacts : [];
|
||||||
|
} else {
|
||||||
$editType = "page";
|
$editType = "page";
|
||||||
|
}
|
||||||
} elseif ($formHelpKey) {
|
} elseif ($formHelpKey) {
|
||||||
$editType = "form_help";
|
$editType = "form_help";
|
||||||
$formHelpContent = $db->getFormHelpBlock($formHelpKey);
|
$formHelpContent = $db->getFormHelpBlock($formHelpKey);
|
||||||
@@ -50,7 +56,6 @@ try {
|
|||||||
$value = $db->getAproposContent($aproposKey);
|
$value = $db->getAproposContent($aproposKey);
|
||||||
$editTitle = match($aproposKey) {
|
$editTitle = match($aproposKey) {
|
||||||
'contacts' => 'Contacts',
|
'contacts' => 'Contacts',
|
||||||
'credits' => 'Crédits',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@@ -58,6 +63,13 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$pageTitle = "Éditer : " . $editTitle;
|
$pageTitle = "Éditer : " . $editTitle;
|
||||||
|
|
||||||
|
$initialContent = '';
|
||||||
|
$extraJs = [];
|
||||||
|
$extraJsInline = '';
|
||||||
|
|
||||||
|
if ($editType === 'page' || $editType === 'about_page') {
|
||||||
|
$initialContent = $page["content"] ?? "";
|
||||||
$extraJs = ["/assets/js/overtype.min.js"];
|
$extraJs = ["/assets/js/overtype.min.js"];
|
||||||
$extraJsInline = <<<'JS'
|
$extraJsInline = <<<'JS'
|
||||||
var OT = window.OverType.default || window.OverType;
|
var OT = window.OverType.default || window.OverType;
|
||||||
@@ -69,12 +81,19 @@ var editor = new OT(document.getElementById('editor'), {
|
|||||||
onChange: function(value) { hidden.value = value; }
|
onChange: function(value) { hidden.value = value; }
|
||||||
});
|
});
|
||||||
JS;
|
JS;
|
||||||
|
|
||||||
$initialContent = '';
|
|
||||||
if ($editType === 'page') {
|
|
||||||
$initialContent = $page["content"] ?? "";
|
|
||||||
} elseif ($editType === 'form_help') {
|
} elseif ($editType === 'form_help') {
|
||||||
$initialContent = $formHelpContent;
|
$initialContent = $formHelpContent;
|
||||||
|
$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;
|
||||||
}
|
}
|
||||||
|
|
||||||
$isAdmin = true;
|
$isAdmin = true;
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ if (empty($_SESSION['csrf_token'])) {
|
|||||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allowedPageSlugs = ['about', 'licenses'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$db = new Database();
|
$db = new Database();
|
||||||
$pages = $db->getAllPages();
|
$allPages = $db->getAllPages();
|
||||||
|
$pages = array_values(array_filter($allPages, fn($p) => in_array($p['slug'], $allowedPageSlugs, true)));
|
||||||
$aproposKeys = $db->getAllAproposContents();
|
$aproposKeys = $db->getAllAproposContents();
|
||||||
$formHelpBlocks = $db->getAllFormHelpBlocks();
|
$formHelpBlocks = $db->getAllFormHelpBlocks();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|||||||
@@ -89,6 +89,21 @@
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.apropos-toc-source {
|
||||||
|
padding-top: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.apropos-toc-source a {
|
||||||
|
font-size: var(--step--2);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apropos-toc-source a:hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Right — main content area */
|
/* Right — main content area */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|||||||
@@ -22,14 +22,11 @@ class AboutController
|
|||||||
$rawContent = $this->defaultContent;
|
$rawContent = $this->defaultContent;
|
||||||
}
|
}
|
||||||
$contacts = $db->getAproposContent('contacts');
|
$contacts = $db->getAproposContent('contacts');
|
||||||
$credits = $db->getAproposContent('credits');
|
|
||||||
$contacts = is_array($contacts) && !empty($contacts) ? $contacts : null;
|
$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 = $this->defaultContent;
|
$rawContent = $this->defaultContent;
|
||||||
$contacts = null;
|
$contacts = null;
|
||||||
$credits = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$pd = new Parsedown();
|
$pd = new Parsedown();
|
||||||
@@ -39,7 +36,6 @@ class AboutController
|
|||||||
'currentNav' => 'apropos',
|
'currentNav' => 'apropos',
|
||||||
'aboutHtml' => $pd->text($rawContent),
|
'aboutHtml' => $pd->text($rawContent),
|
||||||
'contacts' => $contacts,
|
'contacts' => $contacts,
|
||||||
'credits' => $credits,
|
|
||||||
'pageTitle' => 'À Propos – XAMXAM',
|
'pageTitle' => 'À Propos – XAMXAM',
|
||||||
'metaDescription' => "À propos de XAMXAM, le répertoire des mémoires de fin d'études de l'erg – École de Recherches Graphiques de Bruxelles.",
|
'metaDescription' => "À propos de XAMXAM, le répertoire des mémoires de fin d'études de l'erg – École de Recherches Graphiques de Bruxelles.",
|
||||||
'extraCss' => ['/assets/css/apropos.css'],
|
'extraCss' => ['/assets/css/apropos.css'],
|
||||||
|
|||||||
@@ -2239,31 +2239,21 @@ class Database
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $row['value'];
|
$decoded = json_decode($row['value'], true);
|
||||||
if ($key === 'erg_url') {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
$decoded = json_decode($value, true);
|
|
||||||
return is_array($decoded) ? $decoded : null;
|
return is_array($decoded) ? $decoded : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save an apropos content value by key.
|
* Save an apropos content value by key (contacts JSON).
|
||||||
* @param string $key
|
|
||||||
* @param mixed $value array for contacts/credits, string for erg_url
|
|
||||||
*/
|
*/
|
||||||
public function saveAproposContent(string $key, $value): void
|
public function saveAproposContent(string $key, array $value): void
|
||||||
{
|
{
|
||||||
$stmt = $this->pdo->prepare('SELECT id FROM apropos_contents WHERE key = ?');
|
$storedValue = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||||
$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(
|
$stmt = $this->pdo->prepare(
|
||||||
'UPDATE apropos_contents SET value = ?, updated_at = CURRENT_TIMESTAMP WHERE key = ?'
|
'INSERT INTO apropos_contents (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP'
|
||||||
);
|
);
|
||||||
$stmt->execute([$storedValue, $key]);
|
$stmt->execute([$key, $storedValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,3 +11,4 @@
|
|||||||
{"timestamp":"2026-05-05T16:57:57+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"thesis","action":"publish","status":"success","context":{"count":15,"ids":[53,52,51,50,49,48,47,46,45,44,43,42,41,40,39]}}
|
{"timestamp":"2026-05-05T16:57:57+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"thesis","action":"publish","status":"success","context":{"count":15,"ids":[53,52,51,50,49,48,47,46,45,44,43,42,41,40,39]}}
|
||||||
{"timestamp":"2026-05-05T16:58:02+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"thesis","action":"publish","status":"success","context":{"count":25,"ids":[178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154]}}
|
{"timestamp":"2026-05-05T16:58:02+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"thesis","action":"publish","status":"success","context":{"count":25,"ids":[178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154]}}
|
||||||
{"timestamp":"2026-05-07T16:15:27+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"system","action":"files_export","status":"success","context":{"file_count":0,"byte_size":248}}
|
{"timestamp":"2026-05-07T16:15:27+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"system","action":"files_export","status":"success","context":{"file_count":0,"byte_size":248}}
|
||||||
|
{"timestamp":"2026-05-07T16:56:50+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"apropos","action":"edit","status":"success","context":{"key":"credits"}}
|
||||||
|
|||||||
@@ -408,8 +408,8 @@ SELECT * FROM smtp_settings WHERE id = 1;
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS apropos_contents (
|
CREATE TABLE IF NOT EXISTS apropos_contents (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
key TEXT NOT NULL UNIQUE, -- 'contacts', 'credits', 'erg_url'
|
key TEXT NOT NULL UNIQUE, -- 'contacts'
|
||||||
value TEXT, -- JSON array or plain string
|
value TEXT, -- JSON array
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -433,23 +433,6 @@ INSERT OR IGNORE INTO apropos_contents (key, value) VALUES
|
|||||||
{"text": "Brigitte Ledune", "url": "", "email": "brigitte.ledune@erg.be"}
|
{"text": "Brigitte Ledune", "url": "", "email": "brigitte.ledune@erg.be"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]'),
|
|
||||||
('credits', '[
|
|
||||||
{
|
|
||||||
"label": "Design & développement",
|
|
||||||
"entries": [
|
|
||||||
{"text": "Olivia Marly", "url": ""},
|
|
||||||
{"text": "Théophile Gerveau-Mercie", "url": ""},
|
|
||||||
{"text": "Théo Hennequin", "url": ""}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Typographies",
|
|
||||||
"entries": [
|
|
||||||
{"text": "Ductus (Amélie Dumont)", "url": ""},
|
|
||||||
{"text": "BBB DM Sans", "url": ""}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]');
|
]');
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|||||||
115
app/templates/admin/apropos-groups-form.php
Normal file
115
app/templates/admin/apropos-groups-form.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Reusable partial for apropos contacts groups form.
|
||||||
|
* Expected variables:
|
||||||
|
* $aproposKey string 'contacts'
|
||||||
|
* $groups array Existing groups data
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<form action="/admin/actions/apropos.php" method="post" class="admin-form" id="apropos-form-<?= $aproposKey ?>">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||||
|
<input type="hidden" name="apropos_key" value="<?= htmlspecialchars($aproposKey) ?>">
|
||||||
|
|
||||||
|
<?php foreach ($groups as $gi => $group): ?>
|
||||||
|
<fieldset class="apropos-group">
|
||||||
|
<legend>Contact <?= $gi + 1 ?></legend>
|
||||||
|
<label for="group_f_<?= $aproposKey ?>_<?= $gi ?>_role">Rôle :</label>
|
||||||
|
<input type="text" id="group_f_<?= $aproposKey ?>_<?= $gi ?>_role"
|
||||||
|
name="groups[<?= $gi ?>][role]"
|
||||||
|
value="<?= htmlspecialchars($group['role'] ?? '') ?>">
|
||||||
|
|
||||||
|
<?php $entries = is_array($group['entries'] ?? null) ? $group['entries'] : []; ?>
|
||||||
|
<?php foreach ($entries as $ei => $entry): ?>
|
||||||
|
<div class="apropos-entry">
|
||||||
|
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_text">Nom :</label>
|
||||||
|
<input type="text" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_text"
|
||||||
|
name="groups[<?= $gi ?>][entries][<?= $ei ?>][text]"
|
||||||
|
value="<?= htmlspecialchars($entry['text'] ?? '') ?>">
|
||||||
|
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_email">Email :</label>
|
||||||
|
<input type="email" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_email"
|
||||||
|
name="groups[<?= $gi ?>][entries][<?= $ei ?>][email]"
|
||||||
|
value="<?= htmlspecialchars($entry['email'] ?? '') ?>">
|
||||||
|
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_url">Lien (optionnel) :</label>
|
||||||
|
<input type="url" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_url"
|
||||||
|
name="groups[<?= $gi ?>][entries][<?= $ei ?>][url]"
|
||||||
|
value="<?= htmlspecialchars($entry['url'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn--primary btn--sm add-entry-btn-f"
|
||||||
|
data-group="<?= $gi ?>"
|
||||||
|
data-key="<?= $aproposKey ?>">+ Ajouter une entrée</button>
|
||||||
|
</fieldset>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn--primary add-group-btn-f"
|
||||||
|
data-key="<?= $aproposKey ?>">+ Ajouter un contact</button>
|
||||||
|
|
||||||
|
<div class="admin-form-footer">
|
||||||
|
<button type="submit" class="btn btn--primary">Enregistrer</button>
|
||||||
|
<a href="/admin/contenus.php" class="btn btn--secondary admin-cancel-link">Annuler</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template id="entry-template-f-<?= $aproposKey ?>">
|
||||||
|
<div class="apropos-entry">
|
||||||
|
<label>Entrée :</label>
|
||||||
|
<input type="text" name="groups[{{gi}}][entries][{{ei}}][text]">
|
||||||
|
<label>Email :</label>
|
||||||
|
<input type="email" name="groups[{{gi}}][entries][{{ei}}][email]">
|
||||||
|
<label>Lien (optionnel) :</label>
|
||||||
|
<input type="url" name="groups[{{gi}}][entries][{{ei}}][url]">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="group-template-f-<?= $aproposKey ?>">
|
||||||
|
<fieldset class="apropos-group">
|
||||||
|
<legend>Contact {{gi}}</legend>
|
||||||
|
<label>Rôle :</label>
|
||||||
|
<input type="text" name="groups[{{gi}}][role]">
|
||||||
|
<button type="button" class="btn btn--primary btn--sm add-entry-btn-f"
|
||||||
|
data-group="{{gi}}"
|
||||||
|
data-key="<?= $aproposKey ?>">+ Ajouter une entrée</button>
|
||||||
|
</fieldset>
|
||||||
|
</template>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var key = '<?= $aproposKey ?>';
|
||||||
|
var form = document.getElementById('apropos-form-' + key);
|
||||||
|
var groupCount = <?= count($groups) ?>;
|
||||||
|
var entryTpl = document.getElementById('entry-template-f-' + key).innerHTML;
|
||||||
|
var groupTpl = document.getElementById('group-template-f-' + key).innerHTML;
|
||||||
|
|
||||||
|
form.querySelectorAll('.add-entry-btn-f').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var gi = parseInt(this.dataset.group);
|
||||||
|
var fieldset = this.closest('fieldset');
|
||||||
|
var entryCount = fieldset.querySelectorAll('.apropos-entry').length;
|
||||||
|
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
|
||||||
|
this.insertAdjacentHTML('beforebegin', html);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
form.querySelector('.add-group-btn-f').addEventListener('click', function() {
|
||||||
|
groupCount++;
|
||||||
|
var html = groupTpl.replaceAll('{{gi}}', groupCount);
|
||||||
|
this.insertAdjacentHTML('beforebegin', html);
|
||||||
|
|
||||||
|
var newGroup = this.previousElementSibling;
|
||||||
|
if (newGroup && newGroup.classList.contains('apropos-group')) {
|
||||||
|
var btn = newGroup.querySelector('.add-entry-btn-f');
|
||||||
|
if (btn) {
|
||||||
|
btn.dataset.group = groupCount;
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var gi = parseInt(this.dataset.group);
|
||||||
|
var fieldset = this.closest('fieldset');
|
||||||
|
var entryCount = fieldset.querySelectorAll('.apropos-entry').length;
|
||||||
|
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
|
||||||
|
this.insertAdjacentHTML('beforebegin', html);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,34 @@
|
|||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
<h1>Éditer : <?= htmlspecialchars($editTitle) ?></h1>
|
<h1>Éditer : <?= htmlspecialchars($editTitle) ?></h1>
|
||||||
|
|
||||||
<?php if ($editType === 'page'): ?>
|
<?php if ($editType === 'about_page'): ?>
|
||||||
|
|
||||||
|
<!-- ── Markdown content ──────────────────────────────────────────────── -->
|
||||||
|
<h2>Contenu de la page</h2>
|
||||||
|
<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="about">
|
||||||
|
|
||||||
|
<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="btn btn--primary">Enregistrer</button>
|
||||||
|
<a href="/admin/contenus.php" class="btn btn--secondary admin-cancel-link">Annuler</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- ── Contacts ──────────────────────────────────────────────────────── -->
|
||||||
|
<h2 style="margin-top:3rem;">Contacts</h2>
|
||||||
|
<?php
|
||||||
|
$aproposKey = 'contacts';
|
||||||
|
$groups = $aboutContacts ?? [];
|
||||||
|
include APP_ROOT . '/templates/admin/apropos-groups-form.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php elseif ($editType === 'page' && $pageSlug !== 'about'): ?>
|
||||||
<form action="/admin/actions/page.php" method="post" class="admin-form">
|
<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="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||||
<input type="hidden" name="slug" value="<?= htmlspecialchars($pageSlug) ?>">
|
<input type="hidden" name="slug" value="<?= htmlspecialchars($pageSlug) ?>">
|
||||||
@@ -38,125 +65,6 @@
|
|||||||
<?php
|
<?php
|
||||||
$groups = is_array($value) ? $value : [];
|
$groups = is_array($value) ? $value : [];
|
||||||
?>
|
?>
|
||||||
<form action="/admin/actions/apropos.php" method="post" class="admin-form" id="apropos-form">
|
<?php include APP_ROOT . '/templates/admin/apropos-groups-form.php'; ?>
|
||||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
|
||||||
<input type="hidden" name="apropos_key" value="<?= htmlspecialchars($aproposKey) ?>">
|
|
||||||
|
|
||||||
<?php foreach ($groups as $gi => $group): ?>
|
|
||||||
<fieldset class="apropos-group">
|
|
||||||
<legend><?= htmlspecialchars($aproposKey === 'contacts' ? 'Contact' : 'Crédit') ?> <?= $gi + 1 ?></legend>
|
|
||||||
<?php if ($aproposKey === 'contacts'): ?>
|
|
||||||
<label for="group_<?= $gi ?>_role">Rôle :</label>
|
|
||||||
<input type="text" id="group_<?= $gi ?>_role"
|
|
||||||
name="groups[<?= $gi ?>][role]"
|
|
||||||
value="<?= htmlspecialchars($group['role'] ?? '') ?>">
|
|
||||||
<?php else: ?>
|
|
||||||
<label for="group_<?= $gi ?>_label">Label :</label>
|
|
||||||
<input type="text" id="group_<?= $gi ?>_label"
|
|
||||||
name="groups[<?= $gi ?>][label]"
|
|
||||||
value="<?= htmlspecialchars($group['label'] ?? '') ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php $entries = is_array($group['entries'] ?? null) ? $group['entries'] : []; ?>
|
|
||||||
<?php foreach ($entries as $ei => $entry): ?>
|
|
||||||
<div class="apropos-entry">
|
|
||||||
<label for="entry_<?= $gi ?>_<?= $ei ?>_text"><?= $aproposKey === 'contacts' ? 'Nom' : 'Texte' ?> :</label>
|
|
||||||
<input type="text" id="entry_<?= $gi ?>_<?= $ei ?>_text"
|
|
||||||
name="groups[<?= $gi ?>][entries][<?= $ei ?>][text]"
|
|
||||||
value="<?= htmlspecialchars($entry['text'] ?? '') ?>">
|
|
||||||
<?php if ($aproposKey === 'contacts'): ?>
|
|
||||||
<label for="entry_<?= $gi ?>_<?= $ei ?>_email">Email :</label>
|
|
||||||
<input type="email" id="entry_<?= $gi ?>_<?= $ei ?>_email"
|
|
||||||
name="groups[<?= $gi ?>][entries][<?= $ei ?>][email]"
|
|
||||||
value="<?= htmlspecialchars($entry['email'] ?? '') ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
<label for="entry_<?= $gi ?>_<?= $ei ?>_url">Lien (optionnel) :</label>
|
|
||||||
<input type="url" id="entry_<?= $gi ?>_<?= $ei ?>_url"
|
|
||||||
name="groups[<?= $gi ?>][entries][<?= $ei ?>][url]"
|
|
||||||
value="<?= htmlspecialchars($entry['url'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<button type="button" class="btn btn--primary btn--sm add-entry-btn" data-group="<?= $gi ?>">+ Ajouter une entrée</button>
|
|
||||||
</fieldset>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<button type="button" class="btn btn--primary" id="add-group-btn">+ Ajouter un <?= $aproposKey === 'contacts' ? 'contact' : 'groupe de crédit' ?></button>
|
|
||||||
|
|
||||||
<div class="admin-form-footer">
|
|
||||||
<button type="submit" class="btn btn--primary">Enregistrer</button>
|
|
||||||
<a href="/admin/contenus.php" class="btn btn--secondary admin-cancel-link">Annuler</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template id="entry-template-<?= $aproposKey ?>">
|
|
||||||
<div class="apropos-entry">
|
|
||||||
<label>Entrée :</label>
|
|
||||||
<input type="text" name="groups[{{gi}}][entries][{{ei}}][text]">
|
|
||||||
<?php if ($aproposKey === 'contacts'): ?>
|
|
||||||
<label>Email :</label>
|
|
||||||
<input type="email" name="groups[{{gi}}][entries][{{ei}}][email]">
|
|
||||||
<?php endif; ?>
|
|
||||||
<label>Lien (optionnel) :</label>
|
|
||||||
<input type="url" name="groups[{{gi}}][entries][{{ei}}][url]">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template id="group-template-<?= $aproposKey ?>">
|
|
||||||
<fieldset class="apropos-group">
|
|
||||||
<legend><?= htmlspecialchars($aproposKey === 'contacts' ? 'Contact' : 'Crédit') ?> {{gi}}</legend>
|
|
||||||
<?php if ($aproposKey === 'contacts'): ?>
|
|
||||||
<label>Rôle :</label>
|
|
||||||
<input type="text" name="groups[{{gi}}][role]">
|
|
||||||
<?php else: ?>
|
|
||||||
<label>Label :</label>
|
|
||||||
<input type="text" name="groups[{{gi}}][label]">
|
|
||||||
<?php endif; ?>
|
|
||||||
<button type="button" class="btn btn--primary btn--sm add-entry-btn" data-group="{{gi}}">+ Ajouter une entrée</button>
|
|
||||||
</fieldset>
|
|
||||||
</template>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
const aproposKey = '<?= $aproposKey ?>';
|
|
||||||
let groupCount = <?= count($groups) ?>;
|
|
||||||
const entryTpl = document.getElementById('entry-template-' + aproposKey).innerHTML;
|
|
||||||
const groupTpl = document.getElementById('group-template-' + aproposKey).innerHTML;
|
|
||||||
|
|
||||||
// Add entry to a group
|
|
||||||
document.querySelectorAll('.add-entry-btn').forEach(btn => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
const gi = parseInt(this.dataset.group);
|
|
||||||
const fieldset = this.closest('fieldset');
|
|
||||||
const entryCount = fieldset.querySelectorAll('.apropos-entry').length;
|
|
||||||
const html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
|
|
||||||
this.insertAdjacentHTML('beforebegin', html);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add new group
|
|
||||||
document.getElementById('add-group-btn').addEventListener('click', function() {
|
|
||||||
groupCount++;
|
|
||||||
const html = groupTpl.replaceAll('{{gi}}', groupCount);
|
|
||||||
this.insertAdjacentHTML('beforebegin', html);
|
|
||||||
|
|
||||||
// Re-bind add-entry buttons for the new group
|
|
||||||
const newGroup = this.previousElementSibling;
|
|
||||||
if (newGroup && newGroup.classList.contains('apropos-group')) {
|
|
||||||
const btn = newGroup.querySelector('.add-entry-btn');
|
|
||||||
if (btn) {
|
|
||||||
btn.dataset.group = groupCount;
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
const gi = parseInt(this.dataset.group);
|
|
||||||
const fieldset = this.closest('fieldset');
|
|
||||||
const entryCount = fieldset.querySelectorAll('.apropos-entry').length;
|
|
||||||
const html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
|
|
||||||
this.insertAdjacentHTML('beforebegin', html);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -37,38 +37,6 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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',
|
|
||||||
};
|
|
||||||
?>
|
|
||||||
<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="btn btn--primary btn--sm">Éditer</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════════════════
|
<!-- ═══════════════════════════════════════════════════════════════════
|
||||||
Blocs d'aide du formulaire étudiant·e
|
Blocs d'aide du formulaire étudiant·e
|
||||||
═══════════════════════════════════════════════════════════════════ -->
|
═══════════════════════════════════════════════════════════════════ -->
|
||||||
|
|||||||
@@ -33,15 +33,18 @@ function renderEntries(array $entries): string {
|
|||||||
<?php if (!empty($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($credits)): ?>
|
|
||||||
<li><a href="#apropos-credits">Crédits</a></li>
|
<li><a href="#apropos-credits">Crédits</a></li>
|
||||||
<?php endif; ?>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div class="apropos-toc-erg">
|
<div class="apropos-toc-erg">
|
||||||
<a href="https://erg.be" target="_blank" rel="noopener">
|
<a href="https://erg.be" target="_blank" rel="noopener">
|
||||||
Site de l'erg ↗
|
Site de l'erg ↗
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="apropos-toc-source">
|
||||||
|
<a href="https://git.erg.school/PostERG/xamxam" target="_blank" rel="noopener">
|
||||||
|
Code source ↗
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- MIDDLE: main prose + sections -->
|
<!-- MIDDLE: main prose + sections -->
|
||||||
@@ -77,20 +80,27 @@ function renderEntries(array $entries): string {
|
|||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($credits)): ?>
|
<!-- Credits section (hardcoded) -->
|
||||||
<!-- 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 ($credits as $group): ?>
|
|
||||||
<div class="apropos-credit-row">
|
<div class="apropos-credit-row">
|
||||||
<dt><?= htmlspecialchars($group['label']) ?></dt>
|
<dt>Design & développement</dt>
|
||||||
<dd><?= renderEntries($group['entries'] ?? []) ?></dd>
|
<dd>
|
||||||
|
<span class="apropos-entry">Olivia Marly</span>,
|
||||||
|
<span class="apropos-entry">Théophile Gerveau-Mercie</span> &
|
||||||
|
<span class="apropos-entry">Théo Hennequin</span>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="apropos-credit-row">
|
||||||
|
<dt>Typographies</dt>
|
||||||
|
<dd>
|
||||||
|
<span class="apropos-entry">Ductus (Amélie Dumont)</span> &
|
||||||
|
<span class="apropos-entry">BBB DM Sans</span>
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
|
||||||
</dl>
|
</dl>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user