Skip to content

Commit c3ec00d

Browse files
authored
Merge pull request #154 from DirectoryTree/feature-153
Add ability to lazy load message contents (html, text, and attachments)
2 parents 0ec0007 + 71a4394 commit c3ec00d

File tree

12 files changed

+800
-32
lines changed

12 files changed

+800
-32
lines changed

src/HasParsedMessage.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ trait HasParsedMessage
2121
/**
2222
* The parsed message.
2323
*/
24-
protected ?IMessage $parsed = null;
24+
protected ?IMessage $message = null;
2525

2626
/**
2727
* Get the message date and time.
@@ -243,7 +243,7 @@ public function parse(): IMessage
243243
throw new RuntimeException('Cannot parse an empty message');
244244
}
245245

246-
return $this->parsed ??= MessageParser::parse((string) $this);
246+
return $this->message ??= MessageParser::parse((string) $this);
247247
}
248248

249249
/**

src/Message.php

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212

1313
class Message implements Arrayable, JsonSerializable, MessageInterface
1414
{
15-
use HasFlags, HasParsedMessage;
15+
use HasFlags, HasParsedMessage {
16+
text as protected getParsedText;
17+
html as protected getParsedHtml;
18+
attachments as protected getParsedAttachments;
19+
}
1620

1721
/**
1822
* The parsed body structure.
@@ -108,12 +112,16 @@ public function hasBody(): bool
108112
/**
109113
* Get the message's body structure.
110114
*/
111-
public function bodyStructure(): ?BodyStructureCollection
115+
public function bodyStructure(bool $lazy = false): ?BodyStructureCollection
112116
{
113117
if ($this->bodyStructure) {
114118
return $this->bodyStructure;
115119
}
116120

121+
if (! $this->bodyStructureData && $lazy) {
122+
$this->bodyStructureData = $this->fetchBodyStructureData();
123+
}
124+
117125
if (! $tokens = $this->bodyStructureData?->tokens()) {
118126
return null;
119127
}
@@ -218,6 +226,67 @@ public function move(string $folder, bool $expunge = false): ?int
218226
}
219227
}
220228

229+
/**
230+
* Get the message's text content.
231+
*/
232+
public function text(bool $lazy = false): ?string
233+
{
234+
if ($lazy && ! $this->hasBody()) {
235+
if ($part = $this->bodyStructure(lazy: true)?->text()) {
236+
return Support\BodyPartDecoder::text($part, $this->bodyPart($part->partNumber()));
237+
}
238+
}
239+
240+
return $this->getParsedText();
241+
}
242+
243+
/**
244+
* Get the message's HTML content.
245+
*/
246+
public function html(bool $lazy = false): ?string
247+
{
248+
if ($lazy && ! $this->hasBody()) {
249+
if ($part = $this->bodyStructure(lazy: true)?->html()) {
250+
return Support\BodyPartDecoder::text($part, $this->bodyPart($part->partNumber()));
251+
}
252+
}
253+
254+
return $this->getParsedHtml();
255+
}
256+
257+
/**
258+
* Get the message's attachments.
259+
*
260+
* @return Attachment[]
261+
*/
262+
public function attachments(bool $lazy = false): array
263+
{
264+
if ($lazy && ! $this->hasBody()) {
265+
return $this->getLazyAttachments();
266+
}
267+
268+
return $this->getParsedAttachments();
269+
}
270+
271+
/**
272+
* Get attachments using lazy loading from body structure.
273+
*
274+
* @return Attachment[]
275+
*/
276+
protected function getLazyAttachments(): array
277+
{
278+
return array_map(
279+
fn (BodyStructurePart $part) => new Attachment(
280+
$part->filename(),
281+
$part->id(),
282+
$part->contentType(),
283+
$part->disposition()?->type()?->value,
284+
new Support\LazyBodyPartStream($this, $part),
285+
),
286+
$this->bodyStructure(lazy: true)?->attachments() ?? []
287+
);
288+
}
289+
221290
/**
222291
* Fetch a specific body part by part number.
223292
*/
@@ -296,4 +365,27 @@ public function isEmpty(): bool
296365
{
297366
return ! $this->hasHead() && ! $this->hasBody();
298367
}
368+
369+
/**
370+
* Fetch the body structure data from the server.
371+
*/
372+
protected function fetchBodyStructureData(): ?ListData
373+
{
374+
$response = $this->folder
375+
->mailbox()
376+
->connection()
377+
->bodyStructure($this->uid);
378+
379+
if ($response->isEmpty()) {
380+
return null;
381+
}
382+
383+
$data = $response->first()->tokenAt(3);
384+
385+
if (! $data instanceof ListData) {
386+
return null;
387+
}
388+
389+
return $data->lookup('BODYSTRUCTURE');
390+
}
299391
}

src/MessageQuery.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
use Illuminate\Support\ItemNotFoundException;
2222

2323
/**
24-
* @mixin \DirectoryTree\ImapEngine\Connection\ImapQueryBuilder
24+
* @mixin ImapQueryBuilder
2525
*/
2626
class MessageQuery implements MessageQueryInterface
2727
{

src/MessageQueryInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
use BackedEnum;
66
use DirectoryTree\ImapEngine\Collections\MessageCollection;
7+
use DirectoryTree\ImapEngine\Connection\ImapQueryBuilder;
78
use DirectoryTree\ImapEngine\Enums\ImapFetchIdentifier;
89
use DirectoryTree\ImapEngine\Enums\ImapSortKey;
910
use DirectoryTree\ImapEngine\Pagination\LengthAwarePaginator;
1011

1112
/**
12-
* @mixin \DirectoryTree\ImapEngine\Connection\ImapQueryBuilder
13+
* @mixin ImapQueryBuilder
1314
*/
1415
interface MessageQueryInterface
1516
{

src/Support/BodyPartDecoder.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine\Support;
4+
5+
use DirectoryTree\ImapEngine\BodyStructurePart;
6+
use DirectoryTree\ImapEngine\MessageParser;
7+
8+
class BodyPartDecoder
9+
{
10+
/**
11+
* Decode raw text/html content using the part's metadata.
12+
*/
13+
public static function text(BodyStructurePart $part, ?string $content): ?string
14+
{
15+
$content = rtrim($content ?? '', "\r\n");
16+
17+
if ($content === '') {
18+
return null;
19+
}
20+
21+
$parsed = MessageParser::parse(
22+
MimeMessage::make($part, $content)
23+
);
24+
25+
return $part->subtype() === 'html'
26+
? $parsed->getHtmlContent()
27+
: $parsed->getTextContent();
28+
}
29+
30+
/**
31+
* Decode raw binary content using the part's metadata.
32+
*/
33+
public static function binary(BodyStructurePart $part, ?string $content): ?string
34+
{
35+
$content = rtrim($content ?? '', "\r\n");
36+
37+
if ($content === '') {
38+
return null;
39+
}
40+
41+
$parsed = MessageParser::parse(
42+
MimeMessage::make($part, $content)
43+
);
44+
45+
return $parsed->getBinaryContentStream()?->getContents();
46+
}
47+
}

0 commit comments

Comments
 (0)