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