Reintroduce TFE duration metadata: DB columns, form fields, controllers, views, and migration

Add 'unsafe-eval' to CSP script-src directives (htmx requires Function())
This commit is contained in:
Pontoporeia
2026-06-11 13:05:37 +02:00
parent 00fed5f0e3
commit d588ae004d
81 changed files with 1061 additions and 840 deletions

View File

@@ -152,6 +152,7 @@
<th scope="col">Utilisations</th>
<th scope="col">Expiration</th>
<th scope="col">Créé le</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
@@ -175,6 +176,12 @@
<td style="text-align:center;"><?= intval($link['usage_count']) ?></td>
<td><?= $expires ?></td>
<td><?= $created ?></td>
<td class="admin-actions-col">
<button type="button" class="admin-icon-btn admin-icon-btn--delete" title="Supprimer"
onclick="openDeleteArchivedLinkDialog(<?= $link['id'] ?>)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
@@ -641,6 +648,10 @@ function _executeArchiveLink() {
const form = document.getElementById('archive-link-form-' + _pendingArchiveLinkId);
if (form) form.submit();
}
function openDeleteArchivedLinkDialog(id) {
document.getElementById('delete-archived-link-id').value = id;
document.getElementById('delete-archived-link-dialog').showModal();
}
</script>
<!-- Archive link confirm -->
@@ -658,3 +669,24 @@ function _executeArchiveLink() {
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- ═══════════════════════ DELETE ARCHIVED LINK DIALOG ═══════════════════════ -->
<dialog id="delete-archived-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-archived-link-title">
<div class="admin-dialog__header">
<h2 id="delete-archived-link-title">Supprimer le lien</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer définitivement ce lien archivé ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<form method="post" action="actions/acces-etudiante.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" id="delete-archived-link-id" value="">
<button type="submit" class="btn btn--danger">Supprimer</button>
</form>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>

View File

@@ -1,30 +1,28 @@
<main id="main-content">
<main id="main-content" class="full-editor-page">
<h1><a href="/admin/contenus.php" class="admin-back-btn" title="Retour"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm48-88a8,8,0,0,1-8,8H107.31l18.35,18.34a8,8,0,0,1-11.32,11.32l-32-32a8,8,0,0,1,0-11.32l32-32a8,8,0,0,1,11.32,11.32L107.31,120H168A8,8,0,0,1,176,128Z"></path></svg></a> Éditer : <?= htmlspecialchars($editTitle) ?></h1>
<?php if ($editType === 'about_page'): ?>
<!-- ── Markdown content ──────────────────────────────────────────────── -->
<h2>Contenu de la page</h2>
<form action="/admin/actions/page.php" method="post" class="admin-form"
hx-post="/admin/actions/page.php"
hx-trigger="overtype:change delay:1500ms"
hx-swap="none"
hx-on::after-request="handleAutosaveResponse(event)">
<form action="/admin/actions/page.php" method="post" class="admin-form admin-form--full-editor">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
<input type="hidden" name="slug" value="about">
<label for="editor">Contenu (Markdown) :</label>
<button type="button" class="btn btn--sm"
hx-get="/admin/markdown-cheatsheet-fragment.php"
hx-target="#md-cheatsheet-container"
hx-swap="innerHTML"
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
Aide Markdown
</button>
<div class="full-editor-toolbar">
<span class="full-editor-label">Contenu (Markdown) :</span>
<button type="button" class="btn btn--sm"
hx-get="/admin/markdown-cheatsheet-fragment.php"
hx-target="#md-cheatsheet-container"
hx-swap="innerHTML"
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
Aide Markdown
</button>
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
</div>
<input type="hidden" id="content" name="content"
value="<?= htmlspecialchars($initialContent) ?>">
<div id="editor"></div>
<div class="autosave-status" data-autosave-status></div>
</form>
<!-- ── Contacts ──────────────────────────────────────────────────────── -->
@@ -137,50 +135,46 @@
</script>
<?php elseif ($editType === 'page' && $pageSlug !== 'about'): ?>
<form action="/admin/actions/page.php" method="post" class="admin-form"
hx-post="/admin/actions/page.php"
hx-trigger="overtype:change delay:1500ms"
hx-swap="none"
hx-on::after-request="handleAutosaveResponse(event)">
<form action="/admin/actions/page.php" method="post" class="admin-form admin-form--full-editor">
<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>
<button type="button" class="btn btn--sm"
hx-get="/admin/markdown-cheatsheet-fragment.php"
hx-target="#md-cheatsheet-container"
hx-swap="innerHTML"
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
Aide Markdown
</button>
<div class="full-editor-toolbar">
<span class="full-editor-label">Contenu (Markdown) :</span>
<button type="button" class="btn btn--sm"
hx-get="/admin/markdown-cheatsheet-fragment.php"
hx-target="#md-cheatsheet-container"
hx-swap="innerHTML"
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
Aide Markdown
</button>
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
</div>
<input type="hidden" id="content" name="content"
value="<?= htmlspecialchars($initialContent) ?>">
<div id="editor"></div>
<div class="autosave-status" data-autosave-status></div>
</form>
<?php elseif ($editType === 'form_help'): ?>
<p class="param-note">Ce texte est affiché dans le formulaire de soumission des étudiant·es (lien de partage). Supporte le Markdown.</p>
<form action="/admin/actions/form-help.php" method="post" class="admin-form"
hx-post="/admin/actions/form-help.php"
hx-trigger="overtype:change delay:1500ms"
hx-swap="none"
hx-on::after-request="handleAutosaveResponse(event)">
<form action="/admin/actions/form-help.php" method="post" class="admin-form admin-form--full-editor">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="form_help_key" value="<?= htmlspecialchars($formHelpKey) ?>">
<label for="editor">Contenu (Markdown) :</label>
<button type="button" class="btn btn--sm"
hx-get="/admin/markdown-cheatsheet-fragment.php"
hx-target="#md-cheatsheet-container"
hx-swap="innerHTML"
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
Aide Markdown
</button>
<div class="full-editor-toolbar">
<span class="full-editor-label">Contenu (Markdown) :</span>
<button type="button" class="btn btn--sm"
hx-get="/admin/markdown-cheatsheet-fragment.php"
hx-target="#md-cheatsheet-container"
hx-swap="innerHTML"
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
Aide Markdown
</button>
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
</div>
<input type="hidden" id="content" name="content"
value="<?= htmlspecialchars($initialContent) ?>">
<div id="editor"></div>
<div class="autosave-status" data-autosave-status></div>
</form>
<?php else: ?>

View File

@@ -69,14 +69,6 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
<option value="<?= $y ?>" <?= $yearFilter == $y ? 'selected' : '' ?>><?= $y ?></option>
<?php endforeach; ?>
</select>
<select name="orientation">
<option value="">Orientation</option>
<?php foreach ($orientations as $o): ?>
<option value="<?= $o['id'] ?>" <?= $orientationFilter == $o['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($o['name']) ?>
</option>
<?php endforeach; ?>
</select>
<select name="ap">
<option value="">AP</option>
<?php foreach ($apPrograms as $ap): ?>
@@ -85,6 +77,14 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
</option>
<?php endforeach; ?>
</select>
<select name="orientation">
<option value="">Orientation</option>
<?php foreach ($orientations as $o): ?>
<option value="<?= $o['id'] ?>" <?= $orientationFilter == $o['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($o['name']) ?>
</option>
<?php endforeach; ?>
</select>
<?php if ($searchQuery || $yearFilter || $orientationFilter || $apFilter): ?>
<a href="/admin/" class="btn btn--secondary btn--sm admin-filters-reset">&#x2715; Réinitialiser</a>
<?php endif; ?>

View File

@@ -360,14 +360,14 @@
<!-- Danger zone: remove credentials -->
<?php if ($hasPassword): ?>
<fieldset class="param-danger-zone">
<legend>Supprimer la configuration du mot de passe PHP</legend>
<legend>Supprimer la configuration du mot de passe</legend>
<p>
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.
ne sera plus protégé (mode développement).
</p>
<?php /* TODO: replace this browser confirm() with a proper <dialog> modal like the other confirmations */ ?>
<form method="post" action="/admin/actions/account.php"
onsubmit="return confirm('Supprimer le mot de passe PHP ? L\'accès admin ne sera protégé que par nginx Basic Auth.')">
onsubmit="return confirm('Supprimer le mot de passe ? L\'accès admin ne sera plus protégé.')">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="remove_credentials">
<input type="hidden" name="redirect" value="/admin/parametres.php">