PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/public_html/sites/all/modules/civicrm/packages/ezc/Mail/src/transports/imap/imap_set.php

https://github.com/timstephenson/NatureBridge
PHP | 375 lines | 167 code | 22 blank | 186 comment | 27 complexity | 2e26c203e046b22135103ebf62dc458a MD5 | raw file
  1. <?php
  2. /**
  3. * File containing the ezcMailImapSet class.
  4. *
  5. * @package Mail
  6. * @version 1.7beta1
  7. * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
  8. * @license http://ez.no/licenses/new_bsd New BSD License
  9. */
  10. /**
  11. * ezcMailImapSet is an internal class that fetches a series of mail
  12. * from the IMAP server.
  13. *
  14. * The IMAP set works on an existing connection and a list of the messages that
  15. * the user wants to fetch. The user must accept all the data for each mail for
  16. * correct behaviour.
  17. *
  18. * @package Mail
  19. * @version 1.7beta1
  20. */
  21. class ezcMailImapSet implements ezcMailParserSet
  22. {
  23. /**
  24. * Holds the list of messages that the user wants to retrieve from the server.
  25. *
  26. * @var array(int)
  27. */
  28. private $messages;
  29. /**
  30. * Holds the current message the user is fetching.
  31. *
  32. * The variable is null before the first message and false after
  33. * the last message has been fetched.
  34. *
  35. * @var int
  36. */
  37. private $currentMessage = null;
  38. /**
  39. * Holds the line that will be read-ahead in order to determine the trailing paranthesis.
  40. *
  41. * @var string
  42. */
  43. private $nextData = null;
  44. /**
  45. * This variable is true if there is more data in the mail that is being fetched.
  46. *
  47. * It is false if there is no mail being fetched currently or if all the data of the current mail
  48. * has been fetched.
  49. *
  50. * @var bool
  51. */
  52. private $hasMoreMailData = false;
  53. /**
  54. * Holds if mail should be deleted from the server after retrieval.
  55. *
  56. * @var bool
  57. */
  58. private $deleteFromServer = false;
  59. /**
  60. * Used to generate a tag for sending commands to the IMAP server.
  61. *
  62. * @var string
  63. */
  64. private $currentTag = 'A0000';
  65. /**
  66. * Holds the mode in which the IMAP commands operate.
  67. *
  68. * @var string
  69. */
  70. private $uid;
  71. /**
  72. * Holds the options for an IMAP mail set.
  73. *
  74. * @var ezcMailImapSetOptions
  75. */
  76. private $options;
  77. /**
  78. * Holds the number of bytes to read from the IMAP server.
  79. *
  80. * It is set before starting to read a message from the information
  81. * returned by the IMAP server in this form:
  82. *
  83. * <code>
  84. * * 2 FETCH (FLAGS (\Answered \Seen) RFC822 {377}
  85. * </code>
  86. *
  87. * In this example, $this->bytesToRead will be set to 377.
  88. *
  89. * @var int
  90. */
  91. private $bytesToRead = false;
  92. /**
  93. * Constructs a new IMAP parser set that will fetch the messages $messages.
  94. *
  95. * $connection must hold a valid connection to a IMAP server that is ready
  96. * to retrieve the messages.
  97. *
  98. * If $deleteFromServer is set to true the messages will be deleted after retrieval.
  99. *
  100. * See {@link ezcMailImapSetOptions} for options you can set to IMAP sets.
  101. *
  102. * @throws ezcMailTransportException
  103. * if the server sent a negative response
  104. * @param ezcMailTransportConnection $connection
  105. * @param array(int) $messages
  106. * @param bool $deleteFromServer
  107. * @param ezcMailImapSetOptions|array(string=>mixed) $options
  108. */
  109. public function __construct( ezcMailTransportConnection $connection, array $messages, $deleteFromServer = false, $options = array() )
  110. {
  111. if ( $options instanceof ezcMailImapSetOptions )
  112. {
  113. $this->options = $options;
  114. }
  115. else if ( is_array( $options ) )
  116. {
  117. $this->options = new ezcMailImapSetOptions( $options );
  118. }
  119. else
  120. {
  121. throw new ezcBaseValueException( "options", $options, "ezcMailImapSetOptions|array" );
  122. }
  123. $this->connection = $connection;
  124. $this->messages = $messages;
  125. $this->deleteFromServer = $deleteFromServer;
  126. $this->nextData = null;
  127. $this->uid = ( $this->options->uidReferencing ) ? ezcMailImapTransport::UID : ezcMailImapTransport::NO_UID;
  128. }
  129. /**
  130. * Returns true if all the data has been fetched from this set.
  131. *
  132. * @return bool
  133. */
  134. public function isFinished()
  135. {
  136. return $this->currentMessage === false ? true : false;
  137. }
  138. /**
  139. * Returns one line of data from the current mail in the set.
  140. *
  141. * Null is returned if there is no current mail in the set or
  142. * the end of the mail is reached,
  143. *
  144. * @return string
  145. */
  146. public function getNextLine()
  147. {
  148. if ( $this->currentMessage === null )
  149. {
  150. // instead of calling $this->nextMail() in the constructor, it is called
  151. // here, to avoid sending commands to the server when creating the set, and
  152. // instead send the server commands when parsing the set (see ezcMailParser).
  153. $this->nextMail();
  154. }
  155. if ( $this->hasMoreMailData )
  156. {
  157. if ( $this->bytesToRead !== false && $this->bytesToRead >= 0 )
  158. {
  159. $data = $this->connection->getLine();
  160. $this->bytesToRead -= strlen( $data );
  161. // modified for issue #13878 (Endless loop in ezcMailParser):
  162. // removed wrong checks (ending in ')' check and ending with tag check (e.g. 'A0002'))
  163. if ( $this->bytesToRead <= 0 )
  164. {
  165. if ( $this->bytesToRead < 0 )
  166. {
  167. $data = substr( $data, 0, strlen( $data ) + $this->bytesToRead ); //trim( $data, ")\r\n" );
  168. }
  169. if ( $this->bytesToRead === 0 )
  170. {
  171. // hack for Microsoft Exchange, which sometimes puts
  172. // FLAGS (\Seen)) at the end of a message instead of before (!)
  173. if ( strlen( trim( $data, ")\r\n" ) !== strlen( $data ) - 3 ) )
  174. {
  175. // if the last line in a mail does not end with ")\r\n"
  176. // then read an extra line and discard it
  177. $extraData = $this->connection->getLine();
  178. }
  179. }
  180. $this->hasMoreMailData = false;
  181. // remove the mail if required by the user.
  182. if ( $this->deleteFromServer === true )
  183. {
  184. $tag = $this->getNextTag();
  185. $this->connection->sendData( "{$tag} {$this->uid}STORE {$this->currentMessage} +FLAGS (\\Deleted)" );
  186. // skip OK response ("{$tag} OK Store completed.")
  187. $response = $this->getResponse( $tag );
  188. }
  189. return $data;
  190. }
  191. }
  192. return $data;
  193. }
  194. return null;
  195. }
  196. /**
  197. * Moves the set to the next mail and returns true upon success.
  198. *
  199. * False is returned if there are no more mail in the set.
  200. *
  201. * @throws ezcMailTransportException
  202. * if the server sent a negative response
  203. * @return bool
  204. */
  205. public function nextMail()
  206. {
  207. if ( $this->currentMessage === null )
  208. {
  209. $this->currentMessage = reset( $this->messages );
  210. }
  211. else
  212. {
  213. $this->currentMessage = next( $this->messages );
  214. }
  215. $this->nextData = null;
  216. $this->bytesToRead = false;
  217. if ( $this->currentMessage !== false )
  218. {
  219. $tag = $this->getNextTag();
  220. $this->connection->sendData( "{$tag} {$this->uid}FETCH {$this->currentMessage} RFC822" );
  221. $response = $this->connection->getLine();
  222. if ( strpos( $response, ' NO ' ) !== false ||
  223. strpos( $response, ' BAD ') !== false )
  224. {
  225. throw new ezcMailTransportException( "The IMAP server sent a negative reply when requesting mail." );
  226. }
  227. else
  228. {
  229. $response = $this->getResponse( 'FETCH (', $response );
  230. if ( strpos( $response, 'FETCH (' ) !== false )
  231. {
  232. $this->hasMoreMailData = true;
  233. // retrieve the message size from $response, eg. if $response is:
  234. // * 2 FETCH (FLAGS (\Answered \Seen) RFC822 {377}
  235. // then $this->bytesToRead will be 377
  236. preg_match( '/\{(.*)\}/', $response, $matches );
  237. if ( count( $matches ) > 0 )
  238. {
  239. $this->bytesToRead = (int) $matches[1];
  240. }
  241. return true;
  242. }
  243. else
  244. {
  245. $response = $this->getResponse( $tag );
  246. if ( strpos( $response, 'OK ' ) === false )
  247. {
  248. throw new ezcMailTransportException( "The IMAP server sent a negative reply when requesting mail." );
  249. }
  250. }
  251. }
  252. }
  253. return false;
  254. }
  255. /**
  256. * Reads the responses from the server until encountering $tag.
  257. *
  258. * In IMAP, each command sent by the client is prepended with a
  259. * alphanumeric tag like 'A1234'. The server sends the response
  260. * to the client command as lines, and the last line in the response
  261. * is prepended with the same tag, and it contains the status of
  262. * the command completion ('OK', 'NO' or 'BAD').
  263. *
  264. * Sometimes the server sends alerts and response lines from other
  265. * commands before sending the tagged line, so this method just
  266. * reads all the responses until it encounters $tag.
  267. *
  268. * It returns the tagged line to be processed by the calling method.
  269. *
  270. * If $response is specified, then it will not read the response
  271. * from the server before searching for $tag in $response.
  272. *
  273. * Before calling this method, a connection to the IMAP server must be
  274. * established.
  275. *
  276. * @param string $tag
  277. * @param string $response
  278. * @return string
  279. */
  280. private function getResponse( $tag = null, $response = null )
  281. {
  282. if ( is_null( $response ) )
  283. {
  284. $response = $this->connection->getLine();
  285. }
  286. while ( strpos( $response, $tag ) === false )
  287. {
  288. if ( strpos( $response, ' BAD ' ) !== false ||
  289. strpos( $response, ' NO ' ) !== false )
  290. {
  291. break;
  292. }
  293. $response = $this->connection->getLine();
  294. }
  295. return $response;
  296. }
  297. /**
  298. * Generates the next IMAP tag to prepend to client commands.
  299. *
  300. * The structure of the IMAP tag is Axxxx, where:
  301. * - A is a letter (uppercase for conformity)
  302. * - x is a digit from 0 to 9
  303. *
  304. * example of generated tag: T5439
  305. *
  306. * It uses the class variable $this->currentTag.
  307. *
  308. * Everytime it is called, the tag increases by 1.
  309. *
  310. * If it reaches the last tag, it wraps around to the first tag.
  311. *
  312. * By default, the first generated tag is A0001.
  313. *
  314. * @return string
  315. */
  316. private function getNextTag()
  317. {
  318. $tagLetter = substr( $this->currentTag, 0, 1 );
  319. $tagNumber = intval( substr( $this->currentTag, 1 ) );
  320. $tagNumber++;
  321. if ( $tagLetter == 'Z' && $tagNumber == 10000 )
  322. {
  323. $tagLetter = 'A';
  324. $tagNumber = 1;
  325. }
  326. if ( $tagNumber == 10000 )
  327. {
  328. $tagLetter++;
  329. $tagNumber = 0;
  330. }
  331. $this->currentTag = $tagLetter . sprintf( "%04s", $tagNumber );
  332. return $this->currentTag;
  333. }
  334. /**
  335. * Returns whether the set has mails.
  336. *
  337. * @return bool
  338. */
  339. public function hasData()
  340. {
  341. return count( $this->messages );
  342. }
  343. /**
  344. * Returns message numbers from the current set.
  345. *
  346. * @return array(int)
  347. */
  348. public function getMessageNumbers()
  349. {
  350. return $this->messages;
  351. }
  352. }
  353. ?>