blob: 38823fee234642bcad1471465900820318d23449 [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 Benkard1ba53812022-12-27 17:32:58 +010067 #[\ReturnTypeWillChange]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010068 public function count()
69 {
70 $return = \imap_num_msg($this->resource->getStream());
71
72 if (false === $return) {
73 throw new ImapNumMsgException('imap_num_msg failed');
74 }
75
76 return $return;
77 }
78
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010079 public function getStatus(int $flags = null): \stdClass
80 {
81 $return = \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL);
82
83 if (false === $return) {
84 throw new ImapStatusException('imap_status failed');
85 }
86
87 return $return;
88 }
89
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010090 public function setFlag(string $flag, $numbers): bool
91 {
92 return \imap_setflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID);
93 }
94
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010095 public function clearFlag(string $flag, $numbers): bool
96 {
97 return \imap_clearflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID);
98 }
99
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100100 public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface
101 {
102 if (null === $search) {
103 $search = new All();
104 }
105 $query = $search->toString();
106
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100107 if (\PHP_VERSION_ID < 80000) {
108 $descending = (int) $descending;
109 }
110
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100111 // We need to clear the stack to know whether imap_last_error()
112 // is related to this imap_search
113 \imap_errors();
114
115 if (null !== $sortCriteria) {
116 $params = [
117 $this->resource->getStream(),
118 $sortCriteria,
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100119 $descending,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100120 \SE_UID,
121 $query,
122 ];
123 if (null !== $charset) {
124 $params[] = $charset;
125 }
126 $messageNumbers = \imap_sort(...$params);
127 } else {
128 $params = [
129 $this->resource->getStream(),
130 $query,
131 \SE_UID,
132 ];
133 if (null !== $charset) {
134 $params[] = $charset;
135 }
136 $messageNumbers = \imap_search(...$params);
137 }
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100138 if (false !== \imap_last_error()) {
139 // this way all errors occurred during search will be reported
140 throw new InvalidSearchCriteriaException(
141 \sprintf('Invalid search criteria [%s]', $query)
142 );
143 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100144 if (false === $messageNumbers) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100145 // imap_search can also return false
146 $messageNumbers = [];
147 }
148
149 return new MessageIterator($this->resource, $messageNumbers);
150 }
151
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100152 public function getMessageSequence(string $sequence): MessageIteratorInterface
153 {
154 \imap_errors();
155
156 $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, \FT_UID);
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100157 if (false !== \imap_last_error()) {
158 throw new InvalidSearchCriteriaException(
159 \sprintf('Invalid sequence [%s]', $sequence)
160 );
161 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100162 if (\is_array($overview) && [] !== $overview) {
163 $messageNumbers = \array_column($overview, 'uid');
164 } else {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100165 $messageNumbers = [];
166 }
167
168 return new MessageIterator($this->resource, $messageNumbers);
169 }
170
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100171 public function getMessage(int $number): MessageInterface
172 {
173 return new Message($this->resource, $number);
174 }
175
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100176 public function getIterator(): MessageIteratorInterface
177 {
178 return $this->getMessages();
179 }
180
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100181 public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool
182 {
183 $arguments = [
184 $this->resource->getStream(),
185 $this->getFullEncodedName(),
186 $message,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200187 $options ?? '',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100188 ];
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200189 if (null !== $internalDate) {
190 $arguments[] = $internalDate->format('d-M-Y H:i:s O');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100191 }
192
193 return \imap_append(...$arguments);
194 }
195
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100196 public function getThread(): array
197 {
198 \set_error_handler(static function (): bool {
199 return true;
200 });
201
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200202 /** @var array<string, int>|false $tree */
Matthias Andreas Benkarde39c4f82021-01-06 17:59:39 +0100203 $tree = \imap_thread($this->resource->getStream(), \SE_UID);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100204
205 \restore_error_handler();
206
207 return false !== $tree ? $tree : [];
208 }
209
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100210 public function move($numbers, MailboxInterface $mailbox): void
211 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200212 if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID | \CP_MOVE)) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100213 throw new MessageMoveException(\sprintf('Messages cannot be moved to "%s"', $mailbox->getName()));
214 }
215 }
216
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100217 public function copy($numbers, MailboxInterface $mailbox): void
218 {
219 if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) {
220 throw new MessageCopyException(\sprintf('Messages cannot be copied to "%s"', $mailbox->getName()));
221 }
222 }
223
224 /**
225 * Prepare message ids for the use with bulk functions.
226 *
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200227 * @param array<int, int|string>|MessageIterator|string $messageIds Message numbers
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100228 */
229 private function prepareMessageIds($messageIds): string
230 {
231 if ($messageIds instanceof MessageIterator) {
232 $messageIds = $messageIds->getArrayCopy();
233 }
234
235 if (\is_array($messageIds)) {
236 $messageIds = \implode(',', $messageIds);
237 }
238
239 return $messageIds;
240 }
241}