Imap.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Mail
  17. * @subpackage Storage
  18. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Mail_Storage_Abstract
  24. */
  25. require_once 'Zend/Mail/Storage/Abstract.php';
  26. /**
  27. * @see Zend_Mail_Protocol_Imap
  28. */
  29. require_once 'Zend/Mail/Protocol/Imap.php';
  30. /**
  31. * @see Zend_Mail_Storage_Writable_Interface
  32. */
  33. require_once 'Zend/Mail/Storage/Writable/Interface.php';
  34. /**
  35. * @see Zend_Mail_Storage_Folder_Interface
  36. */
  37. require_once 'Zend/Mail/Storage/Folder/Interface.php';
  38. /**
  39. * @see Zend_Mail_Storage_Folder
  40. */
  41. require_once 'Zend/Mail/Storage/Folder.php';
  42. /**
  43. * @see Zend_Mail_Message
  44. */
  45. require_once 'Zend/Mail/Message.php';
  46. /**
  47. * @see Zend_Mail_Storage
  48. */
  49. require_once 'Zend/Mail/Storage.php';
  50. /**
  51. * @category Zend
  52. * @package Zend_Mail
  53. * @subpackage Storage
  54. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  55. * @license http://framework.zend.com/license/new-bsd New BSD License
  56. */
  57. class Zend_Mail_Storage_Imap extends Zend_Mail_Storage_Abstract
  58. implements Zend_Mail_Storage_Folder_Interface, Zend_Mail_Storage_Writable_Interface
  59. {
  60. // TODO: with an internal cache we could optimize this class, or create an extra class with
  61. // such optimizations. Especially the various fetch calls could be combined to one cache call
  62. /**
  63. * protocol handler
  64. * @var null|Zend_Mail_Protocol_Imap
  65. */
  66. protected $_protocol;
  67. /**
  68. * name of current folder
  69. * @var string
  70. */
  71. protected $_currentFolder = '';
  72. /**
  73. * imap flags to constants translation
  74. * @var array
  75. */
  76. protected static $_knownFlags = array('\Passed' => Zend_Mail_Storage::FLAG_PASSED,
  77. '\Answered' => Zend_Mail_Storage::FLAG_ANSWERED,
  78. '\Seen' => Zend_Mail_Storage::FLAG_SEEN,
  79. '\Unseen' => Zend_Mail_Storage::FLAG_UNSEEN,
  80. '\Deleted' => Zend_Mail_Storage::FLAG_DELETED,
  81. '\Draft' => Zend_Mail_Storage::FLAG_DRAFT,
  82. '\Flagged' => Zend_Mail_Storage::FLAG_FLAGGED);
  83. /**
  84. * map flags to search criterias
  85. * @var array
  86. */
  87. protected static $_searchFlags = array('\Recent' => 'RECENT',
  88. '\Answered' => 'ANSWERED',
  89. '\Seen' => 'SEEN',
  90. '\Unseen' => 'UNSEEN',
  91. '\Deleted' => 'DELETED',
  92. '\Draft' => 'DRAFT',
  93. '\Flagged' => 'FLAGGED');
  94. /**
  95. * Count messages all messages in current box
  96. *
  97. * @return int number of messages
  98. * @throws Zend_Mail_Storage_Exception
  99. * @throws Zend_Mail_Protocol_Exception
  100. */
  101. public function countMessages($flags = null)
  102. {
  103. if (!$this->_currentFolder) {
  104. /**
  105. * @see Zend_Mail_Storage_Exception
  106. */
  107. require_once 'Zend/Mail/Storage/Exception.php';
  108. throw new Zend_Mail_Storage_Exception('No selected folder to count');
  109. }
  110. if ($flags === null) {
  111. return count($this->_protocol->search(array('ALL')));
  112. }
  113. $params = array();
  114. foreach ((array)$flags as $flag) {
  115. if (isset(self::$_searchFlags[$flag])) {
  116. $params[] = self::$_searchFlags[$flag];
  117. } else {
  118. $params[] = 'KEYWORD';
  119. $params[] = $this->_protocol->escapeString($flag);
  120. }
  121. }
  122. return count($this->_protocol->search($params));
  123. }
  124. /**
  125. * get a list of messages with number and size
  126. *
  127. * @param int $id number of message
  128. * @return int|array size of given message of list with all messages as array(num => size)
  129. * @throws Zend_Mail_Protocol_Exception
  130. */
  131. public function getSize($id = 0)
  132. {
  133. if ($id) {
  134. return $this->_protocol->fetch('RFC822.SIZE', $id);
  135. }
  136. return $this->_protocol->fetch('RFC822.SIZE', 1, INF);
  137. }
  138. /**
  139. * Fetch a message
  140. *
  141. * @param int $id number of message
  142. * @return Zend_Mail_Message
  143. * @throws Zend_Mail_Protocol_Exception
  144. */
  145. public function getMessage($id)
  146. {
  147. $data = $this->_protocol->fetch(array('FLAGS', 'RFC822.HEADER'), $id);
  148. $header = $data['RFC822.HEADER'];
  149. $flags = array();
  150. foreach ($data['FLAGS'] as $flag) {
  151. $flags[] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag;
  152. }
  153. return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags));
  154. }
  155. /*
  156. * Get raw header of message or part
  157. *
  158. * @param int $id number of message
  159. * @param null|array|string $part path to part or null for messsage header
  160. * @param int $topLines include this many lines with header (after an empty line)
  161. * @param int $topLines include this many lines with header (after an empty line)
  162. * @return string raw header
  163. * @throws Zend_Mail_Protocol_Exception
  164. * @throws Zend_Mail_Storage_Exception
  165. */
  166. public function getRawHeader($id, $part = null, $topLines = 0)
  167. {
  168. if ($part !== null) {
  169. // TODO: implement
  170. /**
  171. * @see Zend_Mail_Storage_Exception
  172. */
  173. require_once 'Zend/Mail/Storage/Exception.php';
  174. throw new Zend_Mail_Storage_Exception('not implemented');
  175. }
  176. // TODO: toplines
  177. return $this->_protocol->fetch('RFC822.HEADER', $id);
  178. }
  179. /*
  180. * Get raw content of message or part
  181. *
  182. * @param int $id number of message
  183. * @param null|array|string $part path to part or null for messsage content
  184. * @return string raw content
  185. * @throws Zend_Mail_Protocol_Exception
  186. * @throws Zend_Mail_Storage_Exception
  187. */
  188. public function getRawContent($id, $part = null)
  189. {
  190. if ($part !== null) {
  191. // TODO: implement
  192. /**
  193. * @see Zend_Mail_Storage_Exception
  194. */
  195. require_once 'Zend/Mail/Storage/Exception.php';
  196. throw new Zend_Mail_Storage_Exception('not implemented');
  197. }
  198. return $this->_protocol->fetch('RFC822.TEXT', $id);
  199. }
  200. /**
  201. * create instance with parameters
  202. * Supported paramters are
  203. * - user username
  204. * - host hostname or ip address of IMAP server [optional, default = 'localhost']
  205. * - password password for user 'username' [optional, default = '']
  206. * - port port for IMAP server [optional, default = 110]
  207. * - ssl 'SSL' or 'TLS' for secure sockets
  208. * - folder select this folder [optional, default = 'INBOX']
  209. *
  210. * @param array $params mail reader specific parameters
  211. * @throws Zend_Mail_Storage_Exception
  212. * @throws Zend_Mail_Protocol_Exception
  213. */
  214. public function __construct($params)
  215. {
  216. if (is_array($params)) {
  217. $params = (object)$params;
  218. }
  219. $this->_has['flags'] = true;
  220. if ($params instanceof Zend_Mail_Protocol_Imap) {
  221. $this->_protocol = $params;
  222. try {
  223. $this->selectFolder('INBOX');
  224. } catch(Zend_Mail_Storage_Exception $e) {
  225. /**
  226. * @see Zend_Mail_Storage_Exception
  227. */
  228. require_once 'Zend/Mail/Storage/Exception.php';
  229. throw new Zend_Mail_Storage_Exception('cannot select INBOX, is this a valid transport?', 0, $e);
  230. }
  231. return;
  232. }
  233. if (!isset($params->user)) {
  234. /**
  235. * @see Zend_Mail_Storage_Exception
  236. */
  237. require_once 'Zend/Mail/Storage/Exception.php';
  238. throw new Zend_Mail_Storage_Exception('need at least user in params');
  239. }
  240. $host = isset($params->host) ? $params->host : 'localhost';
  241. $password = isset($params->password) ? $params->password : '';
  242. $port = isset($params->port) ? $params->port : null;
  243. $ssl = isset($params->ssl) ? $params->ssl : false;
  244. $this->_protocol = new Zend_Mail_Protocol_Imap();
  245. $this->_protocol->connect($host, $port, $ssl);
  246. if (!$this->_protocol->login($params->user, $password)) {
  247. /**
  248. * @see Zend_Mail_Storage_Exception
  249. */
  250. require_once 'Zend/Mail/Storage/Exception.php';
  251. throw new Zend_Mail_Storage_Exception('cannot login, user or password wrong');
  252. }
  253. $this->selectFolder(isset($params->folder) ? $params->folder : 'INBOX');
  254. }
  255. /**
  256. * Close resource for mail lib. If you need to control, when the resource
  257. * is closed. Otherwise the destructor would call this.
  258. *
  259. * @return null
  260. */
  261. public function close()
  262. {
  263. $this->_currentFolder = '';
  264. $this->_protocol->logout();
  265. }
  266. /**
  267. * Keep the server busy.
  268. *
  269. * @return null
  270. * @throws Zend_Mail_Storage_Exception
  271. */
  272. public function noop()
  273. {
  274. if (!$this->_protocol->noop()) {
  275. /**
  276. * @see Zend_Mail_Storage_Exception
  277. */
  278. require_once 'Zend/Mail/Storage/Exception.php';
  279. throw new Zend_Mail_Storage_Exception('could not do nothing');
  280. }
  281. }
  282. /**
  283. * Remove a message from server. If you're doing that from a web enviroment
  284. * you should be careful and use a uniqueid as parameter if possible to
  285. * identify the message.
  286. *
  287. * @param int $id number of message
  288. * @return null
  289. * @throws Zend_Mail_Storage_Exception
  290. */
  291. public function removeMessage($id)
  292. {
  293. if (!$this->_protocol->store(array(Zend_Mail_Storage::FLAG_DELETED), $id, null, '+')) {
  294. /**
  295. * @see Zend_Mail_Storage_Exception
  296. */
  297. require_once 'Zend/Mail/Storage/Exception.php';
  298. throw new Zend_Mail_Storage_Exception('cannot set deleted flag');
  299. }
  300. // TODO: expunge here or at close? we can handle an error here better and are more fail safe
  301. if (!$this->_protocol->expunge()) {
  302. /**
  303. * @see Zend_Mail_Storage_Exception
  304. */
  305. require_once 'Zend/Mail/Storage/Exception.php';
  306. throw new Zend_Mail_Storage_Exception('message marked as deleted, but could not expunge');
  307. }
  308. }
  309. /**
  310. * get unique id for one or all messages
  311. *
  312. * if storage does not support unique ids it's the same as the message number
  313. *
  314. * @param int|null $id message number
  315. * @return array|string message number for given message or all messages as array
  316. * @throws Zend_Mail_Storage_Exception
  317. */
  318. public function getUniqueId($id = null)
  319. {
  320. if ($id) {
  321. return $this->_protocol->fetch('UID', $id);
  322. }
  323. return $this->_protocol->fetch('UID', 1, INF);
  324. }
  325. /**
  326. * get a message number from a unique id
  327. *
  328. * I.e. if you have a webmailer that supports deleting messages you should use unique ids
  329. * as parameter and use this method to translate it to message number right before calling removeMessage()
  330. *
  331. * @param string $id unique id
  332. * @return int message number
  333. * @throws Zend_Mail_Storage_Exception
  334. */
  335. public function getNumberByUniqueId($id)
  336. {
  337. // TODO: use search to find number directly
  338. $ids = $this->getUniqueId();
  339. foreach ($ids as $k => $v) {
  340. if ($v == $id) {
  341. return $k;
  342. }
  343. }
  344. /**
  345. * @see Zend_Mail_Storage_Exception
  346. */
  347. require_once 'Zend/Mail/Storage/Exception.php';
  348. throw new Zend_Mail_Storage_Exception('unique id not found');
  349. }
  350. /**
  351. * get root folder or given folder
  352. *
  353. * @param string $rootFolder get folder structure for given folder, else root
  354. * @return Zend_Mail_Storage_Folder root or wanted folder
  355. * @throws Zend_Mail_Storage_Exception
  356. * @throws Zend_Mail_Protocol_Exception
  357. */
  358. public function getFolders($rootFolder = null)
  359. {
  360. $folders = $this->_protocol->listMailbox((string)$rootFolder);
  361. if (!$folders) {
  362. /**
  363. * @see Zend_Mail_Storage_Exception
  364. */
  365. require_once 'Zend/Mail/Storage/Exception.php';
  366. throw new Zend_Mail_Storage_Exception('folder not found');
  367. }
  368. ksort($folders, SORT_STRING);
  369. $root = new Zend_Mail_Storage_Folder('/', '/', false);
  370. $stack = array(null);
  371. $folderStack = array(null);
  372. $parentFolder = $root;
  373. $parent = '';
  374. foreach ($folders as $globalName => $data) {
  375. do {
  376. if (!$parent || strpos($globalName, $parent) === 0) {
  377. $pos = strrpos($globalName, $data['delim']);
  378. if ($pos === false) {
  379. $localName = $globalName;
  380. } else {
  381. $localName = substr($globalName, $pos + 1);
  382. }
  383. $selectable = !$data['flags'] || !in_array('\\Noselect', $data['flags']);
  384. array_push($stack, $parent);
  385. $parent = $globalName . $data['delim'];
  386. $folder = new Zend_Mail_Storage_Folder($localName, $globalName, $selectable);
  387. $parentFolder->$localName = $folder;
  388. array_push($folderStack, $parentFolder);
  389. $parentFolder = $folder;
  390. break;
  391. } else if ($stack) {
  392. $parent = array_pop($stack);
  393. $parentFolder = array_pop($folderStack);
  394. }
  395. } while ($stack);
  396. if (!$stack) {
  397. /**
  398. * @see Zend_Mail_Storage_Exception
  399. */
  400. require_once 'Zend/Mail/Storage/Exception.php';
  401. throw new Zend_Mail_Storage_Exception('error while constructing folder tree');
  402. }
  403. }
  404. return $root;
  405. }
  406. /**
  407. * select given folder
  408. *
  409. * folder must be selectable!
  410. *
  411. * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
  412. * @return null
  413. * @throws Zend_Mail_Storage_Exception
  414. * @throws Zend_Mail_Protocol_Exception
  415. */
  416. public function selectFolder($globalName)
  417. {
  418. $this->_currentFolder = $globalName;
  419. if (!$this->_protocol->select($this->_currentFolder)) {
  420. $this->_currentFolder = '';
  421. /**
  422. * @see Zend_Mail_Storage_Exception
  423. */
  424. require_once 'Zend/Mail/Storage/Exception.php';
  425. throw new Zend_Mail_Storage_Exception('cannot change folder, maybe it does not exist');
  426. }
  427. }
  428. /**
  429. * get Zend_Mail_Storage_Folder instance for current folder
  430. *
  431. * @return Zend_Mail_Storage_Folder instance of current folder
  432. * @throws Zend_Mail_Storage_Exception
  433. */
  434. public function getCurrentFolder()
  435. {
  436. return $this->_currentFolder;
  437. }
  438. /**
  439. * create a new folder
  440. *
  441. * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
  442. * may be used as parent or which chars may be used in the folder name
  443. *
  444. * @param string $name global name of folder, local name if $parentFolder is set
  445. * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
  446. * @return null
  447. * @throws Zend_Mail_Storage_Exception
  448. */
  449. public function createFolder($name, $parentFolder = null)
  450. {
  451. // TODO: we assume / as the hierarchy delim - need to get that from the folder class!
  452. if ($parentFolder instanceof Zend_Mail_Storage_Folder) {
  453. $folder = $parentFolder->getGlobalName() . '/' . $name;
  454. } else if ($parentFolder != null) {
  455. $folder = $parentFolder . '/' . $name;
  456. } else {
  457. $folder = $name;
  458. }
  459. if (!$this->_protocol->create($folder)) {
  460. /**
  461. * @see Zend_Mail_Storage_Exception
  462. */
  463. require_once 'Zend/Mail/Storage/Exception.php';
  464. throw new Zend_Mail_Storage_Exception('cannot create folder');
  465. }
  466. }
  467. /**
  468. * remove a folder
  469. *
  470. * @param string|Zend_Mail_Storage_Folder $name name or instance of folder
  471. * @return null
  472. * @throws Zend_Mail_Storage_Exception
  473. */
  474. public function removeFolder($name)
  475. {
  476. if ($name instanceof Zend_Mail_Storage_Folder) {
  477. $name = $name->getGlobalName();
  478. }
  479. if (!$this->_protocol->delete($name)) {
  480. /**
  481. * @see Zend_Mail_Storage_Exception
  482. */
  483. require_once 'Zend/Mail/Storage/Exception.php';
  484. throw new Zend_Mail_Storage_Exception('cannot delete folder');
  485. }
  486. }
  487. /**
  488. * rename and/or move folder
  489. *
  490. * The new name has the same restrictions as in createFolder()
  491. *
  492. * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder
  493. * @param string $newName new global name of folder
  494. * @return null
  495. * @throws Zend_Mail_Storage_Exception
  496. */
  497. public function renameFolder($oldName, $newName)
  498. {
  499. if ($oldName instanceof Zend_Mail_Storage_Folder) {
  500. $oldName = $oldName->getGlobalName();
  501. }
  502. if (!$this->_protocol->rename($oldName, $newName)) {
  503. /**
  504. * @see Zend_Mail_Storage_Exception
  505. */
  506. require_once 'Zend/Mail/Storage/Exception.php';
  507. throw new Zend_Mail_Storage_Exception('cannot rename folder');
  508. }
  509. }
  510. /**
  511. * append a new message to mail storage
  512. *
  513. * @param string $message message as string or instance of message class
  514. * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken
  515. * @param null|array $flags set flags for new message, else a default set is used
  516. * @throws Zend_Mail_Storage_Exception
  517. */
  518. // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
  519. public function appendMessage($message, $folder = null, $flags = null)
  520. {
  521. if ($folder === null) {
  522. $folder = $this->_currentFolder;
  523. }
  524. if ($flags === null) {
  525. $flags = array(Zend_Mail_Storage::FLAG_SEEN);
  526. }
  527. // TODO: handle class instances for $message
  528. if (!$this->_protocol->append($folder, $message, $flags)) {
  529. /**
  530. * @see Zend_Mail_Storage_Exception
  531. */
  532. require_once 'Zend/Mail/Storage/Exception.php';
  533. throw new Zend_Mail_Storage_Exception('cannot create message, please check if the folder exists and your flags');
  534. }
  535. }
  536. /**
  537. * copy an existing message
  538. *
  539. * @param int $id number of message
  540. * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
  541. * @return null
  542. * @throws Zend_Mail_Storage_Exception
  543. */
  544. public function copyMessage($id, $folder)
  545. {
  546. if (!$this->_protocol->copy($folder, $id)) {
  547. /**
  548. * @see Zend_Mail_Storage_Exception
  549. */
  550. require_once 'Zend/Mail/Storage/Exception.php';
  551. throw new Zend_Mail_Storage_Exception('cannot copy message, does the folder exist?');
  552. }
  553. }
  554. /**
  555. * move an existing message
  556. *
  557. * NOTE: imap has no native move command, thus it's emulated with copy and delete
  558. *
  559. * @param int $id number of message
  560. * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
  561. * @return null
  562. * @throws Zend_Mail_Storage_Exception
  563. */
  564. public function moveMessage($id, $folder) {
  565. $this->copyMessage($id, $folder);
  566. $this->removeMessage($id);
  567. }
  568. /**
  569. * set flags for message
  570. *
  571. * NOTE: this method can't set the recent flag.
  572. *
  573. * @param int $id number of message
  574. * @param array $flags new flags for message
  575. * @throws Zend_Mail_Storage_Exception
  576. */
  577. public function setFlags($id, $flags)
  578. {
  579. if (!$this->_protocol->store($flags, $id)) {
  580. /**
  581. * @see Zend_Mail_Storage_Exception
  582. */
  583. require_once 'Zend/Mail/Storage/Exception.php';
  584. throw new Zend_Mail_Storage_Exception('cannot set flags, have you tried to set the recent flag or special chars?');
  585. }
  586. }
  587. }