blob: 5d67e904d17edba71c180464d7d7002a8d9508c8 [file] [log] [blame]
<?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);
}
}