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 | /** |
| 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 Benkard | e39c4f8 | 2021-01-06 17:59:39 +0100 | [diff] [blame^] | 116 | $alteredValue = (string) \preg_replace('/\<.*\>/', '', $alteredValue); |
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 117 | $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 | } |