blob: 2f77799f72a62773f0e070908d11a213728b8e1f [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3declare(strict_types=1);
4
5namespace Ddeboer\Imap;
6
7use DateTimeInterface;
8use Ddeboer\Imap\Exception\ImapNumMsgException;
9use Ddeboer\Imap\Exception\ImapStatusException;
10use Ddeboer\Imap\Exception\InvalidSearchCriteriaException;
11use Ddeboer\Imap\Exception\MessageCopyException;
12use Ddeboer\Imap\Exception\MessageMoveException;
13use Ddeboer\Imap\Search\ConditionInterface;
14use Ddeboer\Imap\Search\LogicalOperator\All;
15
16/**
17 * An IMAP mailbox (commonly referred to as a 'folder').
18 */
19final class Mailbox implements MailboxInterface
20{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020021 private ImapResourceInterface $resource;
22 private string $name;
23 private \stdClass $info;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010024
25 /**
26 * Constructor.
27 *
28 * @param ImapResourceInterface $resource IMAP resource
29 * @param string $name Mailbox decoded name
30 * @param \stdClass $info Mailbox info
31 */
32 public function __construct(ImapResourceInterface $resource, string $name, \stdClass $info)
33 {
34 $this->resource = new ImapResource($resource->getStream(), $this);
35 $this->name = $name;
36 $this->info = $info;
37 }
38
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010039 public function getName(): string
40 {
41 return $this->name;
42 }
43
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010044 public function getEncodedName(): string
45 {
46 /** @var string $name */
47 $name = $this->info->name;
48
49 return (string) \preg_replace('/^{.+}/', '', $name);
50 }
51
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010052 public function getFullEncodedName(): string
53 {
54 return $this->info->name;
55 }
56
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010057 public function getAttributes(): int
58 {
59 return $this->info->attributes;
60 }
61
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010062 public function getDelimiter(): string
63 {
64 return $this->info->delimiter;
65 }
66
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010067 public function count()
68 {
69 $return = \imap_num_msg($this->resource->getStream());
70
71 if (false === $return) {
72 throw new ImapNumMsgException('imap_num_msg failed');
73 }
74
75 return $return;
76 }
77
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010078 public function getStatus(int $flags = null): \stdClass
79 {
80 $return = \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL);
81
82 if (false === $return) {
83 throw new ImapStatusException('imap_status failed');
84 }
85
86 return $return;
87 }
88
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010089 public function setFlag(string $flag, $numbers): bool
90 {
91 return \imap_setflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID);
92 }
93
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010094 public function clearFlag(string $flag, $numbers): bool
95 {
96 return \imap_clearflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID);
97 }
98
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010099 public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface
100 {
101 if (null === $search) {
102 $search = new All();
103 }
104 $query = $search->toString();
105
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100106 if (\PHP_VERSION_ID < 80000) {
107 $descending = (int) $descending;
108 }
109
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100110 // We need to clear the stack to know whether imap_last_error()
111 // is related to this imap_search
112 \imap_errors();
113
114 if (null !== $sortCriteria) {
115 $params = [
116 $this->resource->getStream(),
117 $sortCriteria,
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100118 $descending,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100119 \SE_UID,
120 $query,
121 ];
122 if (null !== $charset) {
123 $params[] = $charset;
124 }
125 $messageNumbers = \imap_sort(...$params);
126 } else {
127 $params = [
128 $this->resource->getStream(),
129 $query,
130 \SE_UID,
131 ];
132 if (null !== $charset) {
133 $params[] = $charset;
134 }
135 $messageNumbers = \imap_search(...$params);
136 }
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100137 if (false !== \imap_last_error()) {
138 // this way all errors occurred during search will be reported
139 throw new InvalidSearchCriteriaException(
140 \sprintf('Invalid search criteria [%s]', $query)
141 );
142 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100143 if (false === $messageNumbers) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100144 // imap_search can also return false
145 $messageNumbers = [];
146 }
147
148 return new MessageIterator($this->resource, $messageNumbers);
149 }
150
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100151 public function getMessageSequence(string $sequence): MessageIteratorInterface
152 {
153 \imap_errors();
154
155 $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, \FT_UID);
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100156 if (false !== \imap_last_error()) {
157 throw new InvalidSearchCriteriaException(
158 \sprintf('Invalid sequence [%s]', $sequence)
159 );
160 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100161 if (\is_array($overview) && [] !== $overview) {
162 $messageNumbers = \array_column($overview, 'uid');
163 } else {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100164 $messageNumbers = [];
165 }
166
167 return new MessageIterator($this->resource, $messageNumbers);
168 }
169
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100170 public function getMessage(int $number): MessageInterface
171 {
172 return new Message($this->resource, $number);
173 }
174
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100175 public function getIterator(): MessageIteratorInterface
176 {
177 return $this->getMessages();
178 }
179
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100180 public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool
181 {
182 $arguments = [
183 $this->resource->getStream(),
184 $this->getFullEncodedName(),
185 $message,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200186 $options ?? '',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100187 ];
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200188 if (null !== $internalDate) {
189 $arguments[] = $internalDate->format('d-M-Y H:i:s O');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100190 }
191
192 return \imap_append(...$arguments);
193 }
194
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100195 public function getThread(): array
196 {
197 \set_error_handler(static function (): bool {
198 return true;
199 });
200
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200201 /** @var array<string, int>|false $tree */
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100202 $tree = \imap_thread($this->resource->getStream(), \SE_UID);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100203
204 \restore_error_handler();
205
206 return false !== $tree ? $tree : [];
207 }
208
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100209 public function move($numbers, MailboxInterface $mailbox): void
210 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200211 if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID | \CP_MOVE)) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100212 throw new MessageMoveException(\sprintf('Messages cannot be moved to "%s"', $mailbox->getName()));
213 }
214 }
215
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100216 public function copy($numbers, MailboxInterface $mailbox): void
217 {
218 if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) {
219 throw new MessageCopyException(\sprintf('Messages cannot be copied to "%s"', $mailbox->getName()));
220 }
221 }
222
223 /**
224 * Prepare message ids for the use with bulk functions.
225 *
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200226 * @param array<int, int|string>|MessageIterator|string $messageIds Message numbers
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100227 */
228 private function prepareMessageIds($messageIds): string
229 {
230 if ($messageIds instanceof MessageIterator) {
231 $messageIds = $messageIds->getArrayCopy();
232 }
233
234 if (\is_array($messageIds)) {
235 $messageIds = \implode(',', $messageIds);
236 }
237
238 return $messageIds;
239 }
240}