blob: 5d67e904d17edba71c180464d7d7002a8d9508c8 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3declare(strict_types=1);
4
5namespace Ddeboer\Imap\Message;
6
7use Ddeboer\Imap\Exception\InvalidDateHeaderException;
8
9abstract class AbstractMessage extends AbstractPart
10{
11 /**
12 * @var null|array
13 */
14 private $attachments;
15
16 /**
17 * Get message headers.
18 */
19 abstract public function getHeaders(): Headers;
20
21 /**
22 * Get message id.
23 *
24 * A unique message id in the form <...>
25 */
26 final public function getId(): ?string
27 {
28 return $this->getHeaders()->get('message_id');
29 }
30
31 /**
32 * Get message sender (from headers).
33 */
34 final public function getFrom(): ?EmailAddress
35 {
36 $from = $this->getHeaders()->get('from');
37
38 return null !== $from ? $this->decodeEmailAddress($from[0]) : null;
39 }
40
41 /**
42 * Get To recipients.
43 *
44 * @return EmailAddress[] Empty array in case message has no To: recipients
45 */
46 final public function getTo(): array
47 {
48 return $this->decodeEmailAddresses($this->getHeaders()->get('to') ?: []);
49 }
50
51 /**
52 * Get Cc recipients.
53 *
54 * @return EmailAddress[] Empty array in case message has no CC: recipients
55 */
56 final public function getCc(): array
57 {
58 return $this->decodeEmailAddresses($this->getHeaders()->get('cc') ?: []);
59 }
60
61 /**
62 * Get Bcc recipients.
63 *
64 * @return EmailAddress[] Empty array in case message has no BCC: recipients
65 */
66 final public function getBcc(): array
67 {
68 return $this->decodeEmailAddresses($this->getHeaders()->get('bcc') ?: []);
69 }
70
71 /**
72 * Get Reply-To recipients.
73 *
74 * @return EmailAddress[] Empty array in case message has no Reply-To: recipients
75 */
76 final public function getReplyTo(): array
77 {
78 return $this->decodeEmailAddresses($this->getHeaders()->get('reply_to') ?: []);
79 }
80
81 /**
82 * Get Sender.
83 *
84 * @return EmailAddress[] Empty array in case message has no Sender: recipients
85 */
86 final public function getSender(): array
87 {
88 return $this->decodeEmailAddresses($this->getHeaders()->get('sender') ?: []);
89 }
90
91 /**
92 * Get Return-Path.
93 *
94 * @return EmailAddress[] Empty array in case message has no Return-Path: recipients
95 */
96 final public function getReturnPath(): array
97 {
98 return $this->decodeEmailAddresses($this->getHeaders()->get('return_path') ?: []);
99 }
100
101 /**
102 * Get date (from headers).
103 */
104 final public function getDate(): ?\DateTimeImmutable
105 {
106 /** @var null|string $dateHeader */
107 $dateHeader = $this->getHeaders()->get('date');
108 if (null === $dateHeader) {
109 return null;
110 }
111
112 $alteredValue = $dateHeader;
113 $alteredValue = \str_replace(',', '', $alteredValue);
114 $alteredValue = (string) \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue);
115 $alteredValue = (string) \preg_replace('/\(.*\)/', '', $alteredValue);
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100116 $alteredValue = (string) \preg_replace('/\<.*\>/', '', $alteredValue);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100117 $alteredValue = (string) \preg_replace('/\bUT\b/', 'UTC', $alteredValue);
118 if (0 === \preg_match('/\d\d:\d\d:\d\d.* [\+\-]\d\d:?\d\d/', $alteredValue)) {
119 $alteredValue .= ' +0000';
120 }
121 // Handle numeric months
122 $alteredValue = (string) \preg_replace('/^(\d\d) (\d\d) (\d\d(?:\d\d)?) /', '$3-$2-$1 ', $alteredValue);
123
124 try {
125 $date = new \DateTimeImmutable($alteredValue);
126 } catch (\Throwable $ex) {
127 throw new InvalidDateHeaderException(\sprintf('Invalid Date header found: "%s"', $dateHeader), 0, $ex);
128 }
129
130 return $date;
131 }
132
133 /**
134 * Get message size (from headers).
135 *
136 * @return null|int|string
137 */
138 final public function getSize()
139 {
140 return $this->getHeaders()->get('size');
141 }
142
143 /**
144 * Get message subject (from headers).
145 */
146 final public function getSubject(): ?string
147 {
148 return $this->getHeaders()->get('subject');
149 }
150
151 /**
152 * Get message In-Reply-To (from headers).
153 */
154 final public function getInReplyTo(): array
155 {
156 $inReplyTo = $this->getHeaders()->get('in_reply_to');
157
158 return null !== $inReplyTo ? \explode(' ', $inReplyTo) : [];
159 }
160
161 /**
162 * Get message References (from headers).
163 */
164 final public function getReferences(): array
165 {
166 $references = $this->getHeaders()->get('references');
167
168 return null !== $references ? \explode(' ', $references) : [];
169 }
170
171 /**
172 * Get body HTML.
173 */
174 final public function getBodyHtml(): ?string
175 {
176 $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
177 foreach ($iterator as $part) {
178 if (self::SUBTYPE_HTML === $part->getSubtype()) {
179 return $part->getDecodedContent();
180 }
181 }
182
183 // If message has no parts and is HTML, return content of message itself.
184 if (self::SUBTYPE_HTML === $this->getSubtype()) {
185 return $this->getDecodedContent();
186 }
187
188 return null;
189 }
190
191 /**
192 * Get body text.
193 */
194 final public function getBodyText(): ?string
195 {
196 $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
197 foreach ($iterator as $part) {
198 if (self::SUBTYPE_PLAIN === $part->getSubtype()) {
199 return $part->getDecodedContent();
200 }
201 }
202
203 // If message has no parts, return content of message itself.
204 if (self::SUBTYPE_PLAIN === $this->getSubtype()) {
205 return $this->getDecodedContent();
206 }
207
208 return null;
209 }
210
211 /**
212 * Get attachments (if any) linked to this e-mail.
213 *
214 * @return AttachmentInterface[]
215 */
216 final public function getAttachments(): array
217 {
218 if (null === $this->attachments) {
219 $this->attachments = self::gatherAttachments($this);
220 }
221
222 return $this->attachments;
223 }
224
225 private static function gatherAttachments(PartInterface $part): array
226 {
227 $attachments = [];
228 foreach ($part->getParts() as $childPart) {
229 if ($childPart instanceof Attachment) {
230 $attachments[] = $childPart;
231 }
232 if ($childPart->hasChildren()) {
233 $attachments = \array_merge($attachments, self::gatherAttachments($childPart));
234 }
235 }
236
237 return $attachments;
238 }
239
240 /**
241 * Does this message have attachments?
242 */
243 final public function hasAttachments(): bool
244 {
245 return \count($this->getAttachments()) > 0;
246 }
247
248 /**
249 * @param \stdClass[] $addresses
250 */
251 private function decodeEmailAddresses(array $addresses): array
252 {
253 $return = [];
254 foreach ($addresses as $address) {
255 if (isset($address->mailbox)) {
256 $return[] = $this->decodeEmailAddress($address);
257 }
258 }
259
260 return $return;
261 }
262
263 private function decodeEmailAddress(\stdClass $value): EmailAddress
264 {
265 return new EmailAddress($value->mailbox, $value->host, $value->personal);
266 }
267}