Imap.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  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 Protocol
  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. * @category Zend
  24. * @package Zend_Mail
  25. * @subpackage Protocol
  26. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  27. * @license http://framework.zend.com/license/new-bsd New BSD License
  28. */
  29. class Zend_Mail_Protocol_Imap
  30. {
  31. /**
  32. * Default timeout in seconds for initiating session
  33. */
  34. const TIMEOUT_CONNECTION = 30;
  35. /**
  36. * socket to imap server
  37. * @var resource|null
  38. */
  39. protected $_socket;
  40. /**
  41. * counter for request tag
  42. * @var int
  43. */
  44. protected $_tagCount = 0;
  45. /**
  46. * Public constructor
  47. *
  48. * @param string $host hostname or IP address of IMAP server, if given connect() is called
  49. * @param int|null $port port of IMAP server, null for default (143 or 993 for ssl)
  50. * @param bool $ssl use ssl? 'SSL', 'TLS' or false
  51. * @throws Zend_Mail_Protocol_Exception
  52. */
  53. function __construct($host = '', $port = null, $ssl = false)
  54. {
  55. if ($host) {
  56. $this->connect($host, $port, $ssl);
  57. }
  58. }
  59. /**
  60. * Public destructor
  61. */
  62. public function __destruct()
  63. {
  64. $this->logout();
  65. }
  66. /**
  67. * Open connection to IMAP server
  68. *
  69. * @param string $host hostname or IP address of IMAP server
  70. * @param int|null $port of IMAP server, default is 143 (993 for ssl)
  71. * @param string|bool $ssl use 'SSL', 'TLS' or false
  72. * @return string welcome message
  73. * @throws Zend_Mail_Protocol_Exception
  74. */
  75. public function connect($host, $port = null, $ssl = false)
  76. {
  77. if ($ssl == 'SSL') {
  78. $host = 'ssl://' . $host;
  79. }
  80. if ($port === null) {
  81. $port = $ssl === 'SSL' ? 993 : 143;
  82. }
  83. $errno = 0;
  84. $errstr = '';
  85. $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION);
  86. if (!$this->_socket) {
  87. /**
  88. * @see Zend_Mail_Protocol_Exception
  89. */
  90. #require_once 'Zend/Mail/Protocol/Exception.php';
  91. throw new Zend_Mail_Protocol_Exception('cannot connect to host; error = ' . $errstr .
  92. ' (errno = ' . $errno . ' )');
  93. }
  94. if (!$this->_assumedNextLine('* OK')) {
  95. /**
  96. * @see Zend_Mail_Protocol_Exception
  97. */
  98. #require_once 'Zend/Mail/Protocol/Exception.php';
  99. throw new Zend_Mail_Protocol_Exception('host doesn\'t allow connection');
  100. }
  101. if ($ssl === 'TLS') {
  102. $result = $this->requestAndResponse('STARTTLS');
  103. // TODO: Add STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT in the future when it is supported by PHP
  104. $result = $result && stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
  105. if (!$result) {
  106. /**
  107. * @see Zend_Mail_Protocol_Exception
  108. */
  109. #require_once 'Zend/Mail/Protocol/Exception.php';
  110. throw new Zend_Mail_Protocol_Exception('cannot enable TLS');
  111. }
  112. }
  113. }
  114. /**
  115. * get the next line from socket with error checking, but nothing else
  116. *
  117. * @return string next line
  118. * @throws Zend_Mail_Protocol_Exception
  119. */
  120. protected function _nextLine()
  121. {
  122. $line = @fgets($this->_socket);
  123. if ($line === false) {
  124. /**
  125. * @see Zend_Mail_Protocol_Exception
  126. */
  127. #require_once 'Zend/Mail/Protocol/Exception.php';
  128. throw new Zend_Mail_Protocol_Exception('cannot read - connection closed?');
  129. }
  130. return $line;
  131. }
  132. /**
  133. * get next line and assume it starts with $start. some requests give a simple
  134. * feedback so we can quickly check if we can go on.
  135. *
  136. * @param string $start the first bytes we assume to be in the next line
  137. * @return bool line starts with $start
  138. * @throws Zend_Mail_Protocol_Exception
  139. */
  140. protected function _assumedNextLine($start)
  141. {
  142. $line = $this->_nextLine();
  143. return strpos($line, $start) === 0;
  144. }
  145. /**
  146. * get next line and split the tag. that's the normal case for a response line
  147. *
  148. * @param string $tag tag of line is returned by reference
  149. * @return string next line
  150. * @throws Zend_Mail_Protocol_Exception
  151. */
  152. protected function _nextTaggedLine(&$tag)
  153. {
  154. $line = $this->_nextLine();
  155. // seperate tag from line
  156. list($tag, $line) = explode(' ', $line, 2);
  157. return $line;
  158. }
  159. /**
  160. * split a given line in tokens. a token is literal of any form or a list
  161. *
  162. * @param string $line line to decode
  163. * @return array tokens, literals are returned as string, lists as array
  164. * @throws Zend_Mail_Protocol_Exception
  165. */
  166. protected function _decodeLine($line)
  167. {
  168. $tokens = array();
  169. $stack = array();
  170. /*
  171. We start to decode the response here. The unterstood tokens are:
  172. literal
  173. "literal" or also "lit\\er\"al"
  174. {bytes}<NL>literal
  175. (literals*)
  176. All tokens are returned in an array. Literals in braces (the last unterstood
  177. token in the list) are returned as an array of tokens. I.e. the following response:
  178. "foo" baz {3}<NL>bar ("f\\\"oo" bar)
  179. would be returned as:
  180. array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));
  181. // TODO: add handling of '[' and ']' to parser for easier handling of response text
  182. */
  183. // replace any trailling <NL> including spaces with a single space
  184. $line = rtrim($line) . ' ';
  185. while (($pos = strpos($line, ' ')) !== false) {
  186. $token = substr($line, 0, $pos);
  187. while ($token[0] == '(') {
  188. array_push($stack, $tokens);
  189. $tokens = array();
  190. $token = substr($token, 1);
  191. }
  192. if ($token[0] == '"') {
  193. if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
  194. $tokens[] = $matches[1];
  195. $line = substr($line, strlen($matches[0]));
  196. continue;
  197. }
  198. }
  199. if ($token[0] == '{') {
  200. $endPos = strpos($token, '}');
  201. $chars = substr($token, 1, $endPos - 1);
  202. if (is_numeric($chars)) {
  203. $token = '';
  204. while (strlen($token) < $chars) {
  205. $token .= $this->_nextLine();
  206. }
  207. $line = '';
  208. if (strlen($token) > $chars) {
  209. $line = substr($token, $chars);
  210. $token = substr($token, 0, $chars);
  211. } else {
  212. $line .= $this->_nextLine();
  213. }
  214. $tokens[] = $token;
  215. $line = trim($line) . ' ';
  216. continue;
  217. }
  218. }
  219. if ($stack && $token[strlen($token) - 1] == ')') {
  220. // closing braces are not seperated by spaces, so we need to count them
  221. $braces = strlen($token);
  222. $token = rtrim($token, ')');
  223. // only count braces if more than one
  224. $braces -= strlen($token) + 1;
  225. // only add if token had more than just closing braces
  226. if (rtrim($token) != '') {
  227. $tokens[] = rtrim($token);
  228. }
  229. $token = $tokens;
  230. $tokens = array_pop($stack);
  231. // special handline if more than one closing brace
  232. while ($braces-- > 0) {
  233. $tokens[] = $token;
  234. $token = $tokens;
  235. $tokens = array_pop($stack);
  236. }
  237. }
  238. $tokens[] = $token;
  239. $line = substr($line, $pos + 1);
  240. }
  241. // maybe the server forgot to send some closing braces
  242. while ($stack) {
  243. $child = $tokens;
  244. $tokens = array_pop($stack);
  245. $tokens[] = $child;
  246. }
  247. return $tokens;
  248. }
  249. /**
  250. * read a response "line" (could also be more than one real line if response has {..}<NL>)
  251. * and do a simple decode
  252. *
  253. * @param array|string $tokens decoded tokens are returned by reference, if $dontParse
  254. * is true the unparsed line is returned here
  255. * @param string $wantedTag check for this tag for response code. Default '*' is
  256. * continuation tag.
  257. * @param bool $dontParse if true only the unparsed line is returned $tokens
  258. * @return bool if returned tag matches wanted tag
  259. * @throws Zend_Mail_Protocol_Exception
  260. */
  261. public function readLine(&$tokens = array(), $wantedTag = '*', $dontParse = false)
  262. {
  263. $line = $this->_nextTaggedLine($tag);
  264. if (!$dontParse) {
  265. $tokens = $this->_decodeLine($line);
  266. } else {
  267. $tokens = $line;
  268. }
  269. // if tag is wanted tag we might be at the end of a multiline response
  270. return $tag == $wantedTag;
  271. }
  272. /**
  273. * read all lines of response until given tag is found (last line of response)
  274. *
  275. * @param string $tag the tag of your request
  276. * @param string|array $filter you can filter the response so you get only the
  277. * given response lines
  278. * @param bool $dontParse if true every line is returned unparsed instead of
  279. * the decoded tokens
  280. * @return null|bool|array tokens if success, false if error, null if bad request
  281. * @throws Zend_Mail_Protocol_Exception
  282. */
  283. public function readResponse($tag, $dontParse = false)
  284. {
  285. $lines = array();
  286. while (!$this->readLine($tokens, $tag, $dontParse)) {
  287. $lines[] = $tokens;
  288. }
  289. if ($dontParse) {
  290. // last to chars are still needed for response code
  291. $tokens = array(substr($tokens, 0, 2));
  292. }
  293. // last line has response code
  294. if ($tokens[0] == 'OK') {
  295. return $lines ? $lines : true;
  296. } else if ($tokens[0] == 'NO'){
  297. return false;
  298. }
  299. return null;
  300. }
  301. /**
  302. * send a request
  303. *
  304. * @param string $command your request command
  305. * @param array $tokens additional parameters to command, use escapeString() to prepare
  306. * @param string $tag provide a tag otherwise an autogenerated is returned
  307. * @return null
  308. * @throws Zend_Mail_Protocol_Exception
  309. */
  310. public function sendRequest($command, $tokens = array(), &$tag = null)
  311. {
  312. if (!$tag) {
  313. ++$this->_tagCount;
  314. $tag = 'TAG' . $this->_tagCount;
  315. }
  316. $line = $tag . ' ' . $command;
  317. foreach ($tokens as $token) {
  318. if (is_array($token)) {
  319. if (@fputs($this->_socket, $line . ' ' . $token[0] . "\r\n") === false) {
  320. /**
  321. * @see Zend_Mail_Protocol_Exception
  322. */
  323. #require_once 'Zend/Mail/Protocol/Exception.php';
  324. throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
  325. }
  326. if (!$this->_assumedNextLine('+ ')) {
  327. /**
  328. * @see Zend_Mail_Protocol_Exception
  329. */
  330. #require_once 'Zend/Mail/Protocol/Exception.php';
  331. throw new Zend_Mail_Protocol_Exception('cannot send literal string');
  332. }
  333. $line = $token[1];
  334. } else {
  335. $line .= ' ' . $token;
  336. }
  337. }
  338. if (@fputs($this->_socket, $line . "\r\n") === false) {
  339. /**
  340. * @see Zend_Mail_Protocol_Exception
  341. */
  342. #require_once 'Zend/Mail/Protocol/Exception.php';
  343. throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
  344. }
  345. }
  346. /**
  347. * send a request and get response at once
  348. *
  349. * @param string $command command as in sendRequest()
  350. * @param array $tokens parameters as in sendRequest()
  351. * @param bool $dontParse if true unparsed lines are returned instead of tokens
  352. * @return mixed response as in readResponse()
  353. * @throws Zend_Mail_Protocol_Exception
  354. */
  355. public function requestAndResponse($command, $tokens = array(), $dontParse = false)
  356. {
  357. $this->sendRequest($command, $tokens, $tag);
  358. $response = $this->readResponse($tag, $dontParse);
  359. return $response;
  360. }
  361. /**
  362. * escape one or more literals i.e. for sendRequest
  363. *
  364. * @param string|array $string the literal/-s
  365. * @return string|array escape literals, literals with newline ar returned
  366. * as array('{size}', 'string');
  367. */
  368. public function escapeString($string)
  369. {
  370. if (func_num_args() < 2) {
  371. if (strpos($string, "\n") !== false) {
  372. return array('{' . strlen($string) . '}', $string);
  373. } else {
  374. return '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $string) . '"';
  375. }
  376. }
  377. $result = array();
  378. foreach (func_get_args() as $string) {
  379. $result[] = $this->escapeString($string);
  380. }
  381. return $result;
  382. }
  383. /**
  384. * escape a list with literals or lists
  385. *
  386. * @param array $list list with literals or lists as PHP array
  387. * @return string escaped list for imap
  388. */
  389. public function escapeList($list)
  390. {
  391. $result = array();
  392. foreach ($list as $k => $v) {
  393. if (!is_array($v)) {
  394. // $result[] = $this->escapeString($v);
  395. $result[] = $v;
  396. continue;
  397. }
  398. $result[] = $this->escapeList($v);
  399. }
  400. return '(' . implode(' ', $result) . ')';
  401. }
  402. /**
  403. * Login to IMAP server.
  404. *
  405. * @param string $user username
  406. * @param string $password password
  407. * @return bool success
  408. * @throws Zend_Mail_Protocol_Exception
  409. */
  410. public function login($user, $password)
  411. {
  412. return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true);
  413. }
  414. /**
  415. * logout of imap server
  416. *
  417. * @return bool success
  418. */
  419. public function logout()
  420. {
  421. $result = false;
  422. if ($this->_socket) {
  423. try {
  424. $result = $this->requestAndResponse('LOGOUT', array(), true);
  425. } catch (Zend_Mail_Protocol_Exception $e) {
  426. // ignoring exception
  427. }
  428. fclose($this->_socket);
  429. $this->_socket = null;
  430. }
  431. return $result;
  432. }
  433. /**
  434. * Get capabilities from IMAP server
  435. *
  436. * @return array list of capabilities
  437. * @throws Zend_Mail_Protocol_Exception
  438. */
  439. public function capability()
  440. {
  441. $response = $this->requestAndResponse('CAPABILITY');
  442. if (!$response) {
  443. return $response;
  444. }
  445. $capabilities = array();
  446. foreach ($response as $line) {
  447. $capabilities = array_merge($capabilities, $line);
  448. }
  449. return $capabilities;
  450. }
  451. /**
  452. * Examine and select have the same response. The common code for both
  453. * is in this method
  454. *
  455. * @param string $command can be 'EXAMINE' or 'SELECT' and this is used as command
  456. * @param string $box which folder to change to or examine
  457. * @return bool|array false if error, array with returned information
  458. * otherwise (flags, exists, recent, uidvalidity)
  459. * @throws Zend_Mail_Protocol_Exception
  460. */
  461. public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX')
  462. {
  463. $this->sendRequest($command, array($this->escapeString($box)), $tag);
  464. $result = array();
  465. while (!$this->readLine($tokens, $tag)) {
  466. if ($tokens[0] == 'FLAGS') {
  467. array_shift($tokens);
  468. $result['flags'] = $tokens;
  469. continue;
  470. }
  471. switch ($tokens[1]) {
  472. case 'EXISTS':
  473. case 'RECENT':
  474. $result[strtolower($tokens[1])] = $tokens[0];
  475. break;
  476. case '[UIDVALIDITY':
  477. $result['uidvalidity'] = (int)$tokens[2];
  478. break;
  479. default:
  480. // ignore
  481. }
  482. }
  483. if ($tokens[0] != 'OK') {
  484. return false;
  485. }
  486. return $result;
  487. }
  488. /**
  489. * change folder
  490. *
  491. * @param string $box change to this folder
  492. * @return bool|array see examineOrselect()
  493. * @throws Zend_Mail_Protocol_Exception
  494. */
  495. public function select($box = 'INBOX')
  496. {
  497. return $this->examineOrSelect('SELECT', $box);
  498. }
  499. /**
  500. * examine folder
  501. *
  502. * @param string $box examine this folder
  503. * @return bool|array see examineOrselect()
  504. * @throws Zend_Mail_Protocol_Exception
  505. */
  506. public function examine($box = 'INBOX')
  507. {
  508. return $this->examineOrSelect('EXAMINE', $box);
  509. }
  510. /**
  511. * fetch one or more items of one or more messages
  512. *
  513. * @param string|array $items items to fetch from message(s) as string (if only one item)
  514. * or array of strings
  515. * @param int $from message for items or start message if $to !== null
  516. * @param int|null $to if null only one message ($from) is fetched, else it's the
  517. * last message, INF means last message avaible
  518. * @return string|array if only one item of one message is fetched it's returned as string
  519. * if items of one message are fetched it's returned as (name => value)
  520. * if one items of messages are fetched it's returned as (msgno => value)
  521. * if items of messages are fetchted it's returned as (msgno => (name => value))
  522. * @throws Zend_Mail_Protocol_Exception
  523. */
  524. public function fetch($items, $from, $to = null)
  525. {
  526. if (is_array($from)) {
  527. $set = implode(',', $from);
  528. } else if ($to === null) {
  529. $set = (int)$from;
  530. } else if ($to === INF) {
  531. $set = (int)$from . ':*';
  532. } else {
  533. $set = (int)$from . ':' . (int)$to;
  534. }
  535. $items = (array)$items;
  536. $itemList = $this->escapeList($items);
  537. $this->sendRequest('FETCH', array($set, $itemList), $tag);
  538. $result = array();
  539. while (!$this->readLine($tokens, $tag)) {
  540. // ignore other responses
  541. if ($tokens[1] != 'FETCH') {
  542. continue;
  543. }
  544. // ignore other messages
  545. if ($to === null && !is_array($from) && $tokens[0] != $from) {
  546. continue;
  547. }
  548. // if we only want one item we return that one directly
  549. if (count($items) == 1) {
  550. if ($tokens[2][0] == $items[0]) {
  551. $data = $tokens[2][1];
  552. } else {
  553. // maybe the server send an other field we didn't wanted
  554. $count = count($tokens[2]);
  555. // we start with 2, because 0 was already checked
  556. for ($i = 2; $i < $count; $i += 2) {
  557. if ($tokens[2][$i] != $items[0]) {
  558. continue;
  559. }
  560. $data = $tokens[2][$i + 1];
  561. break;
  562. }
  563. }
  564. } else {
  565. $data = array();
  566. while (key($tokens[2]) !== null) {
  567. $data[current($tokens[2])] = next($tokens[2]);
  568. next($tokens[2]);
  569. }
  570. }
  571. // if we want only one message we can ignore everything else and just return
  572. if ($to === null && !is_array($from) && $tokens[0] == $from) {
  573. // we still need to read all lines
  574. while (!$this->readLine($tokens, $tag));
  575. return $data;
  576. }
  577. $result[$tokens[0]] = $data;
  578. }
  579. if ($to === null && !is_array($from)) {
  580. /**
  581. * @see Zend_Mail_Protocol_Exception
  582. */
  583. #require_once 'Zend/Mail/Protocol/Exception.php';
  584. throw new Zend_Mail_Protocol_Exception('the single id was not found in response');
  585. }
  586. return $result;
  587. }
  588. /**
  589. * get mailbox list
  590. *
  591. * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword
  592. *
  593. * @param string $reference mailbox reference for list
  594. * @param string $mailbox mailbox name match with wildcards
  595. * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..))
  596. * @throws Zend_Mail_Protocol_Exception
  597. */
  598. public function listMailbox($reference = '', $mailbox = '*')
  599. {
  600. $result = array();
  601. $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
  602. if (!$list || $list === true) {
  603. return $result;
  604. }
  605. foreach ($list as $item) {
  606. if (count($item) != 4 || $item[0] != 'LIST') {
  607. continue;
  608. }
  609. $result[$item[3]] = array('delim' => $item[2], 'flags' => $item[1]);
  610. }
  611. return $result;
  612. }
  613. /**
  614. * set flags
  615. *
  616. * @param array $flags flags to set, add or remove - see $mode
  617. * @param int $from message for items or start message if $to !== null
  618. * @param int|null $to if null only one message ($from) is fetched, else it's the
  619. * last message, INF means last message avaible
  620. * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given
  621. * @param bool $silent if false the return values are the new flags for the wanted messages
  622. * @return bool|array new flags if $silent is false, else true or false depending on success
  623. * @throws Zend_Mail_Protocol_Exception
  624. */
  625. public function store(array $flags, $from, $to = null, $mode = null, $silent = true)
  626. {
  627. $item = 'FLAGS';
  628. if ($mode == '+' || $mode == '-') {
  629. $item = $mode . $item;
  630. }
  631. if ($silent) {
  632. $item .= '.SILENT';
  633. }
  634. $flags = $this->escapeList($flags);
  635. $set = (int)$from;
  636. if ($to != null) {
  637. $set .= ':' . ($to == INF ? '*' : (int)$to);
  638. }
  639. $result = $this->requestAndResponse('STORE', array($set, $item, $flags), $silent);
  640. if ($silent) {
  641. return $result ? true : false;
  642. }
  643. $tokens = $result;
  644. $result = array();
  645. foreach ($tokens as $token) {
  646. if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
  647. continue;
  648. }
  649. $result[$token[0]] = $token[2][1];
  650. }
  651. return $result;
  652. }
  653. /**
  654. * append a new message to given folder
  655. *
  656. * @param string $folder name of target folder
  657. * @param string $message full message content
  658. * @param array $flags flags for new message
  659. * @param string $date date for new message
  660. * @return bool success
  661. * @throws Zend_Mail_Protocol_Exception
  662. */
  663. public function append($folder, $message, $flags = null, $date = null)
  664. {
  665. $tokens = array();
  666. $tokens[] = $this->escapeString($folder);
  667. if ($flags !== null) {
  668. $tokens[] = $this->escapeList($flags);
  669. }
  670. if ($date !== null) {
  671. $tokens[] = $this->escapeString($date);
  672. }
  673. $tokens[] = $this->escapeString($message);
  674. return $this->requestAndResponse('APPEND', $tokens, true);
  675. }
  676. /**
  677. * copy message set from current folder to other folder
  678. *
  679. * @param string $folder destination folder
  680. * @param int|null $to if null only one message ($from) is fetched, else it's the
  681. * last message, INF means last message avaible
  682. * @return bool success
  683. * @throws Zend_Mail_Protocol_Exception
  684. */
  685. public function copy($folder, $from, $to = null)
  686. {
  687. $set = (int)$from;
  688. if ($to != null) {
  689. $set .= ':' . ($to == INF ? '*' : (int)$to);
  690. }
  691. return $this->requestAndResponse('COPY', array($set, $this->escapeString($folder)), true);
  692. }
  693. /**
  694. * create a new folder (and parent folders if needed)
  695. *
  696. * @param string $folder folder name
  697. * @return bool success
  698. */
  699. public function create($folder)
  700. {
  701. return $this->requestAndResponse('CREATE', array($this->escapeString($folder)), true);
  702. }
  703. /**
  704. * rename an existing folder
  705. *
  706. * @param string $old old name
  707. * @param string $new new name
  708. * @return bool success
  709. */
  710. public function rename($old, $new)
  711. {
  712. return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true);
  713. }
  714. /**
  715. * remove a folder
  716. *
  717. * @param string $folder folder name
  718. * @return bool success
  719. */
  720. public function delete($folder)
  721. {
  722. return $this->requestAndResponse('DELETE', array($this->escapeString($folder)), true);
  723. }
  724. /**
  725. * permanently remove messages
  726. *
  727. * @return bool success
  728. */
  729. public function expunge()
  730. {
  731. // TODO: parse response?
  732. return $this->requestAndResponse('EXPUNGE');
  733. }
  734. /**
  735. * send noop
  736. *
  737. * @return bool success
  738. */
  739. public function noop()
  740. {
  741. // TODO: parse response
  742. return $this->requestAndResponse('NOOP');
  743. }
  744. /**
  745. * do a search request
  746. *
  747. * This method is currently marked as internal as the API might change and is not
  748. * safe if you don't take precautions.
  749. *
  750. * @internal
  751. * @return array message ids
  752. */
  753. public function search(array $params)
  754. {
  755. $response = $this->requestAndResponse('SEARCH', $params);
  756. if (!$response) {
  757. return $response;
  758. }
  759. foreach ($response as $ids) {
  760. if ($ids[0] == 'SEARCH') {
  761. array_shift($ids);
  762. return $ids;
  763. }
  764. }
  765. return array();
  766. }
  767. }