| <?php |
| |
| declare(strict_types=1); |
| |
| namespace Ddeboer\Imap; |
| |
| use DateTimeInterface; |
| use Ddeboer\Imap\Exception\ImapNumMsgException; |
| use Ddeboer\Imap\Exception\ImapStatusException; |
| use Ddeboer\Imap\Exception\InvalidSearchCriteriaException; |
| use Ddeboer\Imap\Exception\MessageCopyException; |
| use Ddeboer\Imap\Exception\MessageMoveException; |
| use Ddeboer\Imap\Search\ConditionInterface; |
| use Ddeboer\Imap\Search\LogicalOperator\All; |
| |
| /** |
| * An IMAP mailbox (commonly referred to as a 'folder'). |
| */ |
| final class Mailbox implements MailboxInterface |
| { |
| /** |
| * @var ImapResourceInterface |
| */ |
| private $resource; |
| |
| /** |
| * @var string |
| */ |
| private $name; |
| |
| /** |
| * @var \stdClass |
| */ |
| private $info; |
| |
| /** |
| * Constructor. |
| * |
| * @param ImapResourceInterface $resource IMAP resource |
| * @param string $name Mailbox decoded name |
| * @param \stdClass $info Mailbox info |
| */ |
| public function __construct(ImapResourceInterface $resource, string $name, \stdClass $info) |
| { |
| $this->resource = new ImapResource($resource->getStream(), $this); |
| $this->name = $name; |
| $this->info = $info; |
| } |
| |
| /** |
| * Get mailbox decoded name. |
| */ |
| public function getName(): string |
| { |
| return $this->name; |
| } |
| |
| /** |
| * Get mailbox encoded path. |
| */ |
| public function getEncodedName(): string |
| { |
| /** @var string $name */ |
| $name = $this->info->name; |
| |
| return (string) \preg_replace('/^{.+}/', '', $name); |
| } |
| |
| /** |
| * Get mailbox encoded full name. |
| */ |
| public function getFullEncodedName(): string |
| { |
| return $this->info->name; |
| } |
| |
| /** |
| * Get mailbox attributes. |
| */ |
| public function getAttributes(): int |
| { |
| return $this->info->attributes; |
| } |
| |
| /** |
| * Get mailbox delimiter. |
| */ |
| public function getDelimiter(): string |
| { |
| return $this->info->delimiter; |
| } |
| |
| /** |
| * Get number of messages in this mailbox. |
| * |
| * @return int |
| */ |
| public function count() |
| { |
| $return = \imap_num_msg($this->resource->getStream()); |
| |
| if (false === $return) { |
| throw new ImapNumMsgException('imap_num_msg failed'); |
| } |
| |
| return $return; |
| } |
| |
| /** |
| * Get Mailbox status. |
| */ |
| public function getStatus(int $flags = null): \stdClass |
| { |
| $return = \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL); |
| |
| if (false === $return) { |
| throw new ImapStatusException('imap_status failed'); |
| } |
| |
| return $return; |
| } |
| |
| /** |
| * Bulk Set Flag for Messages. |
| * |
| * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft |
| * @param array|MessageIterator|string $numbers Message numbers |
| */ |
| public function setFlag(string $flag, $numbers): bool |
| { |
| return \imap_setflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); |
| } |
| |
| /** |
| * Bulk Clear Flag for Messages. |
| * |
| * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft |
| * @param array|MessageIterator|string $numbers Message numbers |
| */ |
| public function clearFlag(string $flag, $numbers): bool |
| { |
| return \imap_clearflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); |
| } |
| |
| /** |
| * Get message ids. |
| * |
| * @param ConditionInterface $search Search expression (optional) |
| */ |
| public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface |
| { |
| if (null === $search) { |
| $search = new All(); |
| } |
| $query = $search->toString(); |
| |
| if (\PHP_VERSION_ID < 80000) { |
| $descending = (int) $descending; |
| } |
| |
| // We need to clear the stack to know whether imap_last_error() |
| // is related to this imap_search |
| \imap_errors(); |
| |
| if (null !== $sortCriteria) { |
| $params = [ |
| $this->resource->getStream(), |
| $sortCriteria, |
| $descending, |
| \SE_UID, |
| $query, |
| ]; |
| if (null !== $charset) { |
| $params[] = $charset; |
| } |
| $messageNumbers = \imap_sort(...$params); |
| } else { |
| $params = [ |
| $this->resource->getStream(), |
| $query, |
| \SE_UID, |
| ]; |
| if (null !== $charset) { |
| $params[] = $charset; |
| } |
| $messageNumbers = \imap_search(...$params); |
| } |
| if (false !== \imap_last_error()) { |
| // this way all errors occurred during search will be reported |
| throw new InvalidSearchCriteriaException( |
| \sprintf('Invalid search criteria [%s]', $query) |
| ); |
| } |
| if (false === $messageNumbers) { |
| // imap_search can also return false |
| $messageNumbers = []; |
| } |
| |
| return new MessageIterator($this->resource, $messageNumbers); |
| } |
| |
| /** |
| * Get message iterator for a sequence. |
| * |
| * @param string $sequence Message numbers |
| */ |
| public function getMessageSequence(string $sequence): MessageIteratorInterface |
| { |
| \imap_errors(); |
| |
| $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, \FT_UID); |
| if (false !== \imap_last_error()) { |
| throw new InvalidSearchCriteriaException( |
| \sprintf('Invalid sequence [%s]', $sequence) |
| ); |
| } |
| if (\is_array($overview) && [] !== $overview) { |
| $messageNumbers = \array_column($overview, 'uid'); |
| } else { |
| $messageNumbers = []; |
| } |
| |
| return new MessageIterator($this->resource, $messageNumbers); |
| } |
| |
| /** |
| * Get a message by message number. |
| * |
| * @param int $number Message number |
| */ |
| public function getMessage(int $number): MessageInterface |
| { |
| return new Message($this->resource, $number); |
| } |
| |
| /** |
| * Get messages in this mailbox. |
| */ |
| public function getIterator(): MessageIteratorInterface |
| { |
| return $this->getMessages(); |
| } |
| |
| /** |
| * Add a message to the mailbox. |
| */ |
| public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool |
| { |
| $arguments = [ |
| $this->resource->getStream(), |
| $this->getFullEncodedName(), |
| $message, |
| ]; |
| if (null !== $options) { |
| $arguments[] = $options; |
| if (null !== $internalDate) { |
| $arguments[] = $internalDate->format('d-M-Y H:i:s O'); |
| } |
| } |
| |
| return \imap_append(...$arguments); |
| } |
| |
| /** |
| * Returns a tree of threaded message for the current Mailbox. |
| */ |
| public function getThread(): array |
| { |
| \set_error_handler(static function (): bool { |
| return true; |
| }); |
| |
| /** @var array|false $tree */ |
| $tree = \imap_thread($this->resource->getStream(), \SE_UID); |
| |
| \restore_error_handler(); |
| |
| return false !== $tree ? $tree : []; |
| } |
| |
| /** |
| * Bulk move messages. |
| * |
| * @param array|MessageIterator|string $numbers Message numbers |
| * @param MailboxInterface $mailbox Destination Mailbox to move the messages to |
| * |
| * @throws \Ddeboer\Imap\Exception\MessageMoveException |
| */ |
| public function move($numbers, MailboxInterface $mailbox): void |
| { |
| if (!\imap_mail_move($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) { |
| throw new MessageMoveException(\sprintf('Messages cannot be moved to "%s"', $mailbox->getName())); |
| } |
| } |
| |
| /** |
| * Bulk copy messages. |
| * |
| * @param array|MessageIterator|string $numbers Message numbers |
| * @param MailboxInterface $mailbox Destination Mailbox to copy the messages to |
| * |
| * @throws \Ddeboer\Imap\Exception\MessageCopyException |
| */ |
| public function copy($numbers, MailboxInterface $mailbox): void |
| { |
| if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) { |
| throw new MessageCopyException(\sprintf('Messages cannot be copied to "%s"', $mailbox->getName())); |
| } |
| } |
| |
| /** |
| * Prepare message ids for the use with bulk functions. |
| * |
| * @param array|MessageIterator|string $messageIds Message numbers |
| */ |
| private function prepareMessageIds($messageIds): string |
| { |
| if ($messageIds instanceof MessageIterator) { |
| $messageIds = $messageIds->getArrayCopy(); |
| } |
| |
| if (\is_array($messageIds)) { |
| $messageIds = \implode(',', $messageIds); |
| } |
| |
| return $messageIds; |
| } |
| } |