Skip to content

Commit

Permalink
Add API to get all text parts and complete text body (#560)
Browse files Browse the repository at this point in the history
  • Loading branch information
arwinvdv authored May 22, 2023
1 parent 7ace12c commit 424106a
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 35 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,23 @@ Get message headers as a [\Ddeboer\Imap\Message\Headers](/src/Message/Headers.ph
$message->getHeaders();
```

Get message body as HTML or plain text:
Get message body as HTML or plain text (only first part):

```php
$message->getBodyHtml(); // Content of text/html part, if present
$message->getBodyText(); // Content of text/plain part, if present
```


Get complete body (all parts):

```php
$body = $message->getCompleteBodyHtml(); // Content of text/html part, if present
if ($body === null) { // If body is null, there are no HTML parts, so let's try getting the text body
$body = $message->getCompleteBodyText(); // Content of text/plain part, if present
}
```

Reading the message body keeps the message as unseen.
If you want to mark the message as seen:

Expand Down
10 changes: 0 additions & 10 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@ parameters:
count: 1
path: src/Mailbox.php

-
message: "#^Cannot call method getDecodedContent\\(\\) on mixed\\.$#"
count: 2
path: src/Message/AbstractMessage.php

-
message: "#^Cannot call method getSubtype\\(\\) on mixed\\.$#"
count: 2
path: src/Message/AbstractMessage.php

-
message: "#^Call to function base64_decode\\(\\) requires parameter \\#2 to be true\\.$#"
count: 1
Expand Down
62 changes: 41 additions & 21 deletions src/Message/AbstractMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,43 +207,54 @@ final public function getReferences(): array
*/
final public function getBodyHtml(): ?string
{
$htmlParts = $this->getBodyHtmlParts();
$htmlParts = $this->getAllContentsBySubtype(self::SUBTYPE_HTML);

return $htmlParts[0] ?? null;
}

/**
* Get body HTML parts.
* Get all contents parts of specific subtype (self::SUBTYPE_HTML or self::SUBTYPE_PLAIN).
*
* @return string[]
*/
final public function getBodyHtmlParts(): array
final public function getAllContentsBySubtype(string $subtype): array
{
$iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
$htmlParts = [];
$parts = [];
/** @var PartInterface $part */
foreach ($iterator as $part) {
if (self::SUBTYPE_HTML === $part->getSubtype()) {
$htmlParts[] = $part->getDecodedContent();
if ($subtype === $part->getSubtype()) {
$parts[] = $part->getDecodedContent();
}
}
if (\count($htmlParts) > 0) {
return $htmlParts;
if (\count($parts) > 0) {
return $parts;
}

// If message has no parts and is HTML, return content of message itself.
if (self::SUBTYPE_HTML === $this->getSubtype()) {
// If message has no parts and is of right type, return content of message.
if ($subtype === $this->getSubtype()) {
return [$this->getDecodedContent()];
}

return [];
}

/**
* Get body HTML parts.
*
* @return string[]
*/
final public function getBodyHtmlParts(): array
{
return $this->getAllContentsBySubtype(self::SUBTYPE_HTML);
}

/**
* Get all body HTML parts merged into 1 html.
*/
final public function getCompleteBodyHtml(): ?string
{
$htmlParts = $this->getBodyHtmlParts();
$htmlParts = $this->getAllContentsBySubtype(self::SUBTYPE_HTML);

if (1 === \count($htmlParts)) {
return $htmlParts[0];
Expand Down Expand Up @@ -279,19 +290,28 @@ final public function getCompleteBodyHtml(): ?string
*/
final public function getBodyText(): ?string
{
$iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $part) {
if (self::SUBTYPE_PLAIN === $part->getSubtype()) {
return $part->getDecodedContent();
}
}
$plainParts = $this->getAllContentsBySubtype(self::SUBTYPE_PLAIN);

return $plainParts[0] ?? null;
}

// If message has no parts, return content of message itself.
if (self::SUBTYPE_PLAIN === $this->getSubtype()) {
return $this->getDecodedContent();
/**
* Get all body PLAIN parts merged into 1 string.
*
* @return null|string Null if message has no PLAIN message parts
*/
final public function getCompleteBodyText(): ?string
{
$plainParts = $this->getAllContentsBySubtype(self::SUBTYPE_PLAIN);

if (1 === \count($plainParts)) {
return $plainParts[0];
}
if (0 === \count($plainParts)) {
return null;
}

return null;
return \implode("\n", $plainParts);
}

/**
Expand Down
16 changes: 15 additions & 1 deletion src/Message/BasicMessageInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ public function getInReplyTo(): array;
*/
public function getReferences(): array;

/**
* Get message parts by type.
*
* @return string[]
*/
public function getAllContentsBySubtype(string $subtype): array;

/**
* Get first body HTML part.
*
Expand All @@ -132,7 +139,7 @@ public function getBodyHtmlParts(): array;
/**
* Get all body HTML parts merged into 1 html.
*
* @return null|string Null if message has no HTML message part
* @return null|string Null if message has no HTML message parts
*/
public function getCompleteBodyHtml(): ?string;

Expand All @@ -141,6 +148,13 @@ public function getCompleteBodyHtml(): ?string;
*/
public function getBodyText(): ?string;

/**
* Get all body PLAIN parts merged into 1 string.
*
* @return null|string Null if message has no PLAIN message parts
*/
public function getCompleteBodyText(): ?string;

/**
* Get attachments (if any) linked to this e-mail.
*
Expand Down
56 changes: 54 additions & 2 deletions tests/MessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,7 @@ public function testMultipleHtmlParts(): void

// Test html parts
self::assertCount(3, $message->getBodyHtmlParts());
self::assertCount(3, $message->getAllContentsBySubtype(Message::SUBTYPE_HTML));

// Test html parts
$completeBody = $message->getCompleteBodyHtml();
Expand All @@ -1065,7 +1066,7 @@ public function testBodyHtmlEmpty(): void

$message = $this->mailbox->getMessage(1);

self::assertCount(0, $message->getBodyHtmlParts());
self::assertCount(0, $message->getAllContentsBySubtype(Message::SUBTYPE_HTML));

self::assertNull($message->getCompleteBodyHtml());
}
Expand All @@ -1076,11 +1077,62 @@ public function testBodyHtmlOnePart(): void

$message = $this->mailbox->getMessage(1);

self::assertCount(1, $message->getBodyHtmlParts());
self::assertCount(1, $message->getAllContentsBySubtype(Message::SUBTYPE_HTML));

self::assertNotNull($message->getCompleteBodyHtml());
}

public function testMultipleTextParts(): void
{
$this->mailbox->addMessage($this->getFixture('multiple_plain_parts_and_attachments'));

$message = $this->mailbox->getMessage(1);

// Test attachments
$expectedFileNames = [
'attachment1.pdf',
'attachment2.pdf',
];
$attachments = $message->getAttachments();
self::assertCount(2, $attachments);
foreach ($attachments as $attachment) {
self::assertContains($attachment->getFilename(), $expectedFileNames);
}

// Test html parts
self::assertCount(3, $message->getAllContentsBySubtype(Message::SUBTYPE_PLAIN));

// Test html parts
$completeBody = $message->getCompleteBodyText();
$completeBody = null === $completeBody ? '' : $completeBody;

self::assertStringContainsString('first', $completeBody);
self::assertStringContainsString('second', $completeBody);
self::assertStringContainsString('last', $completeBody);
}

public function testBodyTextEmpty(): void
{
$this->mailbox->addMessage($this->getFixture('html_only'));

$message = $this->mailbox->getMessage(1);

self::assertCount(0, $message->getAllContentsBySubtype(Message::SUBTYPE_PLAIN));

self::assertNull($message->getCompleteBodyText());
}

public function testBodyTextOnePart(): void
{
$this->mailbox->addMessage($this->getFixture('plain_only'));

$message = $this->mailbox->getMessage(1);

self::assertCount(1, $message->getAllContentsBySubtype(Message::SUBTYPE_PLAIN));

self::assertNotNull($message->getCompleteBodyText());
}

public function testImapMimeHeaderDecodeReturnsFalse(): void
{
$this->mailbox->addMessage($this->getFixture('imap_mime_header_decode_returns_false'));
Expand Down
Loading

0 comments on commit 424106a

Please sign in to comment.