diff --git a/src/Service/Mailer.php b/src/Service/Mailer.php index ce9fbcba6d..7c6818b5bd 100644 --- a/src/Service/Mailer.php +++ b/src/Service/Mailer.php @@ -97,10 +97,11 @@ public function sendCommentReportedFeedbackEmail(Member $member, $parameters): b $feedbackCategory = $feedbackCategoryRepository->findOneBy(['name' => 'Comment_issue']); return $this->sendTemplateEmail( - new Address($member->getEmail()), + new Address(self::NO_REPLY_EMAIL_ADDRESS, 'BeWelcome'), new Address($feedbackCategory->getEmailToNotify(), 'Comment Issue'), 'comment.feedback', - $parameters + $parameters, + $member->getEmail(), ); } @@ -147,7 +148,8 @@ public function sendNotificationEmail(Address $sender, Member $receiver, $parame } /** - * This feeds the feedback given by a user into the OTRS queues. + * Sends contact/feedback form submissions to the helpdesk queue. + * From: is always noreply@bewelcome.org to pass DMARC; the reporter's address goes in Reply-To. */ public function sendFeedbackEmail($sender, Address $receiver, $parameters): bool { @@ -155,10 +157,11 @@ public function sendFeedbackEmail($sender, Address $receiver, $parameters): bool . str_replace('_', ' ', ($parameters['IdCategory'])->getName()) . "'"; return $this->sendTemplateEmail( - $sender, + self::NO_REPLY_EMAIL_ADDRESS, $receiver, 'feedback', - $parameters + $parameters, + \is_string($sender) ? $sender : null, ); } @@ -298,7 +301,7 @@ private function getBeWelcomeAddress(Member $sender, $email): Address * * @return bool */ - private function sendTemplateEmail($sender, $receiver, string $template, array $parameters): bool + private function sendTemplateEmail($sender, $receiver, string $template, array $parameters, ?string $replyTo = null): bool { $currentLocale = $this->translator->getLocale(); $success = true; @@ -337,6 +340,10 @@ private function sendTemplateEmail($sender, $receiver, string $template, array $ } $email->from($sender); + if (null !== $replyTo) { + $email->replyTo(new Address($replyTo)); + } + try { $this->mailer->send($email); } catch (TransportExceptionInterface $e) { diff --git a/templates/emails/feedback.html.twig b/templates/emails/feedback.html.twig index 63c4086ef4..85ba9c55af 100644 --- a/templates/emails/feedback.html.twig +++ b/templates/emails/feedback.html.twig @@ -1,5 +1,8 @@ {% if member is not null %} - Member {{ member.username }} has sent some feedback.
+ Member {{ member.username }} ({{ FeedbackEmail }}) has sent some feedback.
+
+{% elseif FeedbackEmail is not null %} + From: {{ FeedbackEmail }}

{% endif %} {{ FeedbackQuestion|raw }} diff --git a/tests/Service/MailerTest.php b/tests/Service/MailerTest.php new file mode 100644 index 0000000000..8f6561a2e4 --- /dev/null +++ b/tests/Service/MailerTest.php @@ -0,0 +1,162 @@ +createStub(TranslatorInterface::class); + $translator->method('getLocale')->willReturn('en'); + $translator->method('trans')->willReturnArgument(0); + + $this->innerMailer = $this->createMock(MailerInterface::class); + + $this->mailer = new Mailer( + $this->createStub(EntityManagerInterface::class), + $this->createStub(UrlGeneratorInterface::class), + $translator, + $this->innerMailer, + $this->createStub(Logger::class), + ); + } + + public function testSendFeedbackEmailUsesNoreplyAsFrom(): void + { + $captured = null; + $this->innerMailer + ->expects($this->once()) + ->method('send') + ->with($this->callback(static function (Email $email) use (&$captured) { + $captured = $email; + + return true; + })); + + $category = $this->createStub(FeedbackCategory::class); + $category->method('getName')->willReturn('Safety_Concern'); + + $this->mailer->sendFeedbackEmail( + 'reporter@yahoo.fr', + new Address('abuse@bewelcome.org'), + [ + 'IdCategory' => $category, + 'FeedbackQuestion' => 'Test feedback', + 'member' => null, + 'no_reply_needed' => false, + 'browser' => 'Firefox', + 'host' => 'bewelcome.org', + 'version' => '1.0', + ] + ); + + $this->assertNotNull($captured); + + $from = $captured->getFrom(); + $this->assertCount(1, $from); + $this->assertSame('noreply@bewelcome.org', $from[0]->getAddress(), 'From must be noreply to pass DMARC'); + + $replyTo = $captured->getReplyTo(); + $this->assertCount(1, $replyTo); + $this->assertSame('reporter@yahoo.fr', $replyTo[0]->getAddress(), 'Reply-To must be the reporter so staff can reply'); + } + + public function testSendFeedbackEmailNullFallbackUsesNoreplyWithNoReplyTo(): void + { + $captured = null; + $this->innerMailer + ->expects($this->once()) + ->method('send') + ->with($this->callback(static function (Email $email) use (&$captured) { + $captured = $email; + + return true; + })); + + $category = $this->createStub(FeedbackCategory::class); + $category->method('getName')->willReturn('General'); + + // AboutModel falls back to 'feedback@bewelcome.org' when FeedbackEmail is null + $this->mailer->sendFeedbackEmail( + 'feedback@bewelcome.org', + new Address('feedback@bewelcome.org'), + [ + 'IdCategory' => $category, + 'FeedbackQuestion' => 'Test feedback', + 'member' => null, + 'no_reply_needed' => true, + 'browser' => 'Chrome', + 'host' => 'bewelcome.org', + 'version' => '1.0', + ] + ); + + $from = $captured->getFrom(); + $this->assertSame('noreply@bewelcome.org', $from[0]->getAddress()); + } + + public function testSendCommentReportedFeedbackEmailUsesNoreplyAsFrom(): void + { + $captured = null; + $this->innerMailer + ->expects($this->once()) + ->method('send') + ->with($this->callback(static function (Email $email) use (&$captured) { + $captured = $email; + + return true; + })); + + $member = $this->createStub(Member::class); + $member->method('getEmail')->willReturn('member@gmail.com'); + + $category = $this->createStub(FeedbackCategory::class); + $category->method('getEmailToNotify')->willReturn('account@bewelcome.org'); + + $repo = $this->createStub(EntityRepository::class); + $repo->method('findOneBy')->willReturn($category); + + $em = $this->createStub(EntityManagerInterface::class); + $em->method('getRepository')->willReturn($repo); + + $translator = $this->createStub(TranslatorInterface::class); + $translator->method('getLocale')->willReturn('en'); + $translator->method('trans')->willReturnArgument(0); + + $mailer = new Mailer( + $em, + $this->createStub(UrlGeneratorInterface::class), + $translator, + $this->innerMailer, + $this->createStub(Logger::class), + ); + + $mailer->sendCommentReportedFeedbackEmail($member, ['subject' => 'Comment feedback']); + + $this->assertNotNull($captured); + + $from = $captured->getFrom(); + $this->assertCount(1, $from); + $this->assertSame('noreply@bewelcome.org', $from[0]->getAddress(), 'From must be noreply to pass DMARC'); + + $replyTo = $captured->getReplyTo(); + $this->assertCount(1, $replyTo); + $this->assertSame('member@gmail.com', $replyTo[0]->getAddress(), 'Reply-To must be the member so staff can reply'); + } +}