Folder.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. <?php
  2. /*
  3. * File: Folder.php
  4. * Category: -
  5. * Author: M. Goldenbaum
  6. * Created: 19.01.17 22:21
  7. * Updated: -
  8. *
  9. * Description:
  10. * -
  11. */
  12. namespace Webklex\PHPIMAP;
  13. use Carbon\Carbon;
  14. use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
  15. use Webklex\PHPIMAP\Query\WhereQuery;
  16. use Webklex\PHPIMAP\Support\FolderCollection;
  17. use Webklex\PHPIMAP\Traits\HasEvents;
  18. /**
  19. * Class Folder
  20. *
  21. * @package Webklex\PHPIMAP
  22. */
  23. class Folder {
  24. use HasEvents;
  25. /**
  26. * Client instance
  27. *
  28. * @var Client
  29. */
  30. protected $client;
  31. /**
  32. * Folder full path
  33. *
  34. * @var string
  35. */
  36. public $path;
  37. /**
  38. * Folder name
  39. *
  40. * @var string
  41. */
  42. public $name;
  43. /**
  44. * Folder fullname
  45. *
  46. * @var string
  47. */
  48. public $full_name;
  49. /**
  50. * Children folders
  51. *
  52. * @var FolderCollection|array
  53. */
  54. public $children = [];
  55. /**
  56. * Delimiter for folder
  57. *
  58. * @var string
  59. */
  60. public $delimiter;
  61. /**
  62. * Indicates if folder can't containg any "children".
  63. * CreateFolder won't work on this folder.
  64. *
  65. * @var boolean
  66. */
  67. public $no_inferiors;
  68. /**
  69. * Indicates if folder is only container, not a mailbox - you can't open it.
  70. *
  71. * @var boolean
  72. */
  73. public $no_select;
  74. /**
  75. * Indicates if folder is marked. This means that it may contain new messages since the last time it was checked.
  76. * Not provided by all IMAP servers.
  77. *
  78. * @var boolean
  79. */
  80. public $marked;
  81. /**
  82. * Indicates if folder containg any "children".
  83. * Not provided by all IMAP servers.
  84. *
  85. * @var boolean
  86. */
  87. public $has_children;
  88. /**
  89. * Indicates if folder refers to other.
  90. * Not provided by all IMAP servers.
  91. *
  92. * @var boolean
  93. */
  94. public $referral;
  95. /**
  96. * Folder constructor.
  97. * @param Client $client
  98. * @param string $folder_name
  99. * @param string $delimiter
  100. * @param string[] $attributes
  101. */
  102. public function __construct(Client $client, $folder_name, $delimiter, $attributes) {
  103. $this->client = $client;
  104. $this->events["message"] = $client->getDefaultEvents("message");
  105. $this->events["folder"] = $client->getDefaultEvents("folder");
  106. $this->setDelimiter($delimiter);
  107. $this->path = $folder_name;
  108. $this->full_name = $this->decodeName($folder_name);
  109. $this->name = $this->getSimpleName($this->delimiter, $this->full_name);
  110. $this->parseAttributes($attributes);
  111. }
  112. /**
  113. * Get a new search query instance
  114. * @param string $charset
  115. *
  116. * @return WhereQuery
  117. * @throws Exceptions\ConnectionFailedException
  118. * @throws Exceptions\RuntimeException
  119. */
  120. public function query($charset = 'UTF-8'){
  121. $this->getClient()->checkConnection();
  122. $this->getClient()->openFolder($this->path);
  123. return new WhereQuery($this->getClient(), $charset);
  124. }
  125. /**
  126. * @inheritdoc self::query($charset = 'UTF-8')
  127. * @throws Exceptions\ConnectionFailedException
  128. * @throws Exceptions\RuntimeException
  129. */
  130. public function search($charset = 'UTF-8'){
  131. return $this->query($charset);
  132. }
  133. /**
  134. * @inheritdoc self::query($charset = 'UTF-8')
  135. * @throws Exceptions\ConnectionFailedException
  136. * @throws Exceptions\RuntimeException
  137. */
  138. public function messages($charset = 'UTF-8'){
  139. return $this->query($charset);
  140. }
  141. /**
  142. * Determine if folder has children.
  143. *
  144. * @return bool
  145. */
  146. public function hasChildren() {
  147. return $this->has_children;
  148. }
  149. /**
  150. * Set children.
  151. * @param FolderCollection|array $children
  152. *
  153. * @return self
  154. */
  155. public function setChildren($children = []) {
  156. $this->children = $children;
  157. return $this;
  158. }
  159. /**
  160. * Decode name.
  161. * It converts UTF7-IMAP encoding to UTF-8.
  162. * @param $name
  163. *
  164. * @return mixed|string
  165. */
  166. protected function decodeName($name) {
  167. return mb_convert_encoding($name, "UTF-8", "UTF7-IMAP");
  168. }
  169. /**
  170. * Get simple name (without parent folders).
  171. * @param $delimiter
  172. * @param $full_name
  173. *
  174. * @return mixed
  175. */
  176. protected function getSimpleName($delimiter, $full_name) {
  177. $arr = explode($delimiter, $full_name);
  178. return end($arr);
  179. }
  180. /**
  181. * Parse attributes and set it to object properties.
  182. * @param $attributes
  183. */
  184. protected function parseAttributes($attributes) {
  185. $this->no_inferiors = in_array('\NoInferiors', $attributes) ? true : false;
  186. $this->no_select = in_array('\NoSelect', $attributes) ? true : false;
  187. $this->marked = in_array('\Marked', $attributes) ? true : false;
  188. $this->referral = in_array('\Referral', $attributes) ? true : false;
  189. $this->has_children = in_array('\HasChildren', $attributes) ? true : false;
  190. }
  191. /**
  192. * Move or rename the current folder
  193. * @param string $new_name
  194. * @param boolean $expunge
  195. *
  196. * @return bool
  197. * @throws ConnectionFailedException
  198. * @throws Exceptions\EventNotFoundException
  199. * @throws Exceptions\FolderFetchingException
  200. * @throws Exceptions\RuntimeException
  201. */
  202. public function move($new_name, $expunge = true) {
  203. $this->client->checkConnection();
  204. $status = $this->client->getConnection()->renameFolder($this->full_name, $new_name);
  205. if($expunge) $this->client->expunge();
  206. $folder = $this->client->getFolder($new_name);
  207. $event = $this->getEvent("folder", "moved");
  208. $event::dispatch($this, $folder);
  209. return $status;
  210. }
  211. /**
  212. * Get a message overview
  213. * @param string|null $sequence uid sequence
  214. *
  215. * @return array
  216. * @throws ConnectionFailedException
  217. * @throws Exceptions\InvalidMessageDateException
  218. * @throws Exceptions\MessageNotFoundException
  219. * @throws Exceptions\RuntimeException
  220. */
  221. public function overview($sequence = null){
  222. $this->client->openFolder($this->path);
  223. $sequence = $sequence === null ? "1:*" : $sequence;
  224. $uid = ClientManager::get('options.sequence', IMAP::ST_MSGN) == IMAP::ST_UID;
  225. return $this->client->getConnection()->overview($sequence, $uid);
  226. }
  227. /**
  228. * Append a string message to the current mailbox
  229. * @param string $message
  230. * @param string $options
  231. * @param string $internal_date
  232. *
  233. * @return bool
  234. * @throws Exceptions\ConnectionFailedException
  235. * @throws Exceptions\RuntimeException
  236. */
  237. public function appendMessage($message, $options = null, $internal_date = null) {
  238. /**
  239. * Check if $internal_date is parsed. If it is null it should not be set. Otherwise the message can't be stored.
  240. * If this parameter is set, it will set the INTERNALDATE on the appended message. The parameter should be a
  241. * date string that conforms to the rfc2060 specifications for a date_time value or be a Carbon object.
  242. */
  243. if ($internal_date != null) {
  244. if ($internal_date instanceof Carbon){
  245. $internal_date = $internal_date->format('d-M-Y H:i:s O');
  246. }
  247. }
  248. return $this->client->getConnection()->appendMessage($this->full_name, $message, $options, $internal_date);
  249. }
  250. /**
  251. * Rename the current folder
  252. * @param string $new_name
  253. * @param boolean $expunge
  254. *
  255. * @return bool
  256. * @throws ConnectionFailedException
  257. * @throws Exceptions\EventNotFoundException
  258. * @throws Exceptions\FolderFetchingException
  259. * @throws Exceptions\RuntimeException
  260. */
  261. public function rename($new_name, $expunge = true) {
  262. return $this->move($new_name, $expunge);
  263. }
  264. /**
  265. * Delete the current folder
  266. * @param boolean $expunge
  267. *
  268. * @return bool
  269. * @throws Exceptions\ConnectionFailedException
  270. * @throws Exceptions\RuntimeException
  271. * @throws Exceptions\EventNotFoundException
  272. */
  273. public function delete($expunge = true) {
  274. $status = $this->client->getConnection()->deleteFolder($this->path);
  275. if($expunge) $this->client->expunge();
  276. $event = $this->getEvent("folder", "deleted");
  277. $event::dispatch($this);
  278. return $status;
  279. }
  280. /**
  281. * Subscribe the current folder
  282. *
  283. * @return bool
  284. * @throws Exceptions\ConnectionFailedException
  285. * @throws Exceptions\RuntimeException
  286. */
  287. public function subscribe() {
  288. $this->client->openFolder($this->path);
  289. return $this->client->getConnection()->subscribeFolder($this->path);
  290. }
  291. /**
  292. * Unsubscribe the current folder
  293. *
  294. * @return bool
  295. * @throws Exceptions\ConnectionFailedException
  296. * @throws Exceptions\RuntimeException
  297. */
  298. public function unsubscribe() {
  299. $this->client->openFolder($this->path);
  300. return $this->client->getConnection()->unsubscribeFolder($this->path);
  301. }
  302. /**
  303. * Idle the current connection
  304. * @param callable $callback
  305. * @param integer $timeout max 1740 seconds - recommended by rfc2177 §3
  306. * @param boolean $auto_reconnect try to reconnect on connection close
  307. *
  308. * @throws ConnectionFailedException
  309. * @throws Exceptions\InvalidMessageDateException
  310. * @throws Exceptions\MessageContentFetchingException
  311. * @throws Exceptions\MessageHeaderFetchingException
  312. * @throws Exceptions\RuntimeException
  313. * @throws Exceptions\EventNotFoundException
  314. * @throws Exceptions\MessageFlagException
  315. * @throws Exceptions\MessageNotFoundException
  316. */
  317. public function idle(callable $callback, $timeout = 1200, $auto_reconnect = false) {
  318. $this->client->getConnection()->setConnectionTimeout($timeout);
  319. $this->client->reconnect();
  320. $this->client->openFolder($this->path, true);
  321. $connection = $this->client->getConnection();
  322. $sequence = ClientManager::get('options.sequence', IMAP::ST_MSGN);
  323. $connection->idle();
  324. while (true) {
  325. try {
  326. $line = $connection->nextLine();
  327. if (($pos = strpos($line, "EXISTS")) !== false) {
  328. $msgn = (int) substr($line, 2, $pos -2);
  329. $connection->done();
  330. $this->client->openFolder($this->path, true);
  331. $message = $this->query()->getMessageByMsgn($msgn);
  332. $message->setSequence($sequence);
  333. $callback($message);
  334. $event = $this->getEvent("message", "new");
  335. $event::dispatch($message);
  336. $connection->idle();
  337. }
  338. }catch (Exceptions\RuntimeException $e) {
  339. if(strpos($e->getMessage(), "connection closed") === false) {
  340. throw $e;
  341. }
  342. if ($auto_reconnect === true) {
  343. $this->client->reconnect();
  344. $this->client->openFolder($this->path, true);
  345. $connection = $this->client->getConnection();
  346. $connection->idle();
  347. }
  348. }
  349. }
  350. }
  351. /**
  352. * Get folder status information
  353. *
  354. * @return array|bool
  355. * @throws Exceptions\ConnectionFailedException
  356. * @throws Exceptions\RuntimeException
  357. */
  358. public function getStatus() {
  359. return $this->examine();
  360. }
  361. /**
  362. * Examine the current folder
  363. *
  364. * @return array
  365. * @throws Exceptions\ConnectionFailedException
  366. * @throws Exceptions\RuntimeException
  367. */
  368. public function examine() {
  369. return $this->client->getConnection()->examineFolder($this->path);
  370. }
  371. /**
  372. * Get the current Client instance
  373. *
  374. * @return Client
  375. */
  376. public function getClient() {
  377. return $this->client;
  378. }
  379. /**
  380. * Set the delimiter
  381. * @param $delimiter
  382. */
  383. public function setDelimiter($delimiter){
  384. if(in_array($delimiter, [null, '', ' ', false]) === true) {
  385. $delimiter = ClientManager::get('options.delimiter', '/');
  386. }
  387. $this->delimiter = $delimiter;
  388. }
  389. }