From e21a4d81a2c340e1eb3b64e39d36d81219e54052 Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Mon, 20 Apr 2026 15:02:28 +0200 Subject: [PATCH] refine: required confirmation_email field on both student forms, StudentEmail uses it directly - Add dedicated 'confirmation_email' (type=email, required) field to student form at end of submission (partage + admin). - ThesisCreateController now validates it is present and a valid email; form is rejected if missing/invalid. - Autofocus mapping for confirmation_email errors. - StudentEmail uses confirmation_email directly (removed extractEmail hack that mined email from free-form contact field). --- TODO.md | 16 +++ app/public/admin/acces-etudiante.php | 4 + app/public/admin/add.php | 6 + app/public/assets/css/admin.css | 9 ++ app/public/partage/index.php | 11 ++ app/public/partage/thanks.php | 19 +++ .../Controllers/ThesisCreateController.php | 13 +- app/src/StudentEmail.php | 112 ++++++++++++++++++ app/storage/test.db | Bin 270336 -> 270336 bytes 9 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 app/src/StudentEmail.php diff --git a/TODO.md b/TODO.md index fa3c6b0..3d218b3 100644 --- a/TODO.md +++ b/TODO.md @@ -71,3 +71,19 @@ - [x] Update header.php action URLs - [x] Commit current state - [ ] Test all routes (/, /search, /tfe, /repertoire, /apropos, /licence, /media, /live-reload) + +# Now: Confirmation email on student form submission +- [x] Create src/StudentEmail.php β€” builds HTML recap email, extracts email from contact field, uses SmtpRelay to send +- [x] Wire StudentEmail::sendConfirmation() into partage/index.php handleShareLinkSubmission() after successful thesis creation +- [x] Pass email-sent flag via session to /partage/thanks.php +- [x] Update partage/thanks.php β€” show "email sent" notice with styled green badge when confirmation was sent + +- [x] Add "Visiter" (πŸ‘ Visit) button to student link action row in acces-etudiante.php + - [x] Add link (target _blank) to /partage/ + - [x] Add .admin-btn-visit / .admin-btn-visit:hover CSS in admin.css + +- [x] Add required confirmation_email field to both student forms (partage/index.php + admin/add.php) + - [x] New fieldset at end of form with type="email", required + - [x] ThesisCreateController validates confirmation_email is present and valid + - [x] StudentEmail uses confirmation_email directly (no more extractEmail hack) + - [x] Autofocus mapping added for confirmation_email validation errors diff --git a/app/public/admin/acces-etudiante.php b/app/public/admin/acces-etudiante.php index d26eb81..c1379b6 100644 --- a/app/public/admin/acces-etudiante.php +++ b/app/public/admin/acces-etudiante.php @@ -83,6 +83,10 @@ $bodyClass = 'admin-body';
+ + πŸ‘ Visiter +
diff --git a/app/public/assets/css/admin.css b/app/public/assets/css/admin.css index fc6f852..4840a0f 100644 --- a/app/public/assets/css/admin.css +++ b/app/public/assets/css/admin.css @@ -638,6 +638,15 @@ background: var(--blue-muted-bg-hover); } +.admin-btn-visit { + background: var(--success-muted-bg); + color: var(--success); + border-color: var(--success-muted-border); +} +.admin-btn-visit:hover { + background: var(--green-muted-bg-hover); +} + .admin-btn-edit { background: var(--yellow-muted-bg); color: var(--accent-yellow); diff --git a/app/public/partage/index.php b/app/public/partage/index.php index 9244e6d..a26b169 100644 --- a/app/public/partage/index.php +++ b/app/public/partage/index.php @@ -467,6 +467,12 @@ function renderShareLinkForm(string $slug, array $link): void + +
+ E-mail de confirmation + +
+ @@ -542,11 +548,15 @@ function handleShareLinkSubmission(string $slug): void } require_once APP_ROOT . '/src/Controllers/ThesisCreateController.php'; + require_once APP_ROOT . '/src/StudentEmail.php'; try { $ctrl = ThesisCreateController::make(); $thesisId = $ctrl->submit($_POST, $_FILES); + // Send confirmation e-mail (non-blocking; failure doesn't stop redirect) + $emailSent = StudentEmail::sendConfirmation(Database::getInstance(), $thesisId, $_POST); + // Mark the link as used $shareLinkModel = new ShareLink(Database::getInstance()); $shareLinkModel->incrementUsage($link['id']); @@ -554,6 +564,7 @@ function handleShareLinkSubmission(string $slug): void // Clean up share-specific session data unset($_SESSION[$shareCsrfKey]); unset($_SESSION['share_verified_' . $slug]); + $_SESSION['share_email_sent'] = $emailSent; // Redirect to thanks page header('Location: /partage/thanks.php?id=' . urlencode((string)$thesisId)); diff --git a/app/public/partage/thanks.php b/app/public/partage/thanks.php index 6364f07..f18636a 100644 --- a/app/public/partage/thanks.php +++ b/app/public/partage/thanks.php @@ -22,6 +22,10 @@ if (!$thesis) { die('TFE introuvable.'); } +// Was the confirmation e-mail sent? +$emailSent = !empty($_SESSION['share_email_sent']); +unset($_SESSION['share_email_sent']); + // Get the share link slug from the referer path $pathParts = explode('/', trim($_SERVER['REQUEST_URI'] ?? '', '/')); $slug = count($pathParts) >= 2 ? $pathParts[0] : null; @@ -70,12 +74,27 @@ $pageTitle = 'Merci β€” TFE enregistrΓ©'; .thanks-center .btn-add-another:hover { background: #555; } + .email-sent-notice { + display: inline-block; + padding: 0.75rem 1.5rem; + background: #e8f5e9; + border: 1px solid #a5d6a7; + border-radius: 6px; + font-size: 0.95rem; + color: #2e7d32; + margin: 0 0 1.5rem; + }

βœ… Merci !

Votre TFE a bien Γ©tΓ© enregistrΓ© sur la plateforme.

+ + +
β€”
diff --git a/app/src/Controllers/ThesisCreateController.php b/app/src/Controllers/ThesisCreateController.php index c35f93d..7ae021a 100644 --- a/app/src/Controllers/ThesisCreateController.php +++ b/app/src/Controllers/ThesisCreateController.php @@ -173,6 +173,7 @@ class ThesisCreateController if (str_contains($message, 'langue')) return 'languages'; if (str_contains($message, 'mots-clΓ©s')) return 'tag'; if (str_contains($message, 'Lien URL')) return 'lien'; + if (str_contains($message, 'e-mail de confirmation')) return 'confirmation_email'; return null; } @@ -283,8 +284,18 @@ class ThesisCreateController } } + // Confirmation e-mail (required) + $confirmationEmail = trim($post['confirmation_email'] ?? ''); + if ($confirmationEmail === '') { + throw new Exception("L'adresse e-mail de confirmation est requise."); + } + $confirmationEmail = filter_var($confirmationEmail, FILTER_VALIDATE_EMAIL); + if ($confirmationEmail === false) { + throw new Exception("L'adresse e-mail de confirmation n'est pas valide."); + } + return compact( - 'auteurName', 'mail', 'showContact', 'annee', 'orientationId', 'apProgramId', + 'auteurName', 'mail', 'showContact', 'confirmationEmail', 'annee', 'orientationId', 'apProgramId', 'finalityId', 'titre', 'subtitle', 'synopsis', 'durationInfo', 'juryMembers', 'keywords', 'languageIds', 'formatIds', 'licenseId', 'lien', 'accessTypeId' diff --git a/app/src/StudentEmail.php b/app/src/StudentEmail.php new file mode 100644 index 0000000..9011c12 --- /dev/null +++ b/app/src/StudentEmail.php @@ -0,0 +1,112 @@ + $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 .= "" + . htmlspecialchars($label) . "" + . "{$v}\n"; + } + + return << +

Merci β€” ton TFE a bien Γ©tΓ© enregistrΓ© πŸŽ‰

+

+ Voici un rΓ©capitulatif de ta soumission. Tu n'as pas besoin de rΓ©pondre Γ  cet e-mail. +

+ + {$rows} +
+

+ Plateforme xamxam Β· erg Bruxelles +

+
+ 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); + + $result = SmtpRelay::send($db, $to, $subject, $htmlBody); + + 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; + } +} diff --git a/app/storage/test.db b/app/storage/test.db index 6215a6b01b8d4977fa52ea247ccec3afa421eed5..bea038c1cf9aa8c1fc77c5069886074d6b094d50 100644 GIT binary patch delta 234 zcmZoTAkc6?U;~SS1Q&l01OGezyZk5lck!>_pT^(JU%{WmAGBFez>HsjkBeEEvp6HM zC^bGOGcUWiIZ3}gNuLpjnYJhCGcPJ&W8}Zcz<&{_`Z&L(E+Z#{J_9EQr!j+(fsvVk ziIIV>tCxqTsgHXgNI=)XMAyhb!O+;s*u=`fgo}ZJfsy|;1OIEF_J{oPBFy@n82U(1 Z^oN1}574Cd{Omx3nVC5`mpx#1007!TIGF$d delta 66 zcmZoTAkc6?U;~SS7$<)c1OGezyZk5lck!>_pT^(JU%{WWSx~@(zd1?2JxQMth?%x0 W=`$}X*v#_akN%<$0?QsSI{*OQPZx0j