From 78efa4311437219778d79a49b8d19425eaf037b5 Mon Sep 17 00:00:00 2001 From: Ronan Lenouvel Date: Sun, 11 Oct 2026 19:57:01 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat(AnnouncementMail):=20gabar?= =?UTF-8?q?it=20d'annonce=20de=20l'appli=20#B27=20avec=20boutons=20d'activ?= =?UTF-8?q?ation=20et=20de=20connexion=20(#444)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/mail/announcement.html.php | 33 +++++++++++++++++++++++++++ templates/mail/announcement.txt.php | 25 ++++++++++++++++++++ tests/Mail/MailRendererTest.php | 34 ++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 templates/mail/announcement.html.php create mode 100644 templates/mail/announcement.txt.php diff --git a/templates/mail/announcement.html.php b/templates/mail/announcement.html.php new file mode 100644 index 00000000..420ec157 --- /dev/null +++ b/templates/mail/announcement.html.php @@ -0,0 +1,33 @@ + +

L'appli #B27 est ouverte !

+

Bonjour,

+

L'appli #B27 est maintenant disponible pour tous les groupes du local. Son but : voir à l'avance si le local est libre, sans avoir à demander à tout le monde.

+ +

Se connecter

+

Première fois ? Votre compte existe déjà : pour l'activer, passez par « Mot de passe oublié » avec votre adresse e-mail et choisissez votre mot de passe.

+ + + + +
+ Activer mon compte +
+

Compte déjà configuré ? Rien à refaire, connectez-vous comme d'habitude.

+ + + + +
+ Me connecter +
+

À quoi ça sert

+

1. Voir le planning. Les créneaux du local apparaissent dans l'appli : vous voyez d'un coup d'œil ce qui est pris et ce qui est libre.

+

2. Réserver un créneau libre. Un créneau disponible se réserve directement depuis l'appli, et votre réservation y apparaît aussitôt.

+

3. Demander un échange de créneau. Un créneau vous intéresse mais il est pris ? Contactez le groupe concerné depuis l'appli : la demande d'échange lui apparaît dans l'appli.

+

4. Écrire aux autres membres. La messagerie permet d'échanger entre membres directement dans l'appli.

+ diff --git a/templates/mail/announcement.txt.php b/templates/mail/announcement.txt.php new file mode 100644 index 00000000..6ba42d53 --- /dev/null +++ b/templates/mail/announcement.txt.php @@ -0,0 +1,25 @@ + +Bonjour, + +L'appli #B27 est ouverte ! + +L'appli #B27 est maintenant disponible pour tous les groupes du local. Son but : voir à l'avance si le local est libre, sans avoir à demander à tout le monde. + +SE CONNECTER +- Première fois ? Votre compte existe déjà : pour l'activer, passez par « Mot de passe oublié » avec votre adresse e-mail et choisissez votre mot de passe. Activer mon compte : + + +- Compte déjà configuré ? Rien à refaire, connectez-vous comme d'habitude. Me connecter : + + + +À QUOI ÇA SERT +1. Voir le planning. Les créneaux du local apparaissent dans l'appli : vous voyez d'un coup d'œil ce qui est pris et ce qui est libre. +2. Réserver un créneau libre. Un créneau disponible se réserve directement depuis l'appli, et votre réservation y apparaît aussitôt. +3. Demander un échange de créneau. Un créneau vous intéresse mais il est pris ? Contactez le groupe concerné depuis l'appli : la demande d'échange lui apparaît dans l'appli. +4. Écrire aux autres membres. La messagerie permet d'échanger entre membres directement dans l'appli. diff --git a/tests/Mail/MailRendererTest.php b/tests/Mail/MailRendererTest.php index d94a9eca..8770ee9f 100644 --- a/tests/Mail/MailRendererTest.php +++ b/tests/Mail/MailRendererTest.php @@ -34,6 +34,7 @@ public function testEveryMailIsAFullHtmlDocumentWithTheBrandAndNoExternalResourc ['mention-reminder', ['mentionerName' => 'Alice', 'link' => self::LINK]], ['booking-pending', ['groupName' => 'Alpha', 'when' => 'mercredi 7 octobre 2026', 'range' => '09:00 – 14:00', 'link' => self::LINK]], ['booking-decided', ['groupName' => 'Alpha', 'when' => 'mercredi 7 octobre 2026', 'range' => '09:00 – 14:00', 'accepted' => true, 'note' => null, 'link' => self::LINK]], + ['announcement', ['link' => self::LINK, 'forgotLink' => self::LINK]], ] as [$template, $data]) { $html = $this->renderer->render($template, $data)['html']; @@ -54,8 +55,8 @@ public function testEveryMailIsAFullHtmlDocumentWithTheBrandAndNoExternalResourc #[Test] public function testEveryMailHasABothHtmlAndPlainTextVersion(): void { - foreach (['password-reset', 'account-alert'] as $template) { - $mail = $this->renderer->render($template, ['link' => self::LINK]); + foreach (['password-reset', 'account-alert', 'announcement'] as $template) { + $mail = $this->renderer->render($template, ['link' => self::LINK, 'forgotLink' => self::LINK]); self::assertNotSame('', trim($mail['html']), $template); self::assertNotSame('', trim($mail['text']), $template); @@ -90,6 +91,35 @@ public function testPasswordResetEscapesAMaliciousLink(): void self::assertStringContainsString('<script>', $html); } + // --- Annonce de mise à disposition (#444) ----------------------------------------------- + + #[Test] + public function testAnnouncementExplainsTheAimTheTwoWaysToLogInAndTheFourMainFeaturesInBothVersions(): void + { + $mail = $this->renderer->render('announcement', ['link' => 'https://b27.example/login', 'forgotLink' => 'https://b27.example/forgot-password']); + + foreach ([$mail['html'], $mail['text']] as $body) { + self::assertStringContainsString('local est libre', $body, 'le but premier'); + self::assertStringContainsString('Mot de passe oublié', $body, 'activation du compte'); + self::assertStringContainsString('déjà', $body, 'compte déjà configuré : rien à refaire'); + self::assertStringContainsString('créneau libre', $body, 'réservation'); + self::assertStringContainsString('échange', $body, 'demande par contact du groupe'); + self::assertStringContainsString('messagerie', $body); + self::assertStringContainsString('https://b27.example/forgot-password', $body); + self::assertStringContainsString('https://b27.example/login', $body); + self::assertStringContainsString('Activer mon compte', $body); + self::assertStringContainsString('Me connecter', $body); + } + } + + #[Test] + public function testAnnouncementEscapesTheLinks(): void + { + $html = $this->renderer->render('announcement', ['link' => 'https://x.test/">', 'forgotLink' => 'https://x.test/">'])['html']; + + self::assertStringNotContainsString('