diff --git a/src/Module/MailCatcher.php b/src/Module/MailCatcher.php index eaf612d..d18d7d2 100644 --- a/src/Module/MailCatcher.php +++ b/src/Module/MailCatcher.php @@ -610,7 +610,12 @@ protected function grabUrlsFromEmail(Email $email): array preg_match_all($regex, $message->getTextContent() ?? '', $textMatches); preg_match_all($regex, $message->getHtmlContent() ?? '', $htmlMatches); - return array_merge($textMatches[0], $htmlMatches[0]); + $htmlUrls = array_map( + static fn (string $url): string => html_entity_decode($url, ENT_QUOTES | ENT_HTML5), + $htmlMatches[0] + ); + + return array_merge($textMatches[0], $htmlUrls); } /** diff --git a/tests/acceptance/MailcatcherCest.php b/tests/acceptance/MailcatcherCest.php index aec5cf9..36c1a01 100644 --- a/tests/acceptance/MailcatcherCest.php +++ b/tests/acceptance/MailcatcherCest.php @@ -165,6 +165,14 @@ public function test_grab_urls_from_html_email( $I->assertEquals($url, $urls[0]); } + public function test_grab_urls_from_html_email_with_multiple_query_parameters(AcceptanceTester $I) + { + $I->sendEmail("user@example.com", 'Html email with urls', "
Confirm", true); + $urls = $I->grabUrlsFromLastEmail(); + + $I->assertEquals(['https://example.com/confirm?id=1&token=abc'], $urls); + } + /** * @param AcceptanceTester $I * @param \Codeception\Example $example diff --git a/tests/unit/Module/MailCatcherTest.php b/tests/unit/Module/MailCatcherTest.php index 8655f02..e7f06bc 100644 --- a/tests/unit/Module/MailCatcherTest.php +++ b/tests/unit/Module/MailCatcherTest.php @@ -482,6 +482,39 @@ public function testSeeInLastEmailRecipientFail() $mailcatcher->seeInLastEmailRecipient('userB@example.com'); } + public function testGrabUrlsFromSinglePartEmailsRaisesNoDeprecation() + { + $mailcatcher = new MailCatcherTest_TestClass(); + $sourceDirectory = dirname(__DIR__, 3) . '/src/'; + set_error_handler(static function (int $severity, string $message, string $file, int $line) use ($sourceDirectory): bool { + if (!str_starts_with($file, $sourceDirectory)) { + return false; + } + throw new \ErrorException($message, 0, $severity, $file, $line); + }); + + try { + $mailcatcher->setLastMessage(new Email(1, [], '', $this->mimeSource('text/plain', 'Visit https://example.com/a'))); + $this->assertEquals(['https://example.com/a'], $mailcatcher->grabUrlsFromLastEmail()); + + $mailcatcher->setLastMessage(new Email(1, [], '', $this->mimeSource('text/html', 'B'))); + $this->assertEquals(['https://example.com/b'], $mailcatcher->grabUrlsFromLastEmail()); + } finally { + restore_error_handler(); + } + } + + public function testGrabUrlsFromLastEmailDecodesHtmlEntities() + { + $mailcatcher = new MailCatcherTest_TestClass(); + $mailcatcher->setLastMessage(new Email(1, [], '', $this->mimeSource( + 'text/html', + 'Confirm' + ))); + + $this->assertEquals(['https://example.com/confirm?id=1&token=abc'], $mailcatcher->grabUrlsFromLastEmail()); + } + private function mailcatcherWithMessages(array $messages, Email ...$emails): MailCatcherTest_TestClass { $mailcatcher = new MailCatcherTest_TestClass(); @@ -506,6 +539,17 @@ private function message( 'recipients' => $recipients, ]; } + + private function mimeSource(string $contentType, string $body): string + { + return "From: sender@example.com\r\n" + . "To: user@example.com\r\n" + . "Subject: Test\r\n" + . "MIME-Version: 1.0\r\n" + . "Content-Type: {$contentType}; charset=UTF-8\r\n" + . "\r\n" + . $body; + } } class MailCatcherTest_TestClass extends MailCatcher