Path

ez components / documentation / api reference / 2007.2 / mail


Mail

[ Tutorial ] [ Class tree ] [ Element index ] [ ChangeLog ] [ Credits ]

Source for file imap_set.php

Documentation is available at imap_set.php

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