fix: duplicate warning not shown in admin, double-encoded in partage, no focus

- toast-fragment.php: 204 early-exit now also checks flash['warning'];
  previously the warning was consumed by consumeFlash() then silently dropped
- partage/index.php: store warning as plain text; htmlspecialchars() applied
  once at render time — previously htmlspecialchars() was called inside the
  stored string then again at output, producing ' entities etc.
- partage/index.php: flash-warning div gets id + tabindex=-1; inline JS
  scrolls it into view and focuses it on DOMContentLoaded
- admin/footer.php: htmx:afterSettle listener focuses .toast--warning after
  HTMX injects the toast fragment into #toast-region
This commit is contained in:
Pontoporeia
2026-05-04 17:04:09 +02:00
parent a2cba6d3c0
commit 5f24dcae7e
8 changed files with 42 additions and 19 deletions

View File

@@ -1,11 +1,11 @@
<?php
/**
* Partage Entry point for shared student submission forms.
* Partage - Entry point for shared student submission forms.
*
* Routes:
* /partage/<slug> Render the share-link form (or password gate)
* /partage/<slug>/submit POST endpoint for form submissions via share link
* /partage/recapitulatif.php?id=N Post-submission confirmation page
* /partage/<slug> - Render the share-link form (or password gate)
* /partage/<slug>/submit - POST endpoint for form submissions via share link
* /partage/recapitulatif.php?id=N - Post-submission confirmation page
*/
require_once __DIR__ . '/../../bootstrap.php';
@@ -83,7 +83,7 @@ if (!$validationResult['valid']) {
exit;
}
// Link is valid render the form
// Link is valid - render the form
$link = $validationResult['link'];
renderShareLinkForm($slug, $link);
@@ -222,7 +222,7 @@ function renderShareLinkForm(string $slug, array $link): void
// Build old()-compatible callable from $formData (share forms use the array variant).
$shareOldFn = fn(string $key, string $default = '') => old($formData, $key, $default);
// No autofocus in the share form identity function.
// No autofocus in the share form - identity function.
$shareWithAutofocusFn = fn(string $field, array $attrs = []) => $attrs;
// Load all form help blocks in one query.
@@ -267,7 +267,8 @@ function renderShareLinkForm(string $slug, array $link): void
<div class="flash-error" role="alert"><?= htmlspecialchars($flashError) ?></div>
<?php endif; ?>
<?php if ($flashWarning): ?>
<div class="flash-warning" role="alert"><?= htmlspecialchars($flashWarning) ?></div>
<div class="flash-warning" id="flash-warning" role="alert" tabindex="-1"><?= htmlspecialchars($flashWarning) ?></div>
<script>document.addEventListener('DOMContentLoaded',function(){var el=document.getElementById('flash-warning');if(el){el.scrollIntoView({behavior:'smooth',block:'center'});el.focus();}});</script>
<?php endif; ?>
<?php if ($flashSuccess): ?>
<div class="flash-success" role="alert"><?= htmlspecialchars($flashSuccess) ?></div>
@@ -460,7 +461,7 @@ function handleShareLinkSubmission(string $slug): void
unset($_SESSION[$shareCsrfKey]);
unset($_SESSION['share_verified_' . $slug]);
// Send confirmation e-mail on delivery failure, redirect to retry page
// Send confirmation e-mail - on delivery failure, redirect to retry page
$emailError = null;
try {
$emailSent = StudentEmail::sendConfirmation(Database::getInstance(), $thesisId, $_POST);
@@ -472,7 +473,7 @@ function handleShareLinkSubmission(string $slug): void
header('Location: /partage/retry-email?id=' . urlencode((string)$thesisId));
exit();
}
// Non-recipient errors (relay down, etc.) skip email silently
// Non-recipient errors (relay down, etc.) - skip email silently
$_SESSION['share_email_sent'] = false;
}
@@ -488,9 +489,10 @@ function handleShareLinkSubmission(string $slug): void
error_log('Share link duplicate submission: ' . $e->getMessage());
// Repopulate the form and surface a clear warning to the student.
$_SESSION['_flash_warning'] = 'Votre soumission ressemble à un TFE déjà enregistré ('
. htmlspecialchars($e->existingIdentifier . ' — ' . $e->existingTitle . ', ' . $e->existingYear)
. '). Si vous pensez quil sagit dune erreur, veuillez contacter léquipe.';
// Store as plain text — htmlspecialchars() is applied at render time.
$_SESSION['_flash_warning'] = 'Votre soumission ressemble à un TFE déjà enregistré.'
. "\n" . $e->existingIdentifier . ' — ' . $e->existingTitle . ' (' . $e->existingYear . ')'
. "\nSi vous pensez qu'il s'agit d'une erreur, veuillez contacter l'équipe.";
$_SESSION['form_data_share_' . $slug] = $_POST;
$_SESSION[$shareCsrfKey] = bin2hex(random_bytes(32)); // Regenerate token