diff --git a/docs/book/message/attachments.md b/docs/book/message/attachments.md index d18225de..ca2cf716 100644 --- a/docs/book/message/attachments.md +++ b/docs/book/message/attachments.md @@ -147,5 +147,22 @@ use Laminas\Mime\Mime; $mimeMessage->setMime(new Mime($customBoundary)); ``` +## Retrieving attachments + +If you have created a multipart message with one or more attachments, whether programmatically +or via the `Message::fromString();` method, you can readily retrieve them by calling the `getAttachments()` method. +It will return an array of `\Laminas\Mime\Part` objects. + +For example: + +```php +// Instantiate a Message object from a .eml file. +$raw = file_get_contents(__DIR__ . '/mail_with_attachments.eml'); +$message = Message::fromString($raw); + +// Retrieve the email's attachments. +$attachments = $message->getAttachments(); +``` + [mime-boundary]: https://www.oreilly.com/library/view/programming-internet-email/9780596802585/ch03s04.html [multipart-content-type]: https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html \ No newline at end of file diff --git a/docs/book/message/intro.md b/docs/book/message/intro.md index e3fb72e6..515e7fa8 100644 --- a/docs/book/message/intro.md +++ b/docs/book/message/intro.md @@ -207,6 +207,21 @@ EOF; $message = Message::fromString($rawEmail); ``` +### Retrieve a Message's plain text and HTML body + +Commonly, though not always, an email will contain one or both of a plain text and/or HTMl body. +To retrieve these directly, there are two methods available `getPlainTextBodyPart()` and +`getHtmlBodyPart()`. For example: + +```php +// Instantiate a Message object from a .eml file. +$raw = file_get_contents(__DIR__ . '/mail_with_attachments.eml'); +$message = Message::fromString($raw); + +echo $message->getPlainTextBodyPart(); +echo $message->getHtmlBodyPart(); +``` + ## Configuration Options The `Message` class has no configuration options, and is instead a value object. diff --git a/src/Message.php b/src/Message.php index 41f4c813..c6922cda 100644 --- a/src/Message.php +++ b/src/Message.php @@ -13,16 +13,23 @@ use Laminas\Mail\Header\ReplyTo; use Laminas\Mail\Header\Sender; use Laminas\Mail\Header\To; +use Laminas\Mail\Iterator\AttachmentPartFilterIterator; +use Laminas\Mail\Iterator\MessagePartFilterIterator; +use Laminas\Mail\Iterator\PartsIterator; use Laminas\Mime; +use Laminas\Mime\Part; +use RecursiveIteratorIterator; use Traversable; use function array_filter; +use function array_pop; use function count; use function date; use function gettype; use function is_array; use function is_object; use function is_string; +use function iterator_to_array; use function method_exists; use function sprintf; use function str_starts_with; @@ -118,6 +125,46 @@ public function getHeaders() return $this->headers; } + public function getBodyPart(string $partType): Part + { + /** @var Part[] $iterator */ + $iterator = new RecursiveIteratorIterator( + new MessagePartFilterIterator( + new PartsIterator($this->getBody()->getParts()), + $partType + ) + ); + + $part = iterator_to_array($iterator); + return array_pop($part); + } + + public function getPlainTextBodyPart(): Part + { + return $this->getBodyPart(\Laminas\Mime\Mime::TYPE_TEXT); + } + + public function getHtmlBodyPart(): Part + { + return $this->getBodyPart(\Laminas\Mime\Mime::TYPE_HTML); + } + + /** + * @return Part[] + */ + public function getAttachments(): array + { + /** @var Part[] $iterator */ + $iterator = new RecursiveIteratorIterator( + new AttachmentPartFilterIterator( + new PartsIterator( + $this->getBody()->getParts() + ), + ) + ); + return iterator_to_array($iterator); + } + /** * Set (overwrite) From addresses * diff --git a/test/MessageTest.php b/test/MessageTest.php index 69f83749..b760b29f 100644 --- a/test/MessageTest.php +++ b/test/MessageTest.php @@ -12,11 +12,13 @@ use Laminas\Mail\Message; use Laminas\Mime\Message as MimeMessage; use Laminas\Mime\Mime; +use Laminas\Mime\Part; use Laminas\Mime\Part as MimePart; use PHPUnit\Framework\TestCase; use Smalot\PdfParser\Parser; use stdClass; +use function array_pop; use function count; use function date; use function fclose; @@ -25,6 +27,7 @@ use function fwrite; use function implode; use function sprintf; +use function str_starts_with; use function substr; use function sys_get_temp_dir; use function trim; @@ -888,12 +891,45 @@ public function testCanParseMultipartEmail(): void '
This is a test email with 1 attachment.
', trim($partOne->getParts()[1]->getRawContent()) ); + } + + public function testCanReturnPlainTextAndHTMLMessageBodyIfAvailable() + { + $raw = file_get_contents(__DIR__ . '/_files/mail_with_pdf_attachment.eml'); + $message = Message::fromString($raw); + $this->assertInstanceOf(Message::class, $message); + $this->assertTrue($message->getBody()->isMultiPart()); + $plainTextBody = $message->getPlainTextBodyPart(); + $this->assertSame("This is a test email with 1 attachment.", trim($plainTextBody->getRawContent())); + $htmlBody = $message->getHtmlBodyPart(); + $this->assertSame("
This is a test email with 1 attachment.
", trim($htmlBody->getRawContent())); + } + + public function testReturnsEmptyAttachmentsListWhenEmailHasNoAttachments() + { + $raw = file_get_contents(__DIR__ . '/_files/laminas-mail-19.eml'); + $message = Message::fromString($raw); + $this->assertInstanceOf(Message::class, $message); + $this->assertTrue($message->getBody()->isMultiPart()); + $this->assertEmpty($message->getAttachments()); + } + + public function testCanRetrieveMessageAttachmentsWhenAttachmentsAreAvailable() + { + $raw = file_get_contents(__DIR__ . '/_files/mail_with_pdf_attachment.eml'); + $message = Message::fromString($raw); + $this->assertInstanceOf(Message::class, $message); + $this->assertTrue($message->getBody()->isMultiPart()); - $attachmentPart = $parts[1]; + $attachments = $message->getAttachments(); + $this->assertCount(1, $attachments); + /** @var Part $attachment */ + $attachment = array_pop($attachments); + $this->assertTrue(str_starts_with($attachment->getType(), "application/pdf")); $tempFile = sprintf("%stemp.pdf", sys_get_temp_dir()); $handle = fopen($tempFile, "w"); - fwrite($handle, $attachmentPart->getRawContent()); + fwrite($handle, $attachment->getRawContent()); fclose($handle); $parser = new Parser(); diff --git a/test/_files/mail_with_pdf_attachment.eml b/test/_files/mail_with_pdf_attachment.eml index 245cca2f..89e3ed7a 100644 --- a/test/_files/mail_with_pdf_attachment.eml +++ b/test/_files/mail_with_pdf_attachment.eml @@ -31,7 +31,7 @@ Content-Transfer-Encoding: quoted-printable --001a11447dc881e40b0537fe6d58-- --001a11447dc881e40f0537fe6d5a -Content-Disposition: inline; +Content-Disposition: attachment; filename="test document.pdf" Content-Type: application/pdf; x-mac-hide-extension=yes;