Skip to content

Commit 703ef29

Browse files
authored
Merge pull request #155 from DirectoryTree/lazy-load-headers
Support lazy loading headers and header attributes
2 parents c3ec00d + 022fafd commit 703ef29

File tree

7 files changed

+515
-239
lines changed

7 files changed

+515
-239
lines changed

src/Attachment.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace DirectoryTree\ImapEngine;
44

5+
use GuzzleHttp\Psr7\Utils;
56
use Illuminate\Contracts\Support\Arrayable;
67
use JsonSerializable;
78
use Psr\Http\Message\StreamInterface;
89
use Symfony\Component\Mime\MimeTypes;
10+
use ZBateson\MailMimeParser\Message\IMessagePart;
911

1012
class Attachment implements Arrayable, JsonSerializable
1113
{
@@ -20,6 +22,64 @@ public function __construct(
2022
protected StreamInterface $contentStream,
2123
) {}
2224

25+
/**
26+
* Get attachments from a parsed message.
27+
*
28+
* @return Attachment[]
29+
*/
30+
public static function parsed(MessageInterface $message): array
31+
{
32+
$attachments = [];
33+
34+
foreach ($message->parse()->getAllAttachmentParts() as $part) {
35+
if (static::isForwardedMessage($part)) {
36+
$attachments = array_merge($attachments, (new FileMessage($part->getContent()))->attachments());
37+
} else {
38+
$attachments[] = new Attachment(
39+
$part->getFilename(),
40+
$part->getContentId(),
41+
$part->getContentType(),
42+
$part->getContentDisposition(),
43+
$part->getBinaryContentStream() ?? Utils::streamFor(''),
44+
);
45+
}
46+
}
47+
48+
return $attachments;
49+
}
50+
51+
/**
52+
* Get attachments from a message's body structure using lazy streams.
53+
*
54+
* @return Attachment[]
55+
*/
56+
public static function lazy(Message $message): array
57+
{
58+
$attachments = [];
59+
60+
foreach ($message->bodyStructure(fetch: true)?->attachments() ?? [] as $part) {
61+
$attachments[] = new Attachment(
62+
$part->filename(),
63+
$part->id(),
64+
$part->contentType(),
65+
$part->disposition()?->type()?->value,
66+
new Support\LazyBodyPartStream($message, $part),
67+
);
68+
}
69+
70+
return $attachments;
71+
}
72+
73+
/**
74+
* Determine if the attachment should be treated as an embedded forwarded message.
75+
*/
76+
protected static function isForwardedMessage(IMessagePart $part): bool
77+
{
78+
return empty($part->getFilename())
79+
&& strtolower((string) $part->getContentType()) === 'message/rfc822'
80+
&& strtolower((string) $part->getContentDisposition()) !== 'attachment';
81+
}
82+
2383
/**
2484
* Get the attachment's filename.
2585
*/

src/FileMessage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class FileMessage implements MessageInterface
99
{
10-
use HasFlags, HasParsedMessage;
10+
use HasFlags, HasMessageAccessors, HasParsedMessage;
1111

1212
/**
1313
* Constructor.

src/HasMessageAccessors.php

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine;
4+
5+
use Carbon\Carbon;
6+
use Carbon\CarbonInterface;
7+
use ZBateson\MailMimeParser\Header\DateHeader;
8+
use ZBateson\MailMimeParser\Header\HeaderConsts;
9+
use ZBateson\MailMimeParser\Header\IHeader;
10+
use ZBateson\MailMimeParser\Header\IHeaderPart;
11+
use ZBateson\MailMimeParser\IMessage;
12+
13+
trait HasMessageAccessors
14+
{
15+
/**
16+
* Parse the message into a MailMimeMessage instance.
17+
*/
18+
abstract public function parse(): IMessage;
19+
20+
/**
21+
* Get addresses from the given header.
22+
*
23+
* @return Address[]
24+
*/
25+
abstract public function addresses(string $header): array;
26+
27+
/**
28+
* Get a header from the message.
29+
*/
30+
abstract public function header(string $name, int $offset = 0): ?IHeader;
31+
32+
/**
33+
* Get the message date and time.
34+
*/
35+
public function date(): ?CarbonInterface
36+
{
37+
if (! $header = $this->header(HeaderConsts::DATE)) {
38+
return null;
39+
}
40+
41+
if (! $header instanceof DateHeader) {
42+
return null;
43+
}
44+
45+
if (! $date = $header->getDateTime()) {
46+
return null;
47+
}
48+
49+
return Carbon::instance($date);
50+
}
51+
52+
/**
53+
* Get the message's message-id.
54+
*/
55+
public function messageId(): ?string
56+
{
57+
return $this->header(HeaderConsts::MESSAGE_ID)?->getValue();
58+
}
59+
60+
/**
61+
* Get the message's subject.
62+
*/
63+
public function subject(): ?string
64+
{
65+
return $this->header(HeaderConsts::SUBJECT)?->getValue();
66+
}
67+
68+
/**
69+
* Get the FROM address.
70+
*/
71+
public function from(): ?Address
72+
{
73+
return head($this->addresses(HeaderConsts::FROM)) ?: null;
74+
}
75+
76+
/**
77+
* Get the SENDER address.
78+
*/
79+
public function sender(): ?Address
80+
{
81+
return head($this->addresses(HeaderConsts::SENDER)) ?: null;
82+
}
83+
84+
/**
85+
* Get the REPLY-TO address.
86+
*/
87+
public function replyTo(): ?Address
88+
{
89+
return head($this->addresses(HeaderConsts::REPLY_TO)) ?: null;
90+
}
91+
92+
/**
93+
* Get the IN-REPLY-TO message identifier(s).
94+
*
95+
* @return string[]
96+
*/
97+
public function inReplyTo(): array
98+
{
99+
$parts = $this->header(HeaderConsts::IN_REPLY_TO)?->getParts() ?? [];
100+
101+
$values = array_map(fn (IHeaderPart $part) => $part->getValue(), $parts);
102+
103+
return array_values(array_filter($values));
104+
}
105+
106+
/**
107+
* Get the TO addresses.
108+
*
109+
* @return Address[]
110+
*/
111+
public function to(): array
112+
{
113+
return $this->addresses(HeaderConsts::TO);
114+
}
115+
116+
/**
117+
* Get the CC addresses.
118+
*
119+
* @return Address[]
120+
*/
121+
public function cc(): array
122+
{
123+
return $this->addresses(HeaderConsts::CC);
124+
}
125+
126+
/**
127+
* Get the BCC addresses.
128+
*
129+
* @return Address[]
130+
*/
131+
public function bcc(): array
132+
{
133+
return $this->addresses(HeaderConsts::BCC);
134+
}
135+
136+
/**
137+
* Get the message's HTML content.
138+
*/
139+
public function html(): ?string
140+
{
141+
return $this->parse()->getHtmlContent();
142+
}
143+
144+
/**
145+
* Get the message's text content.
146+
*/
147+
public function text(): ?string
148+
{
149+
return $this->parse()->getTextContent();
150+
}
151+
152+
/**
153+
* Get the message's attachments.
154+
*
155+
* @return Attachment[]
156+
*/
157+
public function attachments(): array
158+
{
159+
return Attachment::parsed($this);
160+
}
161+
162+
/**
163+
* Determine if the message has attachments.
164+
*/
165+
public function hasAttachments(): bool
166+
{
167+
return $this->attachmentCount() > 0;
168+
}
169+
170+
/**
171+
* Get the count of attachments.
172+
*/
173+
public function attachmentCount(): int
174+
{
175+
return $this->parse()->getAttachmentCount();
176+
}
177+
}

0 commit comments

Comments
 (0)