diff --git a/TODO.md b/TODO.md
index 24b4658..0600160 100644
--- a/TODO.md
+++ b/TODO.md
@@ -2,3 +2,5 @@
- [x] Fix broken `flash-messages.php` include in admin footer
- [x] Make `.repertoire-col` columns scrollable instead of `.search-main`
+- [x] Replace JS toast system with pure HTMX toast fragment (top-right, CSS-only auto-fade)
+- [x] Separate admin views from controllers: move all HTML to `templates/admin/*.php`, fragments to `templates/admin/partials/`
diff --git a/app/public/admin/acces-etudiante.php b/app/public/admin/acces-etudiante.php
index c1379b6..69c3558 100644
--- a/app/public/admin/acces-etudiante.php
+++ b/app/public/admin/acces-etudiante.php
@@ -13,193 +13,7 @@ $baseUrl = $protocol . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost');
$pageTitle = 'Accès étudiant·e';
$isAdmin = true;
$bodyClass = 'admin-body';
-?>
-
-
-
-
-
-
-
-
-
- Aucun lien d'accès créé. Cliquez sur « Créer un lien » pour générer un lien partageable.
-
-
-
-
- Lien
- Statut
- Mot de passe
- Utilisations
- Expiration
- Créé le
- Actions
-
-
-
-
-
-
-
- = htmlspecialchars($link['slug']) ?>
-
-
-
-
- = $statusLabel ?>
-
- = $statusLabel ?>
-
- = $statusLabel ?>
-
-
- = $hasPassword ? '🔒 Oui' : 'Non' ?>
- = intval($link['usage_count']) ?>
- = $expires ?>
- = $created ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/acces-etudiante.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/account.php b/app/public/admin/account.php
index 4d27531..b081d75 100644
--- a/app/public/admin/account.php
+++ b/app/public/admin/account.php
@@ -5,104 +5,14 @@ AdminAuth::requireLogin();
$pageTitle = "Compte administrateur";
-$hasPassword = AdminAuth::hasPassword();
-
-// Flash messages are consumed by the flash-messages partial below.
+$hasPassword = AdminAuth::hasPassword();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
-?>
-
-
-
- Compte administrateur
-
-
-
-
-
-
-
Authentification PHP
-
-
-
-
Stockage
-
- site_settings (DB)
-
-
-
-
-
- Aucun mot de passe PHP configuré. Le formulaire ci-dessous stockera
- un hash bcrypt dans la base de données.
-
-
-
-
-
- = $hasPassword ? 'Changer le mot de passe' : 'Définir le mot de passe' ?>
-
-
-
-
-
- Zone de danger
-
-
- Supprimer la configuration du mot de passe PHP
-
- Supprime le hash de la base de données. L'accès admin
- dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée.
-
-
-
-
-
-
-
-
+$isAdmin = true; $bodyClass = 'admin-body';
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/account.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/add.php b/app/public/admin/add.php
index 7cc6956..315d35d 100644
--- a/app/public/admin/add.php
+++ b/app/public/admin/add.php
@@ -23,9 +23,6 @@ $formData = $_SESSION['form_data'] ?? [];
unset($_SESSION['form_data']);
$autofocusField = App::consumeAutofocus();
-/**
- * Merge autofocus into the $attrs array for a given field.
- */
function withAutofocus(string $fieldName, array $attrs = []): array {
global $autofocusField;
if ($autofocusField === $fieldName) {
@@ -38,130 +35,17 @@ function old($key, $default = "") {
global $formData;
return isset($formData[$key]) ? htmlspecialchars($formData[$key]) : $default;
}
+
function wasSelected($key, $value) {
global $formData;
if (!isset($formData[$key])) return false;
if (is_array($formData[$key])) return in_array($value, $formData[$key]);
return $formData[$key] == $value;
}
-?>
-
-
-
-
-
-
-
- * Champs obligatoires
-
-
-
-
+include APP_ROOT . '/templates/admin/add.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/contenus-edit.php b/app/public/admin/contenus-edit.php
index 2f8a351..75cca79 100644
--- a/app/public/admin/contenus-edit.php
+++ b/app/public/admin/contenus-edit.php
@@ -15,7 +15,6 @@ $allowedApropos = ["contacts", "credits"];
$pageSlug = $_GET["slug"] ?? "";
$aproposKey = $_GET["apropos"] ?? "";
-// Exactly one target must be specified
if ($pageSlug && !in_array($pageSlug, $allowedPageSlugs)) {
$pageSlug = "";
}
@@ -62,171 +61,15 @@ var editor = new OT(document.getElementById('editor'), {
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
}
-?>
-
-
-
-
- Éditer : = htmlspecialchars($editTitle) ?>
-
-
-
- ">
-
-
- Contenu (Markdown) :
-
-
-
-
-
-
-
-
-
- ">
-
-
- $group): ?>
-
- = htmlspecialchars($aproposKey === 'contacts' ? 'Contact' : 'Crédit') ?> = $gi + 1 ?>
-
- Rôle :
-
-
- Label :
-
-
-
-
- $entry): ?>
-
- = $aproposKey === 'contacts' ? 'Nom' : 'Texte' ?> :
-
-
- Email :
-
-
- Lien (optionnel) :
-
-
-
-
- + Ajouter une entrée
-
-
-
- + Ajouter un = $aproposKey === 'contacts' ? 'contact' : 'groupe de crédit' ?>
-
-
-
-
-
- Entrée :
-
-
- Email :
-
-
- Lien (optionnel) :
-
-
-
-
-
-
- = htmlspecialchars($aproposKey === 'contacts' ? 'Contact' : 'Crédit') ?> {{gi}}
-
- Rôle :
-
-
- Label :
-
-
- + Ajouter une entrée
-
-
-
-
-
-
-
-
+include APP_ROOT . "/templates/header.php";
+include APP_ROOT . '/templates/admin/contenus-edit.php';
+require_once APP_ROOT . "/templates/admin/footer.php";
diff --git a/app/public/admin/contenus.php b/app/public/admin/contenus.php
index b4d6572..5ca39c5 100644
--- a/app/public/admin/contenus.php
+++ b/app/public/admin/contenus.php
@@ -14,72 +14,9 @@ try {
error_log("Error loading contenus: " . $e->getMessage());
die("Erreur lors du chargement des contenus.");
}
-?>
-
-
-
- Contenus
-
-
-
- Pages statiques
-
-
-
-
- Slug
- Titre
- Mis à jour
- Action
-
-
-
-
-
- = htmlspecialchars($p['slug']) ?>
- = htmlspecialchars($p['title']) ?>
- = htmlspecialchars($p['updated_at'] ?? '—') ?>
-
- Éditer
-
-
-
-
-
-
- À propos
-
-
-
-
- Clé
- Type
- Mis à jour
- Action
-
-
-
-
- 'Contacts',
- 'credits' => 'Crédits',
- };
- ?>
-
- = htmlspecialchars($a['key']) ?>
- = htmlspecialchars($typeLabel) ?>
- = htmlspecialchars($a['updated_at'] ?? '—') ?>
-
- Éditer
-
-
-
-
-
-
-
-
+$isAdmin = true; $bodyClass = 'admin-body';
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/contenus.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/edit.php b/app/public/admin/edit.php
index ed2919a..f0a4dbe 100644
--- a/app/public/admin/edit.php
+++ b/app/public/admin/edit.php
@@ -1,12 +1,8 @@
load($thesisId);
- extract($view); // thesis, currentLanguages, currentFormats, jury, lookup tables, pageTitle …
+ extract($view);
} catch (Exception $e) {
error_log("Error loading edit page: " . $e->getMessage());
die("Erreur lors du chargement: " . $e->getMessage());
}
-?>
-
-
-
- Modifier un TFE
-
-
-
-
-
-
-
- 'name'], $autofocusField === 'auteurice' ? ['autofocus' => true] : []); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
- 'email']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
-
-
-
-
- >
- Je veux que mon contact soit accessible à toustes depuis la plateforme xamxam
-
- Si cette case est cochée, le contact apparaît sur la page publique du TFE.
-
-
- true] : [];
- include APP_ROOT . '/templates/partials/form/text-field.php';
- ?>
-
-
-
-
-
-
-
-
-
-
-
- $at['id'], 'name' => $label];
- }, $accessTypes);
- $name = 'access_type_id'; $label = 'Visibilité / Accès :'; $options = $accessOptions; $selected = $currentAccessTypeId; $placeholder = '- Non défini -';
- include APP_ROOT . '/templates/partials/form/select-field.php';
- ?>
-
-
-
-
Note contextuelle :
-
- = htmlspecialchars($currentContextNote ?? '') ?>
- Visible publiquement pour les TFE Interne ou Interdit. Max 1 500 caractères.
-
-
-
-
-
- true] : []; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
-
-
-
-
- Synopsis :
- >= htmlspecialchars($thesis['synopsis'] ?? '') ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Image bannière (accueil) :
-
-
-
-
-
- Supprimer la bannière
-
-
-
-
-
JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 5 MB.
-
-
-
-
-
- Publication :
-
- >
- Publier ce TFE sur le site public
-
-
-
-
-
-
-
-
+$isAdmin = true; $bodyClass = 'admin-body';
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/edit.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/index.php b/app/public/admin/index.php
index b307ffc..f5ae99b 100644
--- a/app/public/admin/index.php
+++ b/app/public/admin/index.php
@@ -323,311 +323,10 @@ try {
error_log("Error loading theses list: " . $e->getMessage());
die("Erreur lors du chargement de la liste.");
}
-?>
-
-
-
+$isAdmin = true; $bodyClass = 'admin-body';
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/index.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
-
-
-
-
-
-
-
0 TFE(s) sélectionné(s)
-
- Publier
- Dépublier
- Supprimer
-
-
-
-
-
-
-
-
-
-
-
-
- Aucun TFE trouvé.
-
-
- 1) {
- echo "{$from}-{$to} sur {$totalCount} TFE";
- } else {
- echo "$totalCount TFE";
- }
- ?>
-
- $searchQuery,
- 'year' => $yearFilter ?: '',
- 'orientation' => $orientationFilter ?: '',
- 'ap' => $apFilter ?: '',
- ]);
-
- $sortLink = function(string $col) use ($sortCol, $sortDir, $sortParams): string {
- $params = $sortParams;
- $params['sort'] = $col;
- $params['dir'] = ($sortCol === $col && $sortDir === 'desc') ? 'asc' : 'desc';
- return '/admin/?' . http_build_query($params);
- };
-
- $sortArrow = function(string $col) use ($sortCol, $sortDir): string {
- if ($sortCol !== $col) return '';
- return $sortDir === 'asc' ? ' ↑' : ' ↓';
- };
- ?>
-
-
-
- $searchQuery,
- 'year' => $yearFilter ?: '',
- 'orientation' => $orientationFilter ?: '',
- 'ap' => $apFilter ?: '',
- 'sort' => $sortCol,
- 'dir' => $sortDir,
- ]);
- include APP_ROOT . '/templates/partials/pagination.php';
- ?>
-
-
-
-
-
-
-
-
-
-
-
⚠ Erreurs :
-
-
- = htmlspecialchars($err) ?>
-
-
-
-
-
-
✓ = htmlspecialchars($importMessage) ?>
-
-
-
-
-
-
-
-
-
Fichier CSV
-
-
-
- Colonnes : Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, Année, AP, Orientation, Finalité, Mots-clés, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU
- Quatre premières lignes ignorées — Séparateur : virgule — UTF-8
-
-
-
-
-
-
-
-
-
- Logs d'importation (= count($importResults) ?> entrées)
-
-
- = htmlspecialchars($r['msg']) ?>
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/public/admin/login.php b/app/public/admin/login.php
index 9e45bbe..8cd2658 100644
--- a/app/public/admin/login.php
+++ b/app/public/admin/login.php
@@ -22,28 +22,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$pageTitle = 'Connexion';
-?>
-
-
-
-
-
-
-
Administration
-
-
⚠ = htmlspecialchars($error) ?>
-
-
-
- Mot de passe
-
-
-
-
-
-
-
-
-
+$isAdmin = true; $bodyClass = 'admin-body';
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/login.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/parametres.php b/app/public/admin/parametres.php
index 6d72ac6..ae938c7 100644
--- a/app/public/admin/parametres.php
+++ b/app/public/admin/parametres.php
@@ -5,313 +5,23 @@ AdminAuth::requireLogin();
$pageTitle = "Paramètres";
-$hasPassword = AdminAuth::hasPassword();
-$maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag');
+$hasPassword = AdminAuth::hasPassword();
+$maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag');
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/SmtpRelay.php';
$db = new Database();
-$siteSettings = $db->getAllSettings();
-$stats = $db->getThesesStats();
-$smtpSettings = SmtpRelay::getSettings($db);
+$siteSettings = $db->getAllSettings();
+$stats = $db->getThesesStats();
+$smtpSettings = SmtpRelay::getSettings($db);
$smtpConfigured = SmtpRelay::isConfigured($db);
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
-?>
-
-
-
- Paramètres
-
-
-
-
-
- Maintenance
-
-
-
-
- ⚠ Mode maintenance activé — le site public est inaccessible.
-
-
-
-
-
- Désactiver la maintenance
-
-
-
Site public : en ligne
-
-
-
-
-
- Activer la maintenance
-
-
-
-
-
-
-
- Exporter la base de données
- Télécharger une copie complète de la base de données SQLite.
- Cela inclut tous les TFE, auteurs, jury, mots-clés, paramètres, etc.
-
- Exporter la base de données
-
-
-
-
-
- Supprimer tous les TFE
-
- Supprime définitivement tous les TFE de la base de données, y compris auteurs,
- promoteurs, tags, fichiers associés. Cette action est irréversible .
-
-
-
-
- Supprimer tous les TFE (= $stats['total'] ?? '?' ?>)
-
-
-
-
-
-
-
-
-
- Relay SMTP
-
- Identifiants du serveur SMTP utilisé pour l'envoi d'e-mails
- (notifications, partage de TFE, etc.).
-
-
-
- ✓ Configuré
- = htmlspecialchars($smtpSettings['host']) ?>:= (int)$smtpSettings['port'] ?> (= htmlspecialchars($smtpSettings['encryption']) ?>)
-
- ✗ Non configuré
-
-
-
-
-
-
-
-
-
-
- Expéditeur par défaut
-
-
-
- Enregistrer
-
-
-
-
-
-
-
-
-
-
- Télécharger une copie complète de la base de données SQLite.
- Cela inclut tous les TFE, auteurs, jury, mots-clés, paramètres, etc.
-
-
-
-
-
-
+$isAdmin = true; $bodyClass = 'admin-body';
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/parametres.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/system-fragment.php b/app/public/admin/system-fragment.php
index eaf3d7a..7001fd7 100644
--- a/app/public/admin/system-fragment.php
+++ b/app/public/admin/system-fragment.php
@@ -23,14 +23,14 @@ if (!AdminAuth::isAuthenticated()) {
}
// ── Validate inputs ────────────────────────────────────────────────────────
-$tab = $_GET['tab'] ?? 'nginx_access';
-if ($tab !== 'nginx_config' && !array_key_exists($tab, SystemController::LOG_FILES)) {
- $tab = 'nginx_access';
+$activeTab = $_GET['tab'] ?? 'nginx_access';
+if ($activeTab !== 'nginx_config' && !array_key_exists($activeTab, SystemController::LOG_FILES)) {
+ $activeTab = 'nginx_access';
}
-$n = isset($_GET['n']) ? (int) $_GET['n'] : 100;
-if (!in_array($n, SystemController::ALLOWED_LINES, true)) {
- $n = 100;
+$selectedN = isset($_GET['n']) ? (int) $_GET['n'] : 100;
+if (!in_array($selectedN, SystemController::ALLOWED_LINES, true)) {
+ $selectedN = 100;
}
header('Content-Type: text/html; charset=utf-8');
@@ -42,105 +42,19 @@ $_cache = new SystemCache($_db->getPDO());
$_controller = new SystemController($_db, $_cache);
// ── Render ─────────────────────────────────────────────────────────────────
-if ($tab === 'nginx_config') {
- $data = $_controller->getNginxConfigData();
- $lines = $data['lines'];
- $source = $data['source'];
- $meta = $data['meta'];
- $error = $data['error'];
-
- if ($meta): ?>
-
- = htmlspecialchars($meta['path']) ?>
- = $meta['size'] ?>
- = $meta['mtime'] ?>
-
- ● Config déployée
-
- ⚠ Référence locale (config live inaccessible)
-
-
-
-
-
Configuration nginx non disponible
-
= htmlspecialchars($error) ?>
-
-
- En développement, /etc/nginx/sites-available/posterg n'existe pas.
- La config de référence se trouve dans nginx/posterg.conf.
-
-
-
-
-Le fichier de configuration est vide.
-
-
- Copier
- $line): ?>
- = htmlspecialchars($line, ENT_QUOTES | ENT_SUBSTITUTE) ?>
-
-
-getNginxConfigData();
+ $nginxConfigLines = $nginxData['lines'];
+ $nginxConfigSource = $nginxData['source'];
+ $nginxConfigMeta = $nginxData['meta'];
+ $nginxConfigError = $nginxData['error'];
+ include APP_ROOT . '/templates/admin/partials/system-nginx-config-panel.php';
} else {
- // ── Log tab ────────────────────────────────────────────────────────────
- $data = $_controller->getLogData($tab, $n);
- $logLines = $data['lines'];
- $logError = $data['error'];
- $logMeta = $data['meta'];
- ?>
-
-
- Afficher
-
-
- >= $opt ?> dernières lignes
-
-
-
- 0): ?>
- = count($logLines) ?> ligne(s)
-
-
+ $logData = $_controller->getLogData($activeTab, $selectedN);
+ $logLines = $logData['lines'];
+ $logError = $logData['error'];
+ $logFileMeta = $logData['meta'];
-
-
- = htmlspecialchars($logMeta['path']) ?>
- = $logMeta['size'] ?>
- = $logMeta['mtime'] ?>
-
-
-
-
-
-
Journaux non disponibles
-
= $logError ?>
-
-
- En environnement de développement, les logs nginx ne sont pas disponibles.
- Cette page est pleinement fonctionnelle sur le serveur de production.
-
-
-
-
-Le fichier journal est vide.
-
-
- Copier
- $line): ?>
- = htmlspecialchars($line, ENT_QUOTES | ENT_SUBSTITUTE) ?>
-
-
-getPDO());
$_controller = new SystemController($_db, $_cache);
-// ?refresh=1 force-busts all cached sections
if (isset($_GET['refresh']) && $_GET['refresh'] === '1') {
$_controller->invalidateAll();
}
// ── Status / PHP / Disk data ──────────────────────────────────────────────────
-$statusData = $_controller->getStatusData();
-$checks = $statusData['checks'];
-$statusCached = $statusData['cached'];
+$statusData = $_controller->getStatusData();
+$checks = $statusData['checks'];
+$statusCached = $statusData['cached'];
$statusCacheAge = $statusData['cacheAge'];
-$phpInfo = $_controller->getPhpInfo();
+$phpInfo = $_controller->getPhpInfo();
$diskInfo = $_controller->getDiskInfo();
$diskTotal = $diskInfo['total'];
@@ -35,7 +34,7 @@ $diskColor = SystemController::diskColor($diskPct);
// ── Active tab + line count ───────────────────────────────────────────────────
$activeTab = $_GET['tab'] ?? 'nginx_access';
if ($activeTab === 'status') {
- $activeTab = 'nginx_access'; // legacy redirect
+ $activeTab = 'nginx_access';
} elseif ($activeTab !== 'nginx_config' && !array_key_exists($activeTab, SystemController::LOG_FILES)) {
$activeTab = 'nginx_access';
}
@@ -46,9 +45,9 @@ if (!in_array($selectedN, SystemController::ALLOWED_LINES, true)) {
}
// ── Tab content data ──────────────────────────────────────────────────────────
-$logLines = null;
-$logError = null;
-$logFileMeta = null;
+$logLines = null;
+$logError = null;
+$logFileMeta = null;
$nginxConfigLines = null;
$nginxConfigSource = null;
@@ -70,289 +69,11 @@ if ($activeTab === 'nginx_config') {
$isAdmin = true; $bodyClass = 'admin-body';
$extraCss = ['/assets/css/system.css'];
-// HTMX loaded once in footer; status collapse + copy via inline JS
require_once APP_ROOT . '/templates/head.php';
-// Restore collapsed state from cookie
$collapsed = $_COOKIE['sys_collapsed'] ?? null;
$statusInitiallyCollapsed = $collapsed === '1';
-?>
-
-
-
-
- Système
-
-
- Affiché le = date('d/m/Y à H:i:s') ?> —
- Rafraîchir —
- Forcer actualisation
-
-
-
-
-
-
- >
-
-
-
-
-
-
-
= htmlspecialchars($check['detail']) ?>
-
-
-
-
-
-
-
-
-
-
-
- $def): ?>
- >
- = htmlspecialchars($def['label']) ?>
-
-
- >nginx — config
-
-
-
-
-
-
-
-
-
-
- = htmlspecialchars($nginxConfigMeta['path']) ?>
- = $nginxConfigMeta['size'] ?>
- = $nginxConfigMeta['mtime'] ?>
-
- ● Config déployée
-
- ⚠ Référence locale (config live inaccessible)
-
-
-
-
-
-
-
Configuration nginx non disponible
-
= htmlspecialchars($nginxConfigError) ?>
-
-
- En développement, /etc/nginx/sites-available/posterg n'existe pas.
- La config de référence se trouve dans nginx/posterg.conf.
-
-
-
-
-
-
Le fichier de configuration est vide.
-
-
-
-
- Copier
-
- $line): ?>
- = htmlspecialchars($line, ENT_QUOTES | ENT_SUBSTITUTE) ?>
-
-
-
-
-
-
-
-
-
- Afficher
-
- Afficher
-
-
- >
- = $n ?> dernières lignes
-
-
-
-
- 0): ?>
- = count($logLines) ?> ligne(s)
-
-
-
-
-
-
- = htmlspecialchars(SystemController::LOG_FILES[$activeTab]['path']) ?>
- = $logFileMeta['size'] ?>
- = $logFileMeta['mtime'] ?>
-
-
-
-
-
-
-
Journaux non disponibles
-
= $logError ?>
-
-
- En environnement de développement, les logs nginx ne sont pas disponibles.
- Cette page est pleinement fonctionnelle sur le serveur de production.
-
-
-
-
-
-
Le fichier journal est vide.
-
-
-
-
- Copier
-
- $line): ?>
- = htmlspecialchars($line, ENT_QUOTES | ENT_SUBSTITUTE) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/system.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/tags.php b/app/public/admin/tags.php
index 899dd82..dea685d 100644
--- a/app/public/admin/tags.php
+++ b/app/public/admin/tags.php
@@ -18,74 +18,8 @@ try {
die("Erreur : " . htmlspecialchars($e->getMessage()));
}
-// Flash messages are consumed by the flash-messages partial below.
-?>
-
-
-
-
- Mots-clés (= count($tags) ?>)
-
-
-
-
-
-
-
+$isAdmin = true; $bodyClass = 'admin-body';
+require_once APP_ROOT . '/templates/head.php';
+include APP_ROOT . '/templates/header.php';
+include APP_ROOT . '/templates/admin/tags.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/thanks.php b/app/public/admin/thanks.php
index 60a6be4..1ad00ab 100644
--- a/app/public/admin/thanks.php
+++ b/app/public/admin/thanks.php
@@ -1,9 +1,7 @@
0) {
try {
$db = new Database();
-
- // Get thesis data
$thesis = $db->getThesis($thesisId);
if (!$thesis) {
@@ -48,7 +43,6 @@ if (isset($_GET['id'])) {
$error = "Aucun identifiant spécifié.";
}
-// Helper function to format file size
function formatFileSize($bytes) {
if ($bytes >= 1073741824) {
return number_format($bytes / 1073741824, 2) . ' GB';
@@ -61,135 +55,12 @@ function formatFileSize($bytes) {
}
}
-// Set page title for header
$pageTitle = "Récapitulatif TFE";
-?>
-
-
-
-
-
-
-
-
-
- Récapitulatif TFE
-
-
- ⚠ = htmlspecialchars($error) ?>
- Retour au formulaire
-
-
-
- Informations de base
-
- Identifiant = htmlspecialchars($thesis['identifier']) ?>
- Titre = htmlspecialchars($thesis['title']) ?>
-
- Sous-titre = htmlspecialchars($thesis['subtitle']) ?>
-
- Auteur·ice(s) = htmlspecialchars($thesis['authors']) ?>
- Année = htmlspecialchars($thesis['year']) ?>
-
-
-
-
- Détails académiques
-
- Orientation = htmlspecialchars($thesis['orientation'] ?? '–') ?>
- Atelier pratique = htmlspecialchars($thesis['ap_program'] ?? '–') ?>
- Finalité = htmlspecialchars($thesis['finality_type'] ?? '–') ?>
-
- Promoteur·ice(s) = htmlspecialchars($thesis['supervisors']) ?>
-
-
-
-
-
- Contenu
-
-
- Langue(s) = htmlspecialchars($thesis['languages']) ?>
-
-
- Format(s) = htmlspecialchars($thesis['formats']) ?>
-
-
- Mots-clés = htmlspecialchars($thesis['keywords']) ?>
-
-
- Durée / Taille = htmlspecialchars($thesis['file_size_info']) ?>
-
-
- Lien = htmlspecialchars($thesis['baiu_link']) ?>
-
-
-
-
-
-
- Fichiers
-
- Type Fichier Taille Date
-
-
-
- = htmlspecialchars($f['file_type']) ?>
- = htmlspecialchars($f['file_name']) ?>
- = formatFileSize($f['file_size']) ?>
- = date('d/m/Y H:i', strtotime($f['uploaded_at'])) ?>
-
-
-
-
-
-
-
-
-
-
- Aucune donnée à afficher.
- Retour au formulaire
-
-
-
-
-
-
+include APP_ROOT . '/templates/admin/thanks.php';
+require_once APP_ROOT . '/templates/admin/footer.php';
diff --git a/app/public/admin/toast-fragment.php b/app/public/admin/toast-fragment.php
index 3862765..2f61873 100644
--- a/app/public/admin/toast-fragment.php
+++ b/app/public/admin/toast-fragment.php
@@ -17,16 +17,5 @@ if (!$flash['error'] && !$flash['success']) {
http_response_code(204);
exit;
}
-?>
-
-
- ⚠
- = htmlspecialchars($flash['error']) ?>
-
-
-
-
- ✓
- = htmlspecialchars($flash['success']) ?>
-
-
+
+include APP_ROOT . '/templates/admin/partials/toast.php';
diff --git a/app/public/assets/css/admin.css b/app/public/assets/css/admin.css
index 14b4ebb..f5cf26f 100644
--- a/app/public/assets/css/admin.css
+++ b/app/public/assets/css/admin.css
@@ -335,18 +335,17 @@ label:has(+ div > input:required)::after {
filter: brightness(0.9);
}
-/* ── Toast messages (bottom-center floating) ─────────────────────────── */
-#toast-container {
+/* ── Toast messages (top-right, CSS-only auto-fade) ─────────────────── */
+#toast-region {
position: fixed;
- bottom: var(--space-l);
- left: 50%;
- transform: translateX(-50%);
+ top: var(--space-l);
+ right: var(--space-l);
z-index: 10000;
display: flex;
- flex-direction: column-reverse;
+ flex-direction: column;
gap: var(--space-xs);
pointer-events: none;
- max-width: calc(100vw - 2 * var(--space-l));
+ max-width: min(480px, calc(100vw - 2 * var(--space-l)));
}
.toast {
@@ -356,35 +355,32 @@ label:has(+ div > input:required)::after {
border-left: 3px solid;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
pointer-events: auto;
- animation: toast-enter 0.35s ease-out forwards;
- max-width: 480px;
backdrop-filter: blur(6px);
+ /* enter then fade out — total visible ~4.35 s */
+ animation: toast-enter 0.35s ease-out,
+ toast-exit 0.4s ease-in 4s forwards;
}
-.toast[data-type="error"] {
+.toast--error {
background: var(--accent-muted);
border-color: var(--error);
color: var(--text-primary);
}
-.toast[data-type="success"] {
+.toast--success {
background: var(--success-muted-bg);
border-color: var(--success);
color: var(--text-primary);
}
-.toast-exit {
- animation: toast-exit 0.3s ease-in forwards;
-}
-
@keyframes toast-enter {
- from { opacity: 0; transform: translateY(20px); }
+ from { opacity: 0; transform: translateY(-12px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes toast-exit {
- from { opacity: 1; transform: translateY(0); }
- to { opacity: 0; transform: translateY(20px); height: 0; padding: 0; margin: 0; overflow: hidden; }
+ from { opacity: 1; }
+ to { opacity: 0; pointer-events: none; }
}
/* ── Stats cards ────────────────────────────────────────────────────────── */
diff --git a/app/storage/test.db b/app/storage/test.db
index 5f064a8..c1f51a0 100644
Binary files a/app/storage/test.db and b/app/storage/test.db differ
diff --git a/app/templates/admin/acces-etudiante.php b/app/templates/admin/acces-etudiante.php
new file mode 100644
index 0000000..2b0cf61
--- /dev/null
+++ b/app/templates/admin/acces-etudiante.php
@@ -0,0 +1,183 @@
+
+
+
+
+
+ Aucun lien d'accès créé. Cliquez sur « Créer un lien » pour générer un lien partageable.
+
+
+
+
+ Lien
+ Statut
+ Mot de passe
+ Utilisations
+ Expiration
+ Créé le
+ Actions
+
+
+
+
+
+
+
+ = htmlspecialchars($link['slug']) ?>
+
+
+
+
+ = $statusLabel ?>
+
+ = $statusLabel ?>
+
+ = $statusLabel ?>
+
+
+ = $hasPassword ? '🔒 Oui' : 'Non' ?>
+ = intval($link['usage_count']) ?>
+ = $expires ?>
+ = $created ?>
+
+
+
+ 👁 Visiter
+
+
+ Copier
+
+
+
+
+
+
+ = $link['is_active'] ? '⏸' : '▶' ?>
+
+
+
+ 🔑
+
+
+
+
+
+
+ 🗑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mot de passe (optionnel)
+
+ Laissez vide pour un lien sans mot de passe.
+
+
+ Expiration (optionnel)
+
+ Laissez vide pour qu'il n'expire jamais.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Nouveau mot de passe
+
+
Laissez vide pour supprimer le mot de passe.
+
+
+
+
+
+
+
diff --git a/app/templates/admin/account.php b/app/templates/admin/account.php
new file mode 100644
index 0000000..e689c96
--- /dev/null
+++ b/app/templates/admin/account.php
@@ -0,0 +1,86 @@
+
+ Compte administrateur
+
+
+
+
+
Authentification PHP
+
+
+
+
Stockage
+
+ site_settings (DB)
+
+
+
+
+
+ Aucun mot de passe PHP configuré. Le formulaire ci-dessous stockera
+ un hash bcrypt dans la base de données.
+
+
+
+
+
+ = $hasPassword ? 'Changer le mot de passe' : 'Définir le mot de passe' ?>
+
+
+
+
+
+
+
Mot de passe actuel
+
+
+
+
+
+
+
+
Nouveau mot de passe
+
+
+ Minimum 12 caractères.
+
+
+
+
+
Confirmer le mot de passe
+
+
+
+
+
+
+
+
+
+
+ Zone de danger
+
+
+ Supprimer la configuration du mot de passe PHP
+
+ Supprime le hash de la base de données. L'accès admin
+ dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée.
+
+
+
+
+
+
+ Supprimer
+
+
+
+
diff --git a/app/templates/admin/add.php b/app/templates/admin/add.php
new file mode 100644
index 0000000..e9eca13
--- /dev/null
+++ b/app/templates/admin/add.php
@@ -0,0 +1,109 @@
+
+
+
+ * Champs obligatoires
+
+ ">
+
+
+
+ Informations du TFE
+
+
+
+ 'name']); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
+ 'email']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
+
+
+
+ >
+ Je veux que mon contact soit accessible à toustes depuis la plateforme xamxam
+
+ Si cette case est cochée, votre contact apparaîtra sur la page publique de votre TFE.
+
+
+
+ Synopsis :
+ >= old('synopsis') ?>
+
+
+
+
+
+
+
+
+ Cadre académique
+
+ 2000, 'max' => date('Y') + 1]);
+ include APP_ROOT . '/templates/partials/form/text-field.php';
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fichiers
+
+
+
+
+
+
+
+
+ Métadonnées complémentaires
+
+
+
+
+
+
+
+ $at['id'], 'name' => $at['name']];
+ }, $enabledAccessTypes);
+ $defaultAccessType = 2;
+ $selectedAccessType = isset($formData['access_type_id'])
+ ? (int)$formData['access_type_id']
+ : $defaultAccessType;
+ $name = 'access_type_id';
+ $label = 'Visibilité / Accès :';
+ $options = $accessOptions;
+ $selected = $selectedAccessType;
+ $placeholder = null;
+ $required = true;
+ $attrs = [];
+ include APP_ROOT . '/templates/partials/form/select-field.php';
+ ?>
+
+
+
+
+ E-mail de confirmation
+
+
+
+
+
+
diff --git a/app/templates/admin/contenus-edit.php b/app/templates/admin/contenus-edit.php
new file mode 100644
index 0000000..fd90249
--- /dev/null
+++ b/app/templates/admin/contenus-edit.php
@@ -0,0 +1,145 @@
+
+ Éditer : = htmlspecialchars($editTitle) ?>
+
+
+
+ ">
+
+
+ Contenu (Markdown) :
+
+
+
+
+
+
+
+
+
+ ">
+
+
+ $group): ?>
+
+ = htmlspecialchars($aproposKey === 'contacts' ? 'Contact' : 'Crédit') ?> = $gi + 1 ?>
+
+ Rôle :
+
+
+ Label :
+
+
+
+
+ $entry): ?>
+
+ = $aproposKey === 'contacts' ? 'Nom' : 'Texte' ?> :
+
+
+ Email :
+
+
+ Lien (optionnel) :
+
+
+
+
+ + Ajouter une entrée
+
+
+
+ + Ajouter un = $aproposKey === 'contacts' ? 'contact' : 'groupe de crédit' ?>
+
+
+
+
+
+ Entrée :
+
+
+ Email :
+
+
+ Lien (optionnel) :
+
+
+
+
+
+
+ = htmlspecialchars($aproposKey === 'contacts' ? 'Contact' : 'Crédit') ?> {{gi}}
+
+ Rôle :
+
+
+ Label :
+
+
+ + Ajouter une entrée
+
+
+
+
+
+
+
diff --git a/app/templates/admin/contenus.php b/app/templates/admin/contenus.php
new file mode 100644
index 0000000..cb47a18
--- /dev/null
+++ b/app/templates/admin/contenus.php
@@ -0,0 +1,61 @@
+
+ Contenus
+
+ Pages statiques
+
+
+
+
+ Slug
+ Titre
+ Mis à jour
+ Action
+
+
+
+
+
+ = htmlspecialchars($p['slug']) ?>
+ = htmlspecialchars($p['title']) ?>
+ = htmlspecialchars($p['updated_at'] ?? '—') ?>
+
+ Éditer
+
+
+
+
+
+
+ À propos
+
+
+
+
+ Clé
+ Type
+ Mis à jour
+ Action
+
+
+
+
+ 'Contacts',
+ 'credits' => 'Crédits',
+ };
+ ?>
+
+ = htmlspecialchars($a['key']) ?>
+ = htmlspecialchars($typeLabel) ?>
+ = htmlspecialchars($a['updated_at'] ?? '—') ?>
+
+ Éditer
+
+
+
+
+
+
diff --git a/app/templates/admin/edit.php b/app/templates/admin/edit.php
new file mode 100644
index 0000000..4c65479
--- /dev/null
+++ b/app/templates/admin/edit.php
@@ -0,0 +1,131 @@
+
+ Modifier un TFE
+
+
+
+
+
+ 'name'], $autofocusField === 'auteurice' ? ['autofocus' => true] : []); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
+ 'email']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
+
+
+
+
+ >
+ Je veux que mon contact soit accessible à toustes depuis la plateforme xamxam
+
+ Si cette case est cochée, le contact apparaît sur la page publique du TFE.
+
+
+ true] : [];
+ include APP_ROOT . '/templates/partials/form/text-field.php';
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+ $at['id'], 'name' => $label];
+ }, $accessTypes);
+ $name = 'access_type_id'; $label = 'Visibilité / Accès :'; $options = $accessOptions; $selected = $currentAccessTypeId; $placeholder = '- Non défini -';
+ include APP_ROOT . '/templates/partials/form/select-field.php';
+ ?>
+
+
+
+
Note contextuelle :
+
+ = htmlspecialchars($currentContextNote ?? '') ?>
+ Visible publiquement pour les TFE Interne ou Interdit. Max 1 500 caractères.
+
+
+
+
+
+ true] : []; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
+
+
+
+
+ Synopsis :
+ >= htmlspecialchars($thesis['synopsis'] ?? '') ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Image bannière (accueil) :
+
+
+
+
+
+ Supprimer la bannière
+
+
+
+
+
JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 5 MB.
+
+
+
+
+
+ Publication :
+
+ >
+ Publier ce TFE sur le site public
+
+
+
+
+
+
diff --git a/app/templates/admin/footer.php b/app/templates/admin/footer.php
index 2bfb0d7..f6c5028 100644
--- a/app/templates/admin/footer.php
+++ b/app/templates/admin/footer.php
@@ -1,5 +1,11 @@
-
-
+
+
@@ -8,30 +14,5 @@
-