| <?php |
| |
| declare(strict_types=1); |
| |
| namespace Ddeboer\Imap\Message; |
| |
| use Ddeboer\Imap\Exception\InvalidDateHeaderException; |
| |
| abstract class AbstractMessage extends AbstractPart |
| { |
| /** |
| * @var null|array |
| */ |
| private $attachments; |
| |
| /** |
| * Get message headers. |
| */ |
| abstract public function getHeaders(): Headers; |
| |
| /** |
| * Get message id. |
| * |
| * A unique message id in the form <...> |
| */ |
| final public function getId(): ?string |
| { |
| return $this->getHeaders()->get('message_id'); |
| } |
| |
| /** |
| * Get message sender (from headers). |
| */ |
| final public function getFrom(): ?EmailAddress |
| { |
| $from = $this->getHeaders()->get('from'); |
| |
| return null !== $from ? $this->decodeEmailAddress($from[0]) : null; |
| } |
| |
| /** |
| * Get To recipients. |
| * |
| * @return EmailAddress[] Empty array in case message has no To: recipients |
| */ |
| final public function getTo(): array |
| { |
| return $this->decodeEmailAddresses($this->getHeaders()->get('to') ?: []); |
| } |
| |
| /** |
| * Get Cc recipients. |
| * |
| * @return EmailAddress[] Empty array in case message has no CC: recipients |
| */ |
| final public function getCc(): array |
| { |
| return $this->decodeEmailAddresses($this->getHeaders()->get('cc') ?: []); |
| } |
| |
| /** |
| * Get Bcc recipients. |
| * |
| * @return EmailAddress[] Empty array in case message has no BCC: recipients |
| */ |
| final public function getBcc(): array |
| { |
| return $this->decodeEmailAddresses($this->getHeaders()->get('bcc') ?: []); |
| } |
| |
| /** |
| * Get Reply-To recipients. |
| * |
| * @return EmailAddress[] Empty array in case message has no Reply-To: recipients |
| */ |
| final public function getReplyTo(): array |
| { |
| return $this->decodeEmailAddresses($this->getHeaders()->get('reply_to') ?: []); |
| } |
| |
| /** |
| * Get Sender. |
| * |
| * @return EmailAddress[] Empty array in case message has no Sender: recipients |
| */ |
| final public function getSender(): array |
| { |
| return $this->decodeEmailAddresses($this->getHeaders()->get('sender') ?: []); |
| } |
| |
| /** |
| * Get Return-Path. |
| * |
| * @return EmailAddress[] Empty array in case message has no Return-Path: recipients |
| */ |
| final public function getReturnPath(): array |
| { |
| return $this->decodeEmailAddresses($this->getHeaders()->get('return_path') ?: []); |
| } |
| |
| /** |
| * Get date (from headers). |
| */ |
| final public function getDate(): ?\DateTimeImmutable |
| { |
| /** @var null|string $dateHeader */ |
| $dateHeader = $this->getHeaders()->get('date'); |
| if (null === $dateHeader) { |
| return null; |
| } |
| |
| $alteredValue = $dateHeader; |
| $alteredValue = \str_replace(',', '', $alteredValue); |
| $alteredValue = (string) \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue); |
| $alteredValue = (string) \preg_replace('/\(.*\)/', '', $alteredValue); |
| $alteredValue = (string) \preg_replace('/\<.*\>/', '', $alteredValue); |
| $alteredValue = (string) \preg_replace('/\bUT\b/', 'UTC', $alteredValue); |
| if (0 === \preg_match('/\d\d:\d\d:\d\d.* [\+\-]\d\d:?\d\d/', $alteredValue)) { |
| $alteredValue .= ' +0000'; |
| } |
| // Handle numeric months |
| $alteredValue = (string) \preg_replace('/^(\d\d) (\d\d) (\d\d(?:\d\d)?) /', '$3-$2-$1 ', $alteredValue); |
| |
| try { |
| $date = new \DateTimeImmutable($alteredValue); |
| } catch (\Throwable $ex) { |
| throw new InvalidDateHeaderException(\sprintf('Invalid Date header found: "%s"', $dateHeader), 0, $ex); |
| } |
| |
| return $date; |
| } |
| |
| /** |
| * Get message size (from headers). |
| * |
| * @return null|int|string |
| */ |
| final public function getSize() |
| { |
| return $this->getHeaders()->get('size'); |
| } |
| |
| /** |
| * Get message subject (from headers). |
| */ |
| final public function getSubject(): ?string |
| { |
| return $this->getHeaders()->get('subject'); |
| } |
| |
| /** |
| * Get message In-Reply-To (from headers). |
| */ |
| final public function getInReplyTo(): array |
| { |
| $inReplyTo = $this->getHeaders()->get('in_reply_to'); |
| |
| return null !== $inReplyTo ? \explode(' ', $inReplyTo) : []; |
| } |
| |
| /** |
| * Get message References (from headers). |
| */ |
| final public function getReferences(): array |
| { |
| $references = $this->getHeaders()->get('references'); |
| |
| return null !== $references ? \explode(' ', $references) : []; |
| } |
| |
| /** |
| * Get body HTML. |
| */ |
| final public function getBodyHtml(): ?string |
| { |
| $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); |
| foreach ($iterator as $part) { |
| if (self::SUBTYPE_HTML === $part->getSubtype()) { |
| return $part->getDecodedContent(); |
| } |
| } |
| |
| // If message has no parts and is HTML, return content of message itself. |
| if (self::SUBTYPE_HTML === $this->getSubtype()) { |
| return $this->getDecodedContent(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Get body text. |
| */ |
| final public function getBodyText(): ?string |
| { |
| $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); |
| foreach ($iterator as $part) { |
| if (self::SUBTYPE_PLAIN === $part->getSubtype()) { |
| return $part->getDecodedContent(); |
| } |
| } |
| |
| // If message has no parts, return content of message itself. |
| if (self::SUBTYPE_PLAIN === $this->getSubtype()) { |
| return $this->getDecodedContent(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Get attachments (if any) linked to this e-mail. |
| * |
| * @return AttachmentInterface[] |
| */ |
| final public function getAttachments(): array |
| { |
| if (null === $this->attachments) { |
| $this->attachments = self::gatherAttachments($this); |
| } |
| |
| return $this->attachments; |
| } |
| |
| private static function gatherAttachments(PartInterface $part): array |
| { |
| $attachments = []; |
| foreach ($part->getParts() as $childPart) { |
| if ($childPart instanceof Attachment) { |
| $attachments[] = $childPart; |
| } |
| if ($childPart->hasChildren()) { |
| $attachments = \array_merge($attachments, self::gatherAttachments($childPart)); |
| } |
| } |
| |
| return $attachments; |
| } |
| |
| /** |
| * Does this message have attachments? |
| */ |
| final public function hasAttachments(): bool |
| { |
| return \count($this->getAttachments()) > 0; |
| } |
| |
| /** |
| * @param \stdClass[] $addresses |
| */ |
| private function decodeEmailAddresses(array $addresses): array |
| { |
| $return = []; |
| foreach ($addresses as $address) { |
| if (isset($address->mailbox)) { |
| $return[] = $this->decodeEmailAddress($address); |
| } |
| } |
| |
| return $return; |
| } |
| |
| private function decodeEmailAddress(\stdClass $value): EmailAddress |
| { |
| return new EmailAddress($value->mailbox, $value->host, $value->personal); |
| } |
| } |