Files
xamxam/app/src/StudentEmail.php
Pontoporeia a2cba6d3c0 feat: prevent duplicate TFE submissions with logging and user feedback
- Add DuplicateThesisException (typed, carries existing thesis metadata)
- Add Database::findDuplicateThesis(): matches on year + author + normalised
  title (exact, prefix, Levenshtein ≤10% of longer string)
- ThesisCreateController::submit() runs duplicate check before any DB write
  and throws DuplicateThesisException on match
- AppLogger::logDuplicate() writes status=duplicate entries to the JSON-lines
  log for audit purposes
- App::flash/consumeFlash extended to support 'warning' flash type
- admin/actions/formulaire.php: catches DuplicateThesisException, logs it,
  flashes an HTML warning toast with a clickable link to the existing thesis,
  and repopulates the form fields
- partage/index.php: same catch block; surfaces a plain-text flash-warning
  banner on the student form with identifier, title, and year of the match;
  form is repopulated via session
- toast.php: renders toast--warning variant
- admin.css: .toast--warning + link colour rules
- form.css: .flash-warning style for the partage form
2026-05-05 11:04:52 +02:00

119 lines
5.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* StudentEmail — builds and sends confirmation e-mails to students
* after a successful TFE submission via a share-link form.
*
* The e-mail is addressed to the confirmation e-mail field provided
* by the student and contains a recap of every field submitted.
*/
class StudentEmail
{
/**
* Build the HTML body for the confirmation e-mail.
*
* @param array $thesis Thesis row (from getThesis / v_theses_full)
* @return string HTML body
*/
private static function buildHtml(array $thesis): string
{
$rows = '';
$fields = [
'Identifiant' => $thesis['identifier'] ?? '',
'Titre' => $thesis['title'] ?? '',
'Sous-titre' => $thesis['subtitle'] ?? '',
'Auteur·ice(s)' => $thesis['authors'] ?? '',
'Année' => $thesis['year'] ?? '',
'Orientation' => $thesis['orientation'] ?? '',
'Atelier pluridisciplinaire' => $thesis['ap_program'] ?? '',
'Finalité' => $thesis['finality_type'] ?? '',
'Synopsis' => $thesis['synopsis'] ?? '',
'Langue(s)' => $thesis['languages'] ?? '',
'Format(s)' => $thesis['formats'] ?? '',
'Mots-clés' => $thesis['keywords'] ?? '',
'Promoteur·ice(s)' => $thesis['jury_promoteurs'] ?? '',
'Président·e du jury' => $thesis['jury_president'] ?? '',
'Lecteurs·rices' => $thesis['jury_lecteurs'] ?? '',
'Durée / Taille' => $thesis['file_size_info'] ?? '',
'Lien' => $thesis['baiu_link'] ?? '',
'Type d\'accès' => $thesis['access_type'] ?? '',
'Licence' => $thesis['license_type'] ?? '',
];
foreach ($fields as $label => $value) {
$v = $value === '' ? '' : htmlspecialchars((string)$value);
$rows .= "<tr><th style='text-align:left;padding:6px 10px;border-bottom:1px solid #eee'>"
. htmlspecialchars($label) . '</th>'
. "<td style='padding:6px 10px;border-bottom:1px solid #eee'>{$v}</td></tr>\n";
}
return <<<HTML
<div style="font-family:system-ui,sans-serif;max-width:600px;margin:0 auto;color:#333">
<h1 style="font-size:1.4rem;color:#222">Merci — ton TFE a bien été enregistré 🎉</h1>
<p style="color:#555;font-size:0.95rem">
Voici un récapitulatif de ta soumission. Tu n'as pas besoin de répondre à cet e-mail.
</p>
<table style="width:100%;border-collapse:collapse;margin-top:1.5rem">
{$rows}
</table>
<p style="margin-top:2rem;font-size:0.85rem;color:#999">
Plateforme xamxam · erg Bruxelles
</p>
</div>
HTML;
}
/**
* Send a confirmation e-mail for a given thesis.
*
* @param Database $db
* @param int $thesisId
* @param array $postData Raw $_POST (must contain 'confirmation_email')
* @return bool True when sent, false otherwise
*/
public static function sendConfirmation(
Database $db,
int $thesisId,
array $postData
): bool {
// ── 1. Read e-mail from confirmation field ─────────────────────────
$to = trim($postData['confirmation_email'] ?? '');
if ($to === '' || filter_var($to, FILTER_VALIDATE_EMAIL) === false) {
error_log('[StudentEmail] No valid confirmation e-mail — skipping thesis #' . $thesisId);
return false;
}
// ── 2. Check SMTP config ────────────────────────────────────────────
if (!SmtpRelay::isConfigured($db)) {
error_log('[StudentEmail] SMTP not configured — skipping thesis #' . $thesisId);
return false;
}
// ── 3. Fetch thesis data ────────────────────────────────────────────
$thesis = $db->getThesis($thesisId);
if (!$thesis) {
error_log('[StudentEmail] Thesis #' . $thesisId . ' not found after creation');
return false;
}
// ── 4. Send ─────────────────────────────────────────────────────────
$subject = 'Merci — ton TFE a bien été enregistré';
$htmlBody = self::buildHtml($thesis);
try {
$result = SmtpRelay::send($db, $to, $subject, $htmlBody);
} catch (SmtpSendException $e) {
error_log("[StudentEmail] SMTP error sending to {$to} for thesis #{$thesisId}: " . $e->getMessage());
throw $e; // re-throw so callers can react (e.g. redirect to retry page)
}
if ($result) {
error_log("[StudentEmail] Confirmation sent to {$to} for thesis #{$thesisId}");
} else {
error_log("[StudentEmail] Failed to send to {$to} for thesis #{$thesisId}");
}
return $result;
}
}