/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php

https://github.com/mailcow/mailcow-dockerized · PHP · 321 lines · 174 code · 43 blank · 104 comment · 19 complexity · 595a750e24be4e36587c6269a11f5d48 MD5 · raw file

  1. <?php
  2. declare(strict_types=1);
  3. namespace Ddeboer\Imap;
  4. use DateTimeInterface;
  5. use Ddeboer\Imap\Exception\ImapNumMsgException;
  6. use Ddeboer\Imap\Exception\ImapStatusException;
  7. use Ddeboer\Imap\Exception\InvalidSearchCriteriaException;
  8. use Ddeboer\Imap\Exception\MessageCopyException;
  9. use Ddeboer\Imap\Exception\MessageMoveException;
  10. use Ddeboer\Imap\Search\ConditionInterface;
  11. use Ddeboer\Imap\Search\LogicalOperator\All;
  12. /**
  13. * An IMAP mailbox (commonly referred to as a 'folder').
  14. */
  15. final class Mailbox implements MailboxInterface
  16. {
  17. /**
  18. * @var ImapResourceInterface
  19. */
  20. private $resource;
  21. /**
  22. * @var string
  23. */
  24. private $name;
  25. /**
  26. * @var \stdClass
  27. */
  28. private $info;
  29. /**
  30. * Constructor.
  31. *
  32. * @param ImapResourceInterface $resource IMAP resource
  33. * @param string $name Mailbox decoded name
  34. * @param \stdClass $info Mailbox info
  35. */
  36. public function __construct(ImapResourceInterface $resource, string $name, \stdClass $info)
  37. {
  38. $this->resource = new ImapResource($resource->getStream(), $this);
  39. $this->name = $name;
  40. $this->info = $info;
  41. }
  42. /**
  43. * Get mailbox decoded name.
  44. */
  45. public function getName(): string
  46. {
  47. return $this->name;
  48. }
  49. /**
  50. * Get mailbox encoded path.
  51. */
  52. public function getEncodedName(): string
  53. {
  54. /** @var string $name */
  55. $name = $this->info->name;
  56. return (string) \preg_replace('/^{.+}/', '', $name);
  57. }
  58. /**
  59. * Get mailbox encoded full name.
  60. */
  61. public function getFullEncodedName(): string
  62. {
  63. return $this->info->name;
  64. }
  65. /**
  66. * Get mailbox attributes.
  67. */
  68. public function getAttributes(): int
  69. {
  70. return $this->info->attributes;
  71. }
  72. /**
  73. * Get mailbox delimiter.
  74. */
  75. public function getDelimiter(): string
  76. {
  77. return $this->info->delimiter;
  78. }
  79. /**
  80. * Get number of messages in this mailbox.
  81. *
  82. * @return int
  83. */
  84. public function count()
  85. {
  86. $return = \imap_num_msg($this->resource->getStream());
  87. if (false === $return) {
  88. throw new ImapNumMsgException('imap_num_msg failed');
  89. }
  90. return $return;
  91. }
  92. /**
  93. * Get Mailbox status.
  94. */
  95. public function getStatus(int $flags = null): \stdClass
  96. {
  97. $return = \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL);
  98. if (false === $return) {
  99. throw new ImapStatusException('imap_status failed');
  100. }
  101. return $return;
  102. }
  103. /**
  104. * Bulk Set Flag for Messages.
  105. *
  106. * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft
  107. * @param array|MessageIterator|string $numbers Message numbers
  108. */
  109. public function setFlag(string $flag, $numbers): bool
  110. {
  111. return \imap_setflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID);
  112. }
  113. /**
  114. * Bulk Clear Flag for Messages.
  115. *
  116. * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft
  117. * @param array|MessageIterator|string $numbers Message numbers
  118. */
  119. public function clearFlag(string $flag, $numbers): bool
  120. {
  121. return \imap_clearflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID);
  122. }
  123. /**
  124. * Get message ids.
  125. *
  126. * @param ConditionInterface $search Search expression (optional)
  127. */
  128. public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface
  129. {
  130. if (null === $search) {
  131. $search = new All();
  132. }
  133. $query = $search->toString();
  134. // We need to clear the stack to know whether imap_last_error()
  135. // is related to this imap_search
  136. \imap_errors();
  137. if (null !== $sortCriteria) {
  138. $params = [
  139. $this->resource->getStream(),
  140. $sortCriteria,
  141. $descending ? 1 : 0,
  142. \SE_UID,
  143. $query,
  144. ];
  145. if (null !== $charset) {
  146. $params[] = $charset;
  147. }
  148. $messageNumbers = \imap_sort(...$params);
  149. } else {
  150. $params = [
  151. $this->resource->getStream(),
  152. $query,
  153. \SE_UID,
  154. ];
  155. if (null !== $charset) {
  156. $params[] = $charset;
  157. }
  158. $messageNumbers = \imap_search(...$params);
  159. }
  160. if (false === $messageNumbers) {
  161. if (false !== \imap_last_error()) {
  162. throw new InvalidSearchCriteriaException(\sprintf('Invalid search criteria [%s]', $query));
  163. }
  164. // imap_search can also return false
  165. $messageNumbers = [];
  166. }
  167. return new MessageIterator($this->resource, $messageNumbers);
  168. }
  169. /**
  170. * Get message iterator for a sequence.
  171. *
  172. * @param string $sequence Message numbers
  173. */
  174. public function getMessageSequence(string $sequence): MessageIteratorInterface
  175. {
  176. \imap_errors();
  177. $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, \FT_UID);
  178. if (\is_array($overview) && [] !== $overview) {
  179. $messageNumbers = \array_column($overview, 'uid');
  180. } else {
  181. if (false !== \imap_last_error()) {
  182. throw new InvalidSearchCriteriaException(\sprintf('Invalid sequence [%s]', $sequence));
  183. }
  184. $messageNumbers = [];
  185. }
  186. return new MessageIterator($this->resource, $messageNumbers);
  187. }
  188. /**
  189. * Get a message by message number.
  190. *
  191. * @param int $number Message number
  192. */
  193. public function getMessage(int $number): MessageInterface
  194. {
  195. return new Message($this->resource, $number);
  196. }
  197. /**
  198. * Get messages in this mailbox.
  199. */
  200. public function getIterator(): MessageIteratorInterface
  201. {
  202. return $this->getMessages();
  203. }
  204. /**
  205. * Add a message to the mailbox.
  206. */
  207. public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool
  208. {
  209. $arguments = [
  210. $this->resource->getStream(),
  211. $this->getFullEncodedName(),
  212. $message,
  213. ];
  214. if (null !== $options) {
  215. $arguments[] = $options;
  216. if (null !== $internalDate) {
  217. $arguments[] = $internalDate->format('d-M-Y H:i:s O');
  218. }
  219. }
  220. return \imap_append(...$arguments);
  221. }
  222. /**
  223. * Returns a tree of threaded message for the current Mailbox.
  224. */
  225. public function getThread(): array
  226. {
  227. \set_error_handler(static function (): bool {
  228. return true;
  229. });
  230. /** @var array|false $tree */
  231. $tree = \imap_thread($this->resource->getStream());
  232. \restore_error_handler();
  233. return false !== $tree ? $tree : [];
  234. }
  235. /**
  236. * Bulk move messages.
  237. *
  238. * @param array|MessageIterator|string $numbers Message numbers
  239. * @param MailboxInterface $mailbox Destination Mailbox to move the messages to
  240. *
  241. * @throws \Ddeboer\Imap\Exception\MessageMoveException
  242. */
  243. public function move($numbers, MailboxInterface $mailbox): void
  244. {
  245. if (!\imap_mail_move($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) {
  246. throw new MessageMoveException(\sprintf('Messages cannot be moved to "%s"', $mailbox->getName()));
  247. }
  248. }
  249. /**
  250. * Bulk copy messages.
  251. *
  252. * @param array|MessageIterator|string $numbers Message numbers
  253. * @param MailboxInterface $mailbox Destination Mailbox to copy the messages to
  254. *
  255. * @throws \Ddeboer\Imap\Exception\MessageCopyException
  256. */
  257. public function copy($numbers, MailboxInterface $mailbox): void
  258. {
  259. if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) {
  260. throw new MessageCopyException(\sprintf('Messages cannot be copied to "%s"', $mailbox->getName()));
  261. }
  262. }
  263. /**
  264. * Prepare message ids for the use with bulk functions.
  265. *
  266. * @param array|MessageIterator|string $messageIds Message numbers
  267. */
  268. private function prepareMessageIds($messageIds): string
  269. {
  270. if ($messageIds instanceof MessageIterator) {
  271. $messageIds = $messageIds->getArrayCopy();
  272. }
  273. if (\is_array($messageIds)) {
  274. $messageIds = \implode(',', $messageIds);
  275. }
  276. return $messageIds;
  277. }
  278. }