Skip to content

Commit 424106a

Browse files
authored
Add API to get all text parts and complete text body (#560)
1 parent 7ace12c commit 424106a

File tree

6 files changed

+308
-35
lines changed

6 files changed

+308
-35
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,23 @@ Get message headers as a [\Ddeboer\Imap\Message\Headers](/src/Message/Headers.ph
188188
$message->getHeaders();
189189
```
190190

191-
Get message body as HTML or plain text:
191+
Get message body as HTML or plain text (only first part):
192192

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

198+
199+
Get complete body (all parts):
200+
201+
```php
202+
$body = $message->getCompleteBodyHtml(); // Content of text/html part, if present
203+
if ($body === null) { // If body is null, there are no HTML parts, so let's try getting the text body
204+
$body = $message->getCompleteBodyText(); // Content of text/plain part, if present
205+
}
206+
```
207+
198208
Reading the message body keeps the message as unseen.
199209
If you want to mark the message as seen:
200210

phpstan-baseline.neon

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,6 @@ parameters:
1010
count: 1
1111
path: src/Mailbox.php
1212

13-
-
14-
message: "#^Cannot call method getDecodedContent\\(\\) on mixed\\.$#"
15-
count: 2
16-
path: src/Message/AbstractMessage.php
17-
18-
-
19-
message: "#^Cannot call method getSubtype\\(\\) on mixed\\.$#"
20-
count: 2
21-
path: src/Message/AbstractMessage.php
22-
2313
-
2414
message: "#^Call to function base64_decode\\(\\) requires parameter \\#2 to be true\\.$#"
2515
count: 1

src/Message/AbstractMessage.php

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -207,43 +207,54 @@ final public function getReferences(): array
207207
*/
208208
final public function getBodyHtml(): ?string
209209
{
210-
$htmlParts = $this->getBodyHtmlParts();
210+
$htmlParts = $this->getAllContentsBySubtype(self::SUBTYPE_HTML);
211211

212212
return $htmlParts[0] ?? null;
213213
}
214214

215215
/**
216-
* Get body HTML parts.
216+
* Get all contents parts of specific subtype (self::SUBTYPE_HTML or self::SUBTYPE_PLAIN).
217217
*
218218
* @return string[]
219219
*/
220-
final public function getBodyHtmlParts(): array
220+
final public function getAllContentsBySubtype(string $subtype): array
221221
{
222222
$iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
223-
$htmlParts = [];
223+
$parts = [];
224+
/** @var PartInterface $part */
224225
foreach ($iterator as $part) {
225-
if (self::SUBTYPE_HTML === $part->getSubtype()) {
226-
$htmlParts[] = $part->getDecodedContent();
226+
if ($subtype === $part->getSubtype()) {
227+
$parts[] = $part->getDecodedContent();
227228
}
228229
}
229-
if (\count($htmlParts) > 0) {
230-
return $htmlParts;
230+
if (\count($parts) > 0) {
231+
return $parts;
231232
}
232233

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

238239
return [];
239240
}
240241

242+
/**
243+
* Get body HTML parts.
244+
*
245+
* @return string[]
246+
*/
247+
final public function getBodyHtmlParts(): array
248+
{
249+
return $this->getAllContentsBySubtype(self::SUBTYPE_HTML);
250+
}
251+
241252
/**
242253
* Get all body HTML parts merged into 1 html.
243254
*/
244255
final public function getCompleteBodyHtml(): ?string
245256
{
246-
$htmlParts = $this->getBodyHtmlParts();
257+
$htmlParts = $this->getAllContentsBySubtype(self::SUBTYPE_HTML);
247258

248259
if (1 === \count($htmlParts)) {
249260
return $htmlParts[0];
@@ -279,19 +290,28 @@ final public function getCompleteBodyHtml(): ?string
279290
*/
280291
final public function getBodyText(): ?string
281292
{
282-
$iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
283-
foreach ($iterator as $part) {
284-
if (self::SUBTYPE_PLAIN === $part->getSubtype()) {
285-
return $part->getDecodedContent();
286-
}
287-
}
293+
$plainParts = $this->getAllContentsBySubtype(self::SUBTYPE_PLAIN);
294+
295+
return $plainParts[0] ?? null;
296+
}
288297

289-
// If message has no parts, return content of message itself.
290-
if (self::SUBTYPE_PLAIN === $this->getSubtype()) {
291-
return $this->getDecodedContent();
298+
/**
299+
* Get all body PLAIN parts merged into 1 string.
300+
*
301+
* @return null|string Null if message has no PLAIN message parts
302+
*/
303+
final public function getCompleteBodyText(): ?string
304+
{
305+
$plainParts = $this->getAllContentsBySubtype(self::SUBTYPE_PLAIN);
306+
307+
if (1 === \count($plainParts)) {
308+
return $plainParts[0];
309+
}
310+
if (0 === \count($plainParts)) {
311+
return null;
292312
}
293313

294-
return null;
314+
return \implode("\n", $plainParts);
295315
}
296316

297317
/**

src/Message/BasicMessageInterface.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ public function getInReplyTo(): array;
115115
*/
116116
public function getReferences(): array;
117117

118+
/**
119+
* Get message parts by type.
120+
*
121+
* @return string[]
122+
*/
123+
public function getAllContentsBySubtype(string $subtype): array;
124+
118125
/**
119126
* Get first body HTML part.
120127
*
@@ -132,7 +139,7 @@ public function getBodyHtmlParts(): array;
132139
/**
133140
* Get all body HTML parts merged into 1 html.
134141
*
135-
* @return null|string Null if message has no HTML message part
142+
* @return null|string Null if message has no HTML message parts
136143
*/
137144
public function getCompleteBodyHtml(): ?string;
138145

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

151+
/**
152+
* Get all body PLAIN parts merged into 1 string.
153+
*
154+
* @return null|string Null if message has no PLAIN message parts
155+
*/
156+
public function getCompleteBodyText(): ?string;
157+
144158
/**
145159
* Get attachments (if any) linked to this e-mail.
146160
*

tests/MessageTest.php

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,7 @@ public function testMultipleHtmlParts(): void
10491049

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

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

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

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

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

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

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

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

1085+
public function testMultipleTextParts(): void
1086+
{
1087+
$this->mailbox->addMessage($this->getFixture('multiple_plain_parts_and_attachments'));
1088+
1089+
$message = $this->mailbox->getMessage(1);
1090+
1091+
// Test attachments
1092+
$expectedFileNames = [
1093+
'attachment1.pdf',
1094+
'attachment2.pdf',
1095+
];
1096+
$attachments = $message->getAttachments();
1097+
self::assertCount(2, $attachments);
1098+
foreach ($attachments as $attachment) {
1099+
self::assertContains($attachment->getFilename(), $expectedFileNames);
1100+
}
1101+
1102+
// Test html parts
1103+
self::assertCount(3, $message->getAllContentsBySubtype(Message::SUBTYPE_PLAIN));
1104+
1105+
// Test html parts
1106+
$completeBody = $message->getCompleteBodyText();
1107+
$completeBody = null === $completeBody ? '' : $completeBody;
1108+
1109+
self::assertStringContainsString('first', $completeBody);
1110+
self::assertStringContainsString('second', $completeBody);
1111+
self::assertStringContainsString('last', $completeBody);
1112+
}
1113+
1114+
public function testBodyTextEmpty(): void
1115+
{
1116+
$this->mailbox->addMessage($this->getFixture('html_only'));
1117+
1118+
$message = $this->mailbox->getMessage(1);
1119+
1120+
self::assertCount(0, $message->getAllContentsBySubtype(Message::SUBTYPE_PLAIN));
1121+
1122+
self::assertNull($message->getCompleteBodyText());
1123+
}
1124+
1125+
public function testBodyTextOnePart(): void
1126+
{
1127+
$this->mailbox->addMessage($this->getFixture('plain_only'));
1128+
1129+
$message = $this->mailbox->getMessage(1);
1130+
1131+
self::assertCount(1, $message->getAllContentsBySubtype(Message::SUBTYPE_PLAIN));
1132+
1133+
self::assertNotNull($message->getCompleteBodyText());
1134+
}
1135+
10841136
public function testImapMimeHeaderDecodeReturnsFalse(): void
10851137
{
10861138
$this->mailbox->addMessage($this->getFixture('imap_mime_header_decode_returns_false'));

0 commit comments

Comments
 (0)