| <?php |
| /** |
| * File containing the ezcMailImapTransport class. |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| * @package Mail |
| * @version //autogen// |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| */ |
| |
| /** |
| * The class ezcMailImapTransport implements functionality for handling IMAP |
| * mail servers. |
| * |
| * The implementation supports most of the commands specified in: |
| * - {@link http://www.faqs.org/rfcs/rfc1730.html} (IMAP4) |
| * - {@link http://www.faqs.org/rfcs/rfc2060.html} (IMAP4rev1) |
| * |
| * Each user account on the IMAP server has it's own folders (mailboxes). |
| * Mailboxes can be created, renamed or deleted. All accounts have a special |
| * mailbox called Inbox which cannot be deleted or renamed. |
| * |
| * Messages are organized in mailboxes, and are identified by a message number |
| * (which can change over time) and a unique ID (which does not change under |
| * normal circumstances). The commands operating on messages can handle both |
| * modes (message numbers or unique IDs). |
| * |
| * Messages are marked by certain flags (SEEN, DRAFT, etc). Deleting a message |
| * actually sets it's DELETED flag, and a later call to {@link expunge()} will |
| * delete all the messages marked with the DELETED flag. |
| * |
| * The IMAP server can be in different states. Most IMAP commands require |
| * that a connection is established and a user is authenticated. Certain |
| * commands require in addition that a mailbox is selected. |
| * |
| * The IMAP transport class allows developers to interface with an IMAP server. |
| * The commands which support unique IDs to refer to messages are marked with |
| * [*] (see {@link ezcMailImapTransportOptions} to find out how to enable |
| * unique IDs referencing): |
| * |
| * Basic commands: |
| * - connect to an IMAP server ({@link __construct()}) |
| * - authenticate a user with a username and password ({@link authenticate()}) |
| * - select a mailbox ({@link selectMailbox()}) |
| * - disconnect from the IMAP server ({@link disconnect()}) |
| * |
| * Work with mailboxes: |
| * - get the list of mailboxes of the user ({@link listMailboxes()}) |
| * - create a mailbox ({@link createMailbox()}) |
| * - rename a mailbox ({@link renameMailbox()}) |
| * - delete a mailbox ({@link deleteMailbox()}) |
| * - append a message to a mailbox ({@link append()}) |
| * - select a mailbox ({@link selectMailbox()}) |
| * - get the status of messages in the current mailbox ({@link status()}) |
| * - get the number of messages with a certain flag ({@link countByFlag()}) |
| * |
| * Work with message numbers (on the currently selected mailbox): |
| * - get the message numbers and sizes of all the messages ({@link listMessages()}) |
| * - get the message numbers and IDs of all the messages ({@link listUniqueIdentifiers()}) |
| * - [*] get the headers of a certain message ({@link top()}) |
| * - [*] delete a message ({@link delete()} and {@link expunge()}) |
| * - [*] copy messages to another mailbox ({@link copyMessages()}) |
| * - [*] get the sizes of the specified messages ({@link fetchSizes()}) |
| * |
| * Work with flags (on the currently selected mailbox): |
| * - [*] get the flags of the specified messages ({@link fetchFlags()}) |
| * - [*] set a flag on the specified messages ({@link setFlag()}) |
| * - [*] clear a flag from the specified messages ({@link clearFlag()}) |
| * |
| * Work with {@link ezcMailImapSet} sets (parseable with {@link ezcMailParser}) |
| * (on the currently selected mailbox): |
| * - [*] create a set from all messages ({@link fetchAll()}) |
| * - [*] create a set from a certain message ({@link fetchByMessageNr()}) |
| * - [*] create a set from a range of messages ({@link fetchFromOffset()}) |
| * - [*] create a set from messages with a certain flag ({@link fetchByFlag()}) |
| * - [*] create a set from a sorted range of messages ({@link sortFromOffset()}) |
| * - [*] create a set from a sorted list of messages ({@link sortMessages()}) |
| * - [*] create a set from a free-form search ({@link searchMailbox()}) |
| * |
| * Miscellaneous commands: |
| * - get the capabilities of the IMAP server ({@link capability()}) |
| * - get the hierarchy delimiter (useful for nested mailboxes) ({@link getHierarchyDelimiter()}) |
| * - issue a NOOP command to keep the connection alive ({@link noop()}) |
| * |
| * The usual operation with an IMAP server is illustrated by this example: |
| * <code> |
| * // create a new IMAP transport object by specifying the server name, optional port |
| * // and optional SSL mode |
| * $options = new ezcMailImapTransportOptions(); |
| * $options->ssl = true; |
| * $imap = new ezcMailImapTransport( 'imap.example.com', null, $options ); |
| * |
| * // Authenticate to the IMAP server |
| * $imap->authenticate( 'username', 'password' ); |
| * |
| * // Select a mailbox (here 'Inbox') |
| * $imap->selectMailbox( 'Inbox' ); |
| * |
| * // issue commands to the IMAP server |
| * // for example get the number of RECENT messages |
| * $recent = $imap->countByFlag( 'RECENT' ); |
| * |
| * // see the above list of commands or consult the online documentation for |
| * // the full list of commands you can issue to an IMAP server and examples |
| * |
| * // disconnect from the IMAP server |
| * $imap->disconnect(); |
| * </code> |
| * |
| * See {@link ezcMailImapTransportOptions} for other options you can specify |
| * for IMAP. |
| * |
| * @todo ignore messages of a certain size? |
| * @todo // support for signing? |
| * @todo listUniqueIdentifiers(): add UIVALIDITY value to UID (like in POP3). |
| * (if necessary). |
| * |
| * @property ezcMailImapTransportOptions $options |
| * Holds the options you can set to the IMAP transport. |
| * |
| * @package Mail |
| * @version //autogen// |
| * @mainclass |
| */ |
| class ezcMailImapTransport |
| { |
| /** |
| * Internal state when the IMAP transport is not connected to a server. |
| * |
| * @access private |
| */ |
| const STATE_NOT_CONNECTED = 1; |
| |
| /** |
| * Internal state when the IMAP transport is connected to a server, |
| * but no successful authentication has been performed. |
| * |
| * @access private |
| */ |
| const STATE_NOT_AUTHENTICATED = 2; |
| |
| /** |
| * Internal state when the IMAP transport is connected to a server |
| * and authenticated, but no mailbox is selected yet. |
| * |
| * @access private |
| */ |
| const STATE_AUTHENTICATED = 3; |
| |
| /** |
| * Internal state when the IMAP transport is connected to a server, |
| * authenticated, and a mailbox is selected. |
| * |
| * @access private |
| */ |
| const STATE_SELECTED = 4; |
| |
| /** |
| * Internal state when the IMAP transport is connected to a server, |
| * authenticated, and a mailbox is selected read only. |
| * |
| * @access private |
| */ |
| const STATE_SELECTED_READONLY = 5; |
| |
| /** |
| * Internal state when the LOGOUT command has been issued to the IMAP |
| * server, but before the disconnect has taken place. |
| * |
| * @access private |
| */ |
| const STATE_LOGOUT = 6; |
| |
| /** |
| * The response sent from the IMAP server is "OK". |
| * |
| * @access private |
| */ |
| const RESPONSE_OK = 1; |
| |
| /** |
| * The response sent from the IMAP server is "NO". |
| * |
| * @access private |
| */ |
| const RESPONSE_NO = 2; |
| |
| /** |
| * The response sent from the IMAP server is "BAD". |
| * |
| * @access private |
| */ |
| const RESPONSE_BAD = 3; |
| |
| /** |
| * The response sent from the IMAP server is untagged (starts with "*"). |
| * |
| * @access private |
| */ |
| const RESPONSE_UNTAGGED = 4; |
| |
| /** |
| * The response sent from the IMAP server requires the client to send |
| * information (starts with "+"). |
| * |
| * @access private |
| */ |
| const RESPONSE_FEEDBACK = 5; |
| |
| /** |
| * Use UID commands (access messages by their unique ID). |
| * |
| * @access private |
| */ |
| const UID = 'UID '; |
| |
| /** |
| * Use message number commands (access messages by their message numbers). |
| * |
| * @access private |
| */ |
| const NO_UID = ''; |
| |
| /** |
| * The string returned by Google IMAP servers when at connection time. |
| * |
| * @access private |
| */ |
| const SERVER_GIMAP = 'Gimap'; |
| |
| /** |
| * Basic flags are used by {@link setFlag()} and {@link clearFlag()} |
| * |
| * Basic flags: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - SEEN - message has been read |
| * |
| * @var array(string) |
| */ |
| protected static $basicFlags = array( 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'SEEN' ); |
| |
| /** |
| * Extended flags are used by {@link searchByFlag()} |
| * |
| * Basic flags: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - RECENT - message is recent |
| * - SEEN - message has been read |
| * |
| * Opposites of the above flags: |
| * - UNANSWERED |
| * - UNDELETED |
| * - UNDRAFT |
| * - UNFLAGGED |
| * - OLD |
| * - UNSEEN |
| * |
| * Composite flags: |
| * - NEW - equivalent to RECENT + UNSEEN |
| * - ALL - all the messages |
| * |
| * @var array(string) |
| */ |
| protected static $extendedFlags = array( 'ALL', 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'NEW', 'OLD', 'RECENT', 'SEEN', 'UNANSWERED', 'UNDELETED', 'UNDRAFT', 'UNFLAGGED', 'UNRECENT', 'UNSEEN' ); |
| |
| /** |
| * Used to generate a tag for sending commands to the IMAP server. |
| * |
| * @var string |
| */ |
| protected $currentTag = 'A0000'; |
| |
| /** |
| * Holds the connection state. |
| * |
| * @var int {@link STATE_NOT_CONNECTED}, |
| * {@link STATE_NOT_AUTHENTICATED}, |
| * {@link STATE_AUTHENTICATED}, |
| * {@link STATE_SELECTED}, |
| * {@link STATE_SELECTED_READONLY} or |
| * {@link STATE_LOGOUT}. |
| */ |
| protected $state = self::STATE_NOT_CONNECTED; |
| |
| /** |
| * Holds the currently selected mailbox. |
| * |
| * @var string |
| */ |
| protected $selectedMailbox = null; |
| |
| /** |
| * Holds the connection to the IMAP server. |
| * |
| * @var ezcMailTransportConnection |
| */ |
| protected $connection = null; |
| |
| /** |
| * Holds the string which identifies the IMAP server type. |
| * |
| * Used for fixing problems with Google IMAP (see issue #14360). Possible |
| * values are {@link self::SERVER_GIMAP} or null for all other servers. |
| * |
| * @todo Add identification strings for each existing IMAP server? |
| * |
| * @var string |
| */ |
| protected $serverType = null; |
| |
| /** |
| * Holds the options for an IMAP transport connection. |
| * |
| * @var ezcMailImapTransportOptions |
| */ |
| private $options; |
| |
| /** |
| * Creates a new IMAP transport and connects to the $server at $port. |
| * |
| * You can specify the $port if the IMAP server is not on the default port |
| * 993 (for SSL connections) or 143 (for plain connections). Use the $options |
| * parameter to specify an SSL connection. |
| * |
| * See {@link ezcMailImapTransportOptions} for options you can specify for |
| * IMAP. |
| * |
| * Example of creating an IMAP transport: |
| * <code> |
| * // replace with your IMAP server address |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * |
| * // if you want to use SSL: |
| * $options = new ezcMailImapTransportOptions(); |
| * $options->ssl = true; |
| * |
| * $imap = new ezcMailImapTransport( 'imap.example.com', null, $options ); |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if it was not possible to connect to the server |
| * @throws ezcBaseExtensionNotFoundException |
| * if trying to use SSL and the extension openssl is not installed |
| * @throws ezcBasePropertyNotFoundException |
| * if $options contains a property not defined |
| * @throws ezcBaseValueException |
| * if $options contains a property with a value not allowed |
| * @param string $server |
| * @param int $port |
| * @param ezcMailImapTransportOptions|array(string=>mixed) $options |
| */ |
| public function __construct( $server, $port = null, $options = array() ) |
| { |
| if ( $options instanceof ezcMailImapTransportOptions ) |
| { |
| $this->options = $options; |
| } |
| else if ( is_array( $options ) ) |
| { |
| $this->options = new ezcMailImapTransportOptions( $options ); |
| } |
| else |
| { |
| throw new ezcBaseValueException( "options", $options, "ezcMailImapTransportOptions|array" ); |
| } |
| |
| if ( $port === null ) |
| { |
| $port = ( $this->options->ssl === true ) ? 993 : 143; |
| } |
| $this->connection = new ezcMailTransportConnection( $server, $port, $this->options ); |
| // get the server greeting |
| $response = $this->connection->getLine(); |
| if ( strpos( $response, "* OK" ) === false ) |
| { |
| throw new ezcMailTransportException( "The connection to the IMAP server is ok, but a negative response from server was received. Try again later." ); |
| } |
| if ( strpos( $response, self::SERVER_GIMAP ) !== false ) |
| { |
| $this->serverType = self::SERVER_GIMAP; // otherwise it is null |
| } |
| $this->state = self::STATE_NOT_AUTHENTICATED; |
| } |
| |
| /** |
| * Destructs the IMAP transport. |
| * |
| * If there is an open connection to the IMAP server it is closed. |
| */ |
| public function __destruct() |
| { |
| try |
| { |
| $this->disconnect(); |
| } |
| catch ( ezcMailTransportException $e ) |
| { |
| // Ignore occuring transport exceptions. |
| } |
| } |
| |
| /** |
| * Sets the value of the property $name to $value. |
| * |
| * @throws ezcBasePropertyNotFoundException |
| * if the property $name does not exist |
| * @throws ezcBaseValueException |
| * if $value is not accepted for the property $name |
| * @param string $name |
| * @param mixed $value |
| * @ignore |
| */ |
| public function __set( $name, $value ) |
| { |
| switch ( $name ) |
| { |
| case 'options': |
| if ( !( $value instanceof ezcMailImapTransportOptions ) ) |
| { |
| throw new ezcBaseValueException( 'options', $value, 'instanceof ezcMailImapTransportOptions' ); |
| } |
| $this->options = $value; |
| break; |
| |
| default: |
| throw new ezcBasePropertyNotFoundException( $name ); |
| } |
| } |
| |
| /** |
| * Returns the value of the property $name. |
| * |
| * @throws ezcBasePropertyNotFoundException |
| * if the property $name does not exist |
| * @param string $name |
| * @ignore |
| */ |
| public function __get( $name ) |
| { |
| switch ( $name ) |
| { |
| case 'options': |
| return $this->options; |
| |
| default: |
| throw new ezcBasePropertyNotFoundException( $name ); |
| } |
| } |
| |
| /** |
| * Returns true if the property $name is set, otherwise false. |
| * |
| * @param string $name |
| * @return bool |
| * @ignore |
| */ |
| public function __isset( $name ) |
| { |
| switch ( $name ) |
| { |
| case 'options': |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Disconnects the transport from the IMAP server. |
| */ |
| public function disconnect() |
| { |
| if ( $this->state !== self::STATE_NOT_CONNECTED |
| && $this->connection->isConnected() === true ) |
| { |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} LOGOUT" ); |
| // discard the "bye bye" message ("{$tag} OK Logout completed.") |
| $this->getResponse( $tag ); |
| $this->state = self::STATE_LOGOUT; |
| $this->selectedMailbox = null; |
| |
| $this->connection->close(); |
| $this->connection = null; |
| $this->state = self::STATE_NOT_CONNECTED; |
| } |
| } |
| |
| /** |
| * Authenticates the user to the IMAP server with $user and $password. |
| * |
| * This method should be called directly after the construction of this |
| * object. |
| * |
| * If the server is waiting for the authentication process to respond, the |
| * connection with the IMAP server will be closed, and false is returned, |
| * and it is the application's task to reconnect and reauthenticate. |
| * |
| * Example of creating an IMAP transport and authenticating: |
| * <code> |
| * // replace with your IMAP server address |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * |
| * // replace the values with your username and password for the IMAP server |
| * $imap->authenticate( 'username', 'password' ); |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if already authenticated |
| * or if the provided username/password combination did not work |
| * @param string $user |
| * @param string $password |
| * @return bool |
| */ |
| public function authenticate( $user, $password ) |
| { |
| if ( $this->state != self::STATE_NOT_AUTHENTICATED ) |
| { |
| throw new ezcMailTransportException( "Tried to authenticate when there was no connection or when already authenticated." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} LOGIN {$user} {$password}" ); |
| $response = trim( $this->connection->getLine() ); |
| // hack for gmail, to fix issue #15837: imap.google.com (google gmail) changed IMAP response |
| if ( $this->serverType === self::SERVER_GIMAP && strpos( $response, "* CAPABILITY" ) === 0 ) |
| { |
| $response = trim( $this->connection->getLine() ); |
| } |
| if ( strpos( $response, '* OK' ) !== false ) |
| { |
| // the server is busy waiting for authentication process to |
| // respond, so it is a good idea to just close the connection, |
| // otherwise the application will be halted until the server |
| // recovers |
| $this->connection->close(); |
| $this->connection = null; |
| $this->state = self::STATE_NOT_CONNECTED; |
| return false; |
| } |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server did not accept the username and/or password: {$response}." ); |
| } |
| else |
| { |
| $this->state = self::STATE_AUTHENTICATED; |
| $this->selectedMailbox = null; |
| } |
| return true; |
| } |
| |
| /** |
| * Returns an array with the names of the available mailboxes for the user |
| * currently authenticated on the IMAP server. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully. |
| * |
| * For more information about $reference and $mailbox, consult |
| * the IMAP RFCs documents ({@link http://www.faqs.org/rfcs/rfc1730.html} |
| * or {@link http://www.faqs.org/rfcs/rfc2060.html}, section 7.2.2.). |
| * |
| * By default, $reference is "" and $mailbox is "*". |
| * |
| * The array returned contains the mailboxes available for the connected |
| * user on this IMAP server. Inbox is a special mailbox, and it can be |
| * specified upper-case or lower-case or mixed-case. The other mailboxes |
| * should be specified as they are (to the {@link selectMailbox()} method). |
| * |
| * Example of listing mailboxes: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * |
| * $mailboxes = $imap->listMailboxes(); |
| * </code> |
| * |
| * @throws ezcMailMailTransportException |
| * if the current server state is not accepted |
| * or if the server sent a negative response |
| * @param string $reference |
| * @param string $mailbox |
| * @return array(string) |
| */ |
| public function listMailboxes( $reference = '', $mailbox = '*' ) |
| { |
| if ( $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call listMailboxes() when not successfully logged in." ); |
| } |
| |
| $result = array(); |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} LIST \"{$reference}\" \"{$mailbox}\"" ); |
| $response = trim( $this->connection->getLine() ); |
| while ( strpos( $response, '* LIST (' ) !== false ) |
| { |
| // only consider the selectable mailboxes |
| if ( strpos( $response, "\\Noselect" ) === false ) |
| { |
| $response = substr( $response, strpos( $response, "\" " ) + 2 ); |
| $response = trim( $response ); |
| $response = trim( $response, "\"" ); |
| $result[] = $response; |
| |
| } |
| $response = $this->connection->getLine(); |
| } |
| |
| $response = $this->getResponse( $tag, $response ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "Could not list mailboxes with the parameters '\"{$reference}\"' and '\"{$mailbox}\"': {$response}." ); |
| } |
| return $result; |
| } |
| |
| /** |
| * Returns the hierarchy delimiter of the IMAP server, useful for handling |
| * nested IMAP folders. |
| * |
| * For more information about the hierarchy delimiter, consult the IMAP RFCs |
| * {@link http://www.faqs.org/rfcs/rfc1730.html} or |
| * {@link http://www.faqs.org/rfcs/rfc2060.html}, section 6.3.8. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully. |
| * |
| * Example of returning the hierarchy delimiter: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * |
| * $delimiter = $imap->getDelimiter(); |
| * </code> |
| * |
| * After running the above code, $delimiter should be something like "/". |
| * |
| * @throws ezcMailMailTransportException |
| * if the current server state is not accepted |
| * or if the server sent a negative response |
| * @return string |
| */ |
| public function getHierarchyDelimiter() |
| { |
| if ( $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call getDelimiter() when not successfully logged in." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} LIST \"\" \"\"" ); |
| |
| // there should be only one * LIST response line from IMAP |
| $response = trim( $this->getResponse( '* LIST' ) ); |
| $parts = explode( '"', $response ); |
| |
| if ( count( $parts ) >= 2 ) |
| { |
| $result = $parts[1]; |
| } |
| else |
| { |
| throw new ezcMailTransportException( "Could not retrieve the hierarchy delimiter: {$response}." ); |
| } |
| |
| $response = $this->getResponse( $tag, $response ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "Could not retrieve the hierarchy delimiter: {$response}." ); |
| } |
| return $result; |
| } |
| |
| /** |
| * Selects the mailbox $mailbox, which will be the active mailbox for the |
| * subsequent commands until it is changed. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully. |
| * |
| * Inbox is a special mailbox and can be specified with any case. |
| * |
| * This method should be called after authentication, and before fetching |
| * any messages. |
| * |
| * Example of selecting a mailbox: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * |
| * $imap->selectMailbox( 'Reports 2006' ); |
| * </code> |
| * |
| * @throws ezcMailMailTransportException |
| * if the current server state is not accepted |
| * or if the server sent a negative response |
| * @param string $mailbox |
| * @param bool $readOnly |
| */ |
| public function selectMailbox( $mailbox, $readOnly = false ) |
| { |
| if ( $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call selectMailbox() when not successfully logged in." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| |
| // if the mailbox selection will be successful, $state will be STATE_SELECTED |
| // or STATE_SELECTED_READONLY, depending on the $readOnly parameter |
| if ( $readOnly !== true ) |
| { |
| $this->connection->sendData( "{$tag} SELECT \"{$mailbox}\"" ); |
| $state = self::STATE_SELECTED; |
| } |
| else |
| { |
| $this->connection->sendData( "{$tag} EXAMINE \"{$mailbox}\"" ); |
| $state = self::STATE_SELECTED_READONLY; |
| } |
| |
| // if the selecting of the mailbox fails (with "NO" or "BAD" response |
| // from the server), $state reverts to STATE_AUTHENTICATED |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) == self::RESPONSE_OK ) |
| { |
| $this->state = $state; |
| $this->selectedMailbox = $mailbox; |
| } |
| else |
| { |
| $this->state = self::STATE_AUTHENTICATED; |
| $this->selectedMailbox = null; |
| throw new ezcMailTransportException( "Could not select mailbox '{$mailbox}': {$response}." ); |
| } |
| } |
| |
| /** |
| * Creates the mailbox $mailbox. |
| * |
| * Inbox cannot be created. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully. |
| * |
| * @throws ezcMailTransportException |
| * if the current server state is not accepted |
| * or if the server sent a negative response |
| * @param string $mailbox |
| * @return bool |
| */ |
| public function createMailbox( $mailbox ) |
| { |
| if ( $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call createMailbox() when not successfully logged in." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} CREATE \"{$mailbox}\"" ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not create mailbox '{$mailbox}': {$response}." ); |
| } |
| return true; |
| } |
| |
| /** |
| * Renames the mailbox $mailbox to $newName. |
| * |
| * Inbox cannot be renamed. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully. |
| * |
| * @throws ezcMailTransportException |
| * if the current server state is not accepted |
| * or if trying to rename the currently selected mailbox |
| * or if the server sent a negative response |
| * @param string $mailbox |
| * @param string $newName |
| * @return bool |
| */ |
| public function renameMailbox( $mailbox, $newName ) |
| { |
| if ( $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call renameMailbox() when not successfully logged in." ); |
| } |
| |
| if ( strtolower( $this->selectedMailbox ) == strtolower( $mailbox ) ) |
| { |
| throw new ezcMailTransportException( "Can't rename the currently selected mailbox." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} RENAME \"{$mailbox}\" \"{$newName}\"" ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not rename the mailbox '{$mailbox}' to '{$newName}': {$response}." ); |
| } |
| return true; |
| } |
| |
| /** |
| * Deletes the mailbox $mailbox. |
| * |
| * Inbox and the the currently selected mailbox cannot be deleted. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully. |
| * |
| * @throws ezcMailTransportException |
| * if the current server state is not accepted |
| * or if trying to delete the currently selected mailbox |
| * or if the server sent a negative response |
| * @param string $mailbox |
| * @return bool |
| */ |
| public function deleteMailbox( $mailbox ) |
| { |
| if ( $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call deleteMailbox() when not successfully logged in." ); |
| } |
| |
| if ( strtolower( $this->selectedMailbox ) == strtolower( $mailbox ) ) |
| { |
| throw new ezcMailTransportException( "Can't delete the currently selected mailbox." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} DELETE \"{$mailbox}\"" ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not delete the mailbox '{$mailbox}': {$response}." ); |
| } |
| return true; |
| } |
| |
| /** |
| * Copies message(s) from the currently selected mailbox to mailbox |
| * $destination. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * Warning! When using unique IDs referencing and trying to copy a message |
| * with an ID that does not exist, this method will not throw an exception. |
| * |
| * @todo Find out if it is possible to catch this IMAP bug. |
| * |
| * $messages can be: |
| * - a single message number (eg: '1') |
| * - a message range (eg. '1:4') |
| * - a message list (eg. '1,2,4') |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected (the mailbox from which messages will be copied). |
| * |
| * Example of copying 3 messages to a mailbox: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'Inbox' ); |
| * |
| * $imap->copyMessages( '1,2,4', 'Reports 2006' ); |
| * </code> |
| * |
| * The above code will copy the messages with numbers 1, 2 and 4 from Inbox |
| * to Reports 2006. |
| * |
| * @throws ezcMailTransportException |
| * if the current server state is not accepted |
| * or if the server sent a negative response |
| * @param string $messages |
| * @param string $destination |
| * @return bool |
| */ |
| public function copyMessages( $messages, $destination ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call copyMessages() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}COPY {$messages} \"{$destination}\"" ); |
| |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not copy '{$messages}' to '{$destination}': {$response}." ); |
| } |
| return true; |
| } |
| |
| /** |
| * Returns a list of the not deleted messages in the current mailbox. |
| * |
| * It returns only the messages with the flag DELETED not set. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * The format of the returned array is |
| * <code> |
| * array( message_id => size ); |
| * </code> |
| * |
| * Example: |
| * <code> |
| * array( 2 => 1700, 5 => 1450, 6 => 21043 ); |
| * </code> |
| * |
| * If $contentType is set, it returns only the messages with |
| * $contentType in the Content-Type header. |
| * |
| * For example $contentType can be "multipart/mixed" to return only the |
| * messages with attachments. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param string $contentType |
| * @return array(int) |
| */ |
| public function listMessages( $contentType = null ) |
| { |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call listMessages() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $messageList = array(); |
| $messages = array(); |
| |
| // get the numbers of the existing messages |
| $tag = $this->getNextTag(); |
| $command = "{$tag} SEARCH UNDELETED"; |
| if ( !is_null( $contentType ) ) |
| { |
| $command .= " HEADER \"Content-Type\" \"{$contentType}\""; |
| } |
| $this->connection->sendData( $command ); |
| $response = trim( $this->getResponse( '* SEARCH' ) ); |
| if ( strpos( $response, '* SEARCH' ) !== false ) |
| { |
| $ids = trim( substr( $response, 9 ) ); |
| if ( $ids !== "" ) |
| { |
| $messageList = explode( ' ', $ids ); |
| } |
| } |
| // skip the OK response ("{$tag} OK Search completed.") |
| $response = trim( $this->getResponse( $tag, $response ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not list messages: {$response}." ); |
| } |
| |
| if ( !empty( $messageList ) ) |
| { |
| // get the sizes of the messages |
| $tag = $this->getNextTag(); |
| $query = trim( implode( ',', $messageList ) ); |
| $this->connection->sendData( "{$tag} FETCH {$query} RFC822.SIZE" ); |
| $response = $this->getResponse( 'FETCH (' ); |
| $currentMessage = trim( reset( $messageList ) ); |
| while ( strpos( $response, 'FETCH (' ) !== false ) |
| { |
| $line = $response; |
| $line = explode( ' ', $line ); |
| $line = trim( $line[count( $line ) - 1] ); |
| $line = substr( $line, 0, strlen( $line ) - 1 ); |
| $messages[$currentMessage] = intval( $line ); |
| $currentMessage = next( $messageList ); |
| $response = $this->connection->getLine(); |
| } |
| // skip the OK response ("{$tag} OK Fetch completed.") |
| $response = trim( $this->getResponse( $tag, $response ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not list messages: {$response}." ); |
| } |
| } |
| return $messages; |
| } |
| |
| /** |
| * Fetches the sizes in bytes for messages $messages. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $messages is an array of message numbers, for example: |
| * <code> |
| * array( 1, 2, 4 ); |
| * </code> |
| * |
| * The format of the returned array is: |
| * <code> |
| * array( message_number => size ) |
| * </code> |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * $sizes = $imap->fetchSizes( array( 1, 2, 4 ) ); |
| * </code> |
| * |
| * The returned array $sizes will be something like: |
| * <code> |
| * array( 1 => 1043, |
| * 2 => 203901, |
| * 4 => 14277 |
| * ); |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param array $messages |
| * @return array(int) |
| */ |
| public function fetchSizes( $messages ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call fetchSizes() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $sizes = array(); |
| $ids = implode( $messages, ',' ); |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}FETCH {$ids} (RFC822.SIZE)" ); |
| |
| $response = trim( $this->connection->getLine() ); |
| while ( strpos( $response, $tag ) === false ) |
| { |
| if ( strpos( $response, ' FETCH (' ) !== false ) |
| { |
| if ( $this->options->uidReferencing ) |
| { |
| preg_match( '/\*\s.*\sFETCH\s\(RFC822\.SIZE\s(.*)\sUID\s(.*)\)/U', $response, $matches ); |
| $sizes[intval( $matches[2] )] = (int) $matches[1]; |
| } |
| else |
| { |
| preg_match( '/\*\s(.*)\sFETCH\s\(RFC822\.SIZE\s(.*)\)/U', $response, $matches ); |
| $sizes[intval( $matches[1] )] = (int) $matches[2]; |
| } |
| |
| } |
| $response = trim( $this->connection->getLine() ); |
| } |
| |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not fetch flags for the messages '{$messages}': {$response}." ); |
| } |
| return $sizes; |
| } |
| |
| /** |
| * Returns information about the messages in the current mailbox. |
| * |
| * The information returned through the parameters is: |
| * - $numMessages = number of not deleted messages in the selected mailbox |
| * - $sizeMessages = sum of the not deleted messages sizes |
| * - $recent = number of recent and not deleted messages |
| * - $unseen = number of unseen and not deleted messages |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example of returning the status of the currently selected mailbox: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'Inbox' ); |
| * |
| * $imap->status( $numMessages, $sizeMessages, $recent, $unseen ); |
| * </code> |
| * |
| * After running the above code, $numMessages, $sizeMessages, $recent |
| * and $unseen will be populated with values. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param int &$numMessages |
| * @param int &$sizeMessages |
| * @param int &$recent |
| * @param int &$unseen |
| * @return bool |
| */ |
| public function status( &$numMessages, &$sizeMessages, &$recent = 0, &$unseen = 0 ) |
| { |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call status() on the IMAP transport when a mailbox is not selected." ); |
| } |
| $messages = $this->listMessages(); |
| $numMessages = count( $messages ); |
| $sizeMessages = array_sum( $messages ); |
| $messages = array_keys( $messages ); |
| $recentMessages = array_intersect( $this->searchByFlag( "RECENT" ), $messages ); |
| $unseenMessages = array_intersect( $this->searchByFlag( "UNSEEN" ), $messages ); |
| $recent = count( $recentMessages ); |
| $unseen = count( $unseenMessages ); |
| return true; |
| } |
| |
| /** |
| * Deletes the message with the message number $msgNum from the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * The message number $msgNum must be a valid identifier fetched with e.g. |
| * {@link listMessages()}. |
| * |
| * The message is not physically deleted, but has its DELETED flag set, |
| * and can be later undeleted by clearing its DELETED flag with |
| * {@link clearFlag()}. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param int $msgNum |
| * @return bool |
| */ |
| public function delete( $msgNum ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED ) |
| { |
| throw new ezcMailTransportException( "Can't call delete() when a mailbox is not selected." ); |
| } |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}STORE {$msgNum} +FLAGS (\\Deleted)" ); |
| |
| // get the response (should be "{$tag} OK Store completed.") |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not delete the message '{$msgNum}': {$response}." ); |
| } |
| return true; |
| } |
| |
| /** |
| * Returns the headers and the first characters from message $msgNum, |
| * without setting the SEEN flag. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * If the command failed or if it was not supported by the server an empty |
| * string is returned. |
| * |
| * This method is useful for retrieving the headers of messages from the |
| * mailbox as strings, which can be later parsed with {@link ezcMailParser} |
| * and {@link ezcMailVariableSet}. In this way the retrieval of the full |
| * messages from the server is avoided when building a list of messages. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example of listing the mail headers of all the messages in the current |
| * mailbox: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'Inbox' ); |
| * |
| * $parser = new ezcMailParser(); |
| * $messages = $imap->listMessages(); |
| * foreach ( $messages as $messageNr => $size ) |
| * { |
| * $set = new ezcMailVariableSet( $imap->top( $messageNr ) ); |
| * $mail = $parser->parseMail( $set ); |
| * $mail = $mail[0]; |
| * echo "From: {$mail->from}, Subject: {$mail->subject}, Size: {$size}\n"; |
| * } |
| * </code> |
| * |
| * For a more advanced example see the "Mail listing example" in the online |
| * documentation. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param int $msgNum |
| * @param int $chars |
| * @return string |
| */ |
| public function top( $msgNum, $chars = 0 ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call top() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| |
| if ( $chars === 0 ) |
| { |
| $command = "{$tag} {$uid}FETCH {$msgNum} (BODY.PEEK[HEADER] BODY.PEEK[TEXT])"; |
| } |
| else |
| { |
| $command = "{$tag} {$uid}FETCH {$msgNum} (BODY.PEEK[HEADER] BODY.PEEK[TEXT]<0.{$chars}>)"; |
| } |
| $this->connection->sendData( $command ); |
| if ( $this->options->uidReferencing ) |
| { |
| // special case (BUG?) where "UID FETCH {$msgNum}" returns nothing |
| $response = trim( $this->connection->getLine() ); |
| if ( $this->responseType( $response ) === self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not fetch the message '{$msgNum}': {$response}." ); |
| } |
| } |
| else |
| { |
| $response = $this->getResponse( 'FETCH (' ); |
| } |
| $message = ""; |
| if ( strpos( $response, 'FETCH (' ) !== false ) |
| { |
| // Added hack for issue #14360: problems with $imap->top() command in gmail. |
| if ( $this->serverType === self::SERVER_GIMAP ) |
| { |
| // Google IMAP servers return the body first, then the headers (!) |
| $bytesToRead = $this->getMessageSectionSize( $response ); |
| $response = ""; |
| while ( $bytesToRead >= 0 ) |
| { |
| $data = $this->connection->getLine(); |
| $lastResponse = $data; |
| $bytesToRead -= strlen( $data ); |
| |
| // in case reading too much and the string "BODY[HEADER] {size}" |
| // is at the end of the last line |
| if ( $bytesToRead <= 0 ) |
| { |
| if ( $bytesToRead < 0 ) |
| { |
| $lastResponse = substr( $data, $bytesToRead ); |
| $data = substr( $data, 0, strlen( $data ) + $bytesToRead ); |
| } |
| } |
| $message .= $data; |
| } |
| |
| // Read the headers |
| $headers = ''; |
| $response = $this->connection->getLine(); |
| $bytesToRead = $this->getMessageSectionSize( $lastResponse ); |
| |
| $response = $this->connection->getLine(); |
| while ( strpos( $response, $tag ) === false ) |
| { |
| $headers .= $response; |
| $response = $this->connection->getLine(); |
| } |
| $headers = trim( $headers, ")\r\n" ); |
| |
| // Append the body AFTER the headers as it should be |
| $message = $headers . "\r\n\r\n" . $message; |
| } |
| else |
| { |
| // Other IMAP servers return the headers first, then the body |
| $response = ""; |
| while ( strpos( $response, 'BODY[TEXT]' ) === false ) |
| { |
| $message .= $response; |
| $response = $this->connection->getLine(); |
| } |
| |
| $response = $this->connection->getLine(); |
| while ( strpos( $response, $tag ) === false ) |
| { |
| $message .= $response; |
| $response = $this->connection->getLine(); |
| } |
| } |
| } |
| // skip the OK response ("{$tag} OK Fetch completed.") |
| $response = trim( $this->getResponse( $tag, $response ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not fetch the message '{$msgNum}': {$response}." ); |
| } |
| return $message; |
| } |
| |
| /** |
| * Returns the unique identifiers for the messages from the current mailbox. |
| * |
| * You can fetch the unique identifier for a specific message by |
| * providing the $msgNum parameter. |
| * |
| * The unique identifier can be used to recognize mail from servers |
| * between requests. In contrast to the message numbers the unique |
| * numbers assigned to an email usually never changes. |
| * |
| * The format of the returned array is: |
| * <code> |
| * array( message_num => unique_id ); |
| * </code> |
| * |
| * Example: |
| * <code> |
| * array( 1 => 216, 2 => 217, 3 => 218, 4 => 219 ); |
| * </code> |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * @todo add UIVALIDITY value to UID (like in POP3) (if necessary). |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param int $msgNum |
| * @return array(string) |
| */ |
| public function listUniqueIdentifiers( $msgNum = null ) |
| { |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call listUniqueIdentifiers() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $result = array(); |
| if ( $msgNum !== null ) |
| { |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} UID SEARCH {$msgNum}" ); |
| $response = $this->getResponse( '* SEARCH' ); |
| if ( strpos( $response, '* SEARCH' ) !== false ) |
| { |
| $result[(int)$msgNum] = trim( substr( $response, 9 ) ); |
| } |
| $response = trim( $this->getResponse( $tag, $response ) ); |
| } |
| else |
| { |
| $uids = array(); |
| $messages = array_keys( $this->listMessages() ); |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} UID SEARCH UNDELETED" ); |
| $response = $this->getResponse( '* SEARCH' ); |
| if ( strpos( $response, '* SEARCH' ) !== false ) |
| { |
| $response = trim( substr( $response, 9 ) ); |
| if ( $response !== "" ) |
| { |
| $uids = explode( ' ', $response ); |
| } |
| for ( $i = 0; $i < count( $messages ); $i++ ) |
| { |
| $result[trim( $messages[$i] )] = $uids[$i]; |
| } |
| } |
| $response = trim( $this->getResponse( $tag ) ); |
| } |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not fetch the unique identifiers: {$response}." ); |
| } |
| return $result; |
| } |
| |
| /** |
| * Returns an {@link ezcMailImapSet} with all the messages from the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * If $deleteFromServer is set to true the mail will be marked for deletion |
| * after retrieval. If not it will be left intact. |
| * |
| * The set returned can be parsed with {@link ezcMailParser}. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'Inbox' ); |
| * |
| * $set = $imap->fetchAll(); |
| * |
| * // parse $set with ezcMailParser |
| * $parser = new ezcMailParser(); |
| * $mails = $parser->parseMail( $set ); |
| * foreach ( $mails as $mail ) |
| * { |
| * // process $mail which is an ezcMail object |
| * } |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param bool $deleteFromServer |
| * @return ezcMailParserSet |
| */ |
| public function fetchAll( $deleteFromServer = false ) |
| { |
| if ( $this->options->uidReferencing ) |
| { |
| $messages = array_values( $this->listUniqueIdentifiers() ); |
| } |
| else |
| { |
| $messages = array_keys( $this->listMessages() ); |
| } |
| |
| return new ezcMailImapSet( $this->connection, $messages, $deleteFromServer, array( 'uidReferencing' => $this->options->uidReferencing ) ); |
| } |
| |
| /** |
| * Returns an {@link ezcMailImapSet} containing only the $number -th message in |
| * the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * If $deleteFromServer is set to true the mail will be marked for deletion |
| * after retrieval. If not it will be left intact. |
| * |
| * Note: for IMAP the first message is 1 (so for $number = 0 an exception |
| * will be thrown). |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'Inbox' ); |
| * |
| * $set = $imap->fetchByMessageNr( 1 ); |
| * |
| * // $set can be parsed with ezcMailParser |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @throws ezcMailNoSuchMessageException |
| * if the message $number is out of range |
| * @param int $number |
| * @param bool $deleteFromServer |
| * @return ezcMailImapSet |
| */ |
| public function fetchByMessageNr( $number, $deleteFromServer = false ) |
| { |
| if ( $this->options->uidReferencing ) |
| { |
| $messages = array_flip( $this->listUniqueIdentifiers() ); |
| } |
| else |
| { |
| $messages = $this->listMessages(); |
| } |
| |
| if ( !isset( $messages[$number] ) ) |
| { |
| throw new ezcMailNoSuchMessageException( $number ); |
| } |
| |
| return new ezcMailImapSet( $this->connection, array( 0 => $number ), $deleteFromServer, array( 'uidReferencing' => $this->options->uidReferencing ) ); |
| } |
| |
| /** |
| * Returns an {@link ezcMailImapSet} with $count messages starting from $offset from |
| * the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * Fetches $count messages starting from the $offset and returns them as a |
| * {@link ezcMailImapSet}. If $count is not specified or if it is 0, it fetches |
| * all messages starting from the $offset. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'Inbox' ); |
| * |
| * $set = $imap->fetchFromOffset( 1, 10 ); |
| * |
| * // $set can be parsed with ezcMailParser |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @throws ezcMailInvalidLimitException |
| * if $count is negative |
| * @throws ezcMailOffsetOutOfRangeException |
| * if $offset is outside of the existing range of messages |
| * @param int $offset |
| * @param int $count |
| * @param bool $deleteFromServer |
| * @return ezcMailImapSet |
| */ |
| public function fetchFromOffset( $offset, $count = 0, $deleteFromServer = false ) |
| { |
| if ( $count < 0 ) |
| { |
| throw new ezcMailInvalidLimitException( $offset, $count ); |
| } |
| |
| if ( $this->options->uidReferencing ) |
| { |
| $messages = array_values( $this->listUniqueIdentifiers() ); |
| $ids = array_flip( $messages ); |
| |
| if ( $count === 0 ) |
| { |
| $count = count( $messages ); |
| } |
| |
| if ( !isset( $ids[$offset] ) ) |
| { |
| throw new ezcMailOffsetOutOfRangeException( $offset, $count ); |
| } |
| |
| $range = array(); |
| for ( $i = $ids[$offset]; $i < min( $count, count( $messages ) ); $i++ ) |
| { |
| $range[] = $messages[$i]; |
| } |
| } |
| else |
| { |
| $messages = array_keys( $this->listMessages() ); |
| |
| if ( $count === 0 ) |
| { |
| $count = count( $messages ); |
| } |
| |
| $range = array_slice( $messages, $offset - 1, $count, true ); |
| |
| if ( !isset( $range[$offset - 1] ) ) |
| { |
| throw new ezcMailOffsetOutOfRangeException( $offset, $count ); |
| } |
| } |
| |
| return new ezcMailImapSet( $this->connection, $range, $deleteFromServer, array( 'uidReferencing' => $this->options->uidReferencing ) ); |
| } |
| |
| /** |
| * Returns an {@link ezcMailImapSet} containing the messages which match the |
| * provided $criteria from the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * See {@link http://www.faqs.org/rfcs/rfc1730.html} - 6.4.4. (or |
| * {@link http://www.faqs.org/rfcs/rfc1730.html} - 6.4.4.) for criterias |
| * which can be used for searching. The criterias can be combined in the |
| * same search string (separate the criterias with spaces). |
| * |
| * If $criteria is null or empty then it will default to 'ALL' (returns all |
| * messages in the mailbox). |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Examples: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * // return an ezcMailImapSet containing all messages flagged as 'SEEN' |
| * $set = $imap->searchMailbox( 'SEEN' ); |
| * |
| * // return an ezcMailImapSet containing messages with 'release' in their Subject |
| * $set = $imap->searchMailbox( 'SUBJECT "release"' ); |
| * |
| * // criterias can be combined: |
| * // return an ezcMailImapSet containing messages flagged as 'SEEN' and |
| * // with 'release' in their Subject |
| * $set = $imap->searchMailbox( 'SEEN SUBJECT "release"' ); |
| * |
| * // $set can be parsed with ezcMailParser |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param string $criteria |
| * @return ezcMailImapSet |
| */ |
| public function searchMailbox( $criteria = null ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call searchMailbox() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $criteria = trim( $criteria ); |
| if ( empty( $criteria ) ) |
| { |
| $criteria = 'ALL'; |
| } |
| |
| $matchingMessages = array(); |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}SEARCH {$criteria}" ); |
| |
| $response = $this->getResponse( '* SEARCH' ); |
| if ( strpos( $response, '* SEARCH' ) !== false ) |
| { |
| $ids = substr( trim( $response ), 9 ); |
| if ( trim( $ids ) !== "" ) |
| { |
| $matchingMessages = explode( ' ', $ids ); |
| } |
| } |
| |
| $response = trim( $this->getResponse( $tag, $response ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not search the messages by the specified criteria: {$response}." ); |
| } |
| |
| return new ezcMailImapSet( $this->connection, array_values( $matchingMessages ), false, array( 'uidReferencing' => $this->options->uidReferencing ) ); |
| } |
| |
| /** |
| * Returns an {@link ezcMailImapSet} containing $count messages starting |
| * from $offset sorted by $sortCriteria from the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * It is useful for paging through a mailbox. |
| * |
| * Fetches $count messages starting from the $offset and returns them as a |
| * {@link ezcMailImapSet}. If $count is is 0, it fetches all messages |
| * starting from the $offset. |
| * |
| * $sortCriteria is an email header like: Subject, To, From, Date, Sender, etc. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * // Fetch a range of messages sorted by Date |
| * $set = $imap->sortFromOffset( 1, 10, "Date" ); |
| * |
| * // $set can be parsed with ezcMailParser |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @throws ezcMailInvalidLimitException |
| * if $count is negative |
| * @throws ezcMailOffsetOutOfRangeException |
| * if $offset is outside of the existing range of messages |
| * @param int $offset |
| * @param int $count |
| * @param string $sortCriteria |
| * @param bool $reverse |
| * @return ezcMailImapSet |
| */ |
| public function sortFromOffset( $offset, $count = 0, $sortCriteria, $reverse = false ) |
| { |
| if ( $count < 0 ) |
| { |
| throw new ezcMailInvalidLimitException( $offset, $count ); |
| } |
| |
| $range = array(); |
| if ( $this->options->uidReferencing ) |
| { |
| $uids = array_values( $this->listUniqueIdentifiers() ); |
| |
| $flip = array_flip( $uids ); |
| if ( !isset( $flip[$offset] ) ) |
| { |
| throw new ezcMailOffsetOutOfRangeException( $offset, $count ); |
| } |
| |
| $start = $flip[$offset]; |
| |
| $messages = $this->sort( $uids, $sortCriteria, $reverse ); |
| |
| if ( $count === 0 ) |
| { |
| $count = count( $messages ); |
| } |
| |
| $ids = array_keys( $messages ); |
| |
| for ( $i = $start; $i < $count; $i++ ) |
| { |
| $range[] = $ids[$i]; |
| } |
| } |
| else |
| { |
| $messageCount = $this->countByFlag( 'ALL' ); |
| $messages = array_keys( $this->sort( range( 1, $messageCount ), $sortCriteria, $reverse ) ); |
| |
| if ( $count === 0 ) |
| { |
| $count = count( $messages ); |
| } |
| |
| $range = array_slice( $messages, $offset - 1, $count, true ); |
| |
| if ( !isset( $range[$offset - 1] ) ) |
| { |
| throw new ezcMailOffsetOutOfRangeException( $offset, $count ); |
| } |
| } |
| |
| return new ezcMailImapSet( $this->connection, $range, false, array( 'uidReferencing' => $this->options->uidReferencing ) ); |
| } |
| |
| /** |
| * Returns an {@link ezcMailImapSet} containing messages $messages sorted by |
| * $sortCriteria from the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $messages is an array of message numbers, for example: |
| * <code> |
| * array( 1, 2, 4 ); |
| * </code> |
| * |
| * $sortCriteria is an email header like: Subject, To, From, Date, Sender, etc. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * // Fetch the list of messages sorted by Date |
| * $set = $imap->sortMessages( 1, 10, "Date" ); |
| * |
| * // $set can be parsed with ezcMailParser |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * or if array $messages is empty |
| * @param array(int) $messages |
| * @param string $sortCriteria |
| * @param bool $reverse |
| * @return ezcMailImapSet |
| */ |
| public function sortMessages( $messages, $sortCriteria, $reverse = false ) |
| { |
| $messages = $this->sort( $messages, $sortCriteria, $reverse ); |
| return new ezcMailImapSet( $this->connection, array_keys ( $messages ), false, array( 'uidReferencing' => $this->options->uidReferencing ) ); |
| } |
| |
| /** |
| * Returns an {@link ezcMailImapSet} containing messages with a certain flag from |
| * the current mailbox. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $flag can be one of: |
| * |
| * Basic flags: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - RECENT - message is recent |
| * - SEEN - message has been read |
| * |
| * Opposites of the above flags: |
| * - UNANSWERED |
| * - UNDELETED |
| * - UNDRAFT |
| * - UNFLAGGED |
| * - OLD |
| * - UNSEEN |
| * |
| * Composite flags: |
| * - NEW - equivalent to RECENT + UNSEEN |
| * - ALL - all the messages |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * // Fetch the messages marked with the RECENT flag |
| * $set = $imap->fetchByFlag( 'RECENT' ); |
| * |
| * // $set can be parsed with ezcMailParser |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * or if $flag is not valid |
| * @param string $flag |
| * @return ezcMailImapSet |
| */ |
| public function fetchByFlag( $flag ) |
| { |
| $messages = $this->searchByFlag( $flag ); |
| return new ezcMailImapSet( $this->connection, $messages, false, array( 'uidReferencing' => $this->options->uidReferencing ) ); |
| } |
| |
| /** |
| * Wrapper function to fetch count of messages by a certain flag. |
| * |
| * $flag can be one of: |
| * |
| * Basic flags: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - RECENT - message is recent |
| * - SEEN - message has been read |
| * |
| * Opposites of the above flags: |
| * - UNANSWERED |
| * - UNDELETED |
| * - UNDRAFT |
| * - UNFLAGGED |
| * - OLD |
| * - UNSEEN |
| * |
| * Composite flags: |
| * - NEW - equivalent to RECENT + UNSEEN |
| * - ALL - all the messages |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * or if $flag is not valid |
| * @param string $flag |
| * @return int |
| */ |
| public function countByFlag( $flag ) |
| { |
| $flag = $this->normalizeFlag( $flag ); |
| $messages = $this->searchByFlag( $flag ); |
| return count( $messages ); |
| } |
| |
| /** |
| * Fetches IMAP flags for messages $messages. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $messages is an array of message numbers, for example: |
| * <code> |
| * array( 1, 2, 4 ); |
| * </code> |
| * |
| * The format of the returned array is: |
| * <code> |
| * array( message_number => array( flags ) ) |
| * </code> |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * $flags = $imap->fetchFlags( array( 1, 2, 4 ) ); |
| * </code> |
| * |
| * The returned array $flags will be something like: |
| * <code> |
| * array( 1 => array( '\Seen' ), |
| * 2 => array( '\Seen' ), |
| * 4 => array( '\Seen', 'NonJunk' ) |
| * ); |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * @param array $messages |
| * @return array(mixed) |
| */ |
| public function fetchFlags( $messages ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call fetchFlags() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $flags = array(); |
| $ids = implode( $messages, ',' ); |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}FETCH {$ids} (FLAGS)" ); |
| |
| $response = trim( $this->connection->getLine() ); |
| while ( strpos( $response, $tag ) === false ) |
| { |
| if ( strpos( $response, ' FETCH (' ) !== false ) |
| { |
| if ( $this->options->uidReferencing ) |
| { |
| preg_match( '/\*\s.*\sFETCH\s\(FLAGS \((.*)\)\sUID\s(.*)\)/U', $response, $matches ); |
| $parts = explode( ' ', $matches[1] ); |
| $flags[intval( $matches[2] )] = $parts; |
| } |
| else |
| { |
| preg_match( '/\*\s(.*)\sFETCH\s\(FLAGS \((.*)\)/U', $response, $matches ); |
| $parts = explode( ' ', $matches[2] ); |
| $flags[intval( $matches[1] )] = $parts; |
| } |
| } |
| $response = trim( $this->connection->getLine() ); |
| } |
| |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not fetch flags for the messages '{$messages}': {$response}." ); |
| } |
| return $flags; |
| } |
| |
| /** |
| * Sets $flag on $messages. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $messages can be: |
| * - a single message number (eg. 1) |
| * - a message range (eg. 1:4) |
| * - a message list (eg. 1,2,4) |
| * |
| * $flag can be one of: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - SEEN - message has been read |
| * |
| * This function automatically adds the '\' in front of the flag when |
| * calling the server command. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * $imap->setFlag( '1:4', 'DRAFT' ); |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * or if $flag is not valid |
| * @param string $messages |
| * @param string $flag |
| * @return bool |
| */ |
| public function setFlag( $messages, $flag ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED ) |
| { |
| throw new ezcMailTransportException( "Can't call setFlag() when a mailbox is not selected." ); |
| } |
| |
| $flag = $this->normalizeFlag( $flag ); |
| if ( in_array( $flag, self::$basicFlags ) ) |
| { |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}STORE {$messages} +FLAGS (\\{$flag})" ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not set flag '{$flag}' on the messages '{$messages}': {$response}." ); |
| } |
| } |
| else |
| { |
| throw new ezcMailTransportException( "Flag '{$flag}' is not allowed for setting." ); |
| } |
| return true; |
| } |
| |
| /** |
| * Clears $flag from $messages. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $messages can be: |
| * - a single message number (eg. '1') |
| * - a message range (eg. '1:4') |
| * - a message list (eg. '1,2,4') |
| * |
| * $flag can be one of: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - SEEN - message has been read |
| * |
| * This function automatically adds the '\' in front of the flag when |
| * calling the server command. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * Example: |
| * <code> |
| * $imap = new ezcMailImapTransport( 'imap.example.com' ); |
| * $imap->authenticate( 'username', 'password' ); |
| * $imap->selectMailbox( 'mailbox' ); // Inbox or another mailbox |
| * |
| * $imap->clearFlag( '1:4', 'DRAFT' ); |
| * </code> |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * or if $flag is not valid |
| * @param string $messages |
| * @param string $flag |
| * @return bool |
| */ |
| public function clearFlag( $messages, $flag ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED ) |
| { |
| throw new ezcMailTransportException( "Can't call clearFlag() when a mailbox is not selected." ); |
| } |
| |
| $flag = $this->normalizeFlag( $flag ); |
| if ( in_array( $flag, self::$basicFlags ) ) |
| { |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}STORE {$messages} -FLAGS (\\{$flag})" ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not clear flag '{$flag}' on the messages '{$messages}': {$response}." ); |
| } |
| } |
| else |
| { |
| throw new ezcMailTransportException( "Flag '{$flag}' is not allowed for clearing." ); |
| } |
| return true; |
| } |
| |
| /** |
| * Returns an array of message numbers from the selected mailbox which have a |
| * certain flag set. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $flag can be one of: |
| * |
| * Basic flags: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - RECENT - message is recent |
| * - SEEN - message has been read |
| * |
| * Opposites of the above flags: |
| * - UNANSWERED |
| * - UNDELETED |
| * - UNDRAFT |
| * - UNFLAGGED |
| * - OLD |
| * - UNSEEN |
| * |
| * Composite flags: |
| * - NEW - equivalent to RECENT + UNSEEN |
| * - ALL - all the messages |
| * |
| * The returned array is something like this: |
| * <code> |
| * array( 0 => 1, 1 => 5 ); |
| * </code> |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * or if $flag is not valid |
| * @param string $flag |
| * @return array(int) |
| */ |
| protected function searchByFlag( $flag ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call searchByFlag() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $matchingMessages = array(); |
| $flag = $this->normalizeFlag( $flag ); |
| if ( in_array( $flag, self::$extendedFlags ) ) |
| { |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}SEARCH ({$flag})" ); |
| $response = $this->getResponse( '* SEARCH' ); |
| |
| if ( strpos( $response, '* SEARCH' ) !== false ) |
| { |
| $ids = substr( trim( $response ), 9 ); |
| if ( trim( $ids ) !== "" ) |
| { |
| $matchingMessages = explode( ' ', $ids ); |
| } |
| } |
| $response = trim( $this->getResponse( $tag, $response ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not search the messages by flags: {$response}." ); |
| } |
| } |
| else |
| { |
| throw new ezcMailTransportException( "Flag '{$flag}' is not allowed for searching." ); |
| } |
| return $matchingMessages; |
| } |
| |
| /** |
| * Sends a NOOP command to the server, use it to keep the connection alive. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established. |
| * |
| * @throws ezcMailTransportException |
| * if there was no connection to the server |
| * or if the server sent a negative response |
| */ |
| public function noop() |
| { |
| if ( $this->state != self::STATE_NOT_AUTHENTICATED && |
| $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can not issue NOOP command if not connected." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} NOOP" ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "NOOP failed: {$response}." ); |
| } |
| } |
| |
| /** |
| * Returns an array with the capabilities of the IMAP server. |
| * |
| * The returned array will be something like this: |
| * <code> |
| * array( 'IMAP4rev1', 'SASL-IR SORT', 'THREAD=REFERENCES', 'MULTIAPPEND', |
| * 'UNSELECT', 'LITERAL+', 'IDLE', 'CHILDREN', 'NAMESPACE', |
| * 'LOGIN-REFERRALS' |
| * ); |
| * </code> |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established. |
| * |
| * @throws ezcMailTransportException |
| * if there was no connection to the server |
| * or if the server sent a negative response |
| * @return array(string) |
| */ |
| public function capability() |
| { |
| if ( $this->state != self::STATE_NOT_AUTHENTICATED && |
| $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Trying to request capability when not connected to server." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} CAPABILITY" ); |
| |
| $response = $this->connection->getLine(); |
| while ( $this->responseType( $response ) != self::RESPONSE_UNTAGGED && |
| strpos( $response, '* CAPABILITY ' ) === false ) |
| { |
| $response = $this->connection->getLine(); |
| } |
| $result = trim( $response ); |
| |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server responded negative to the CAPABILITY command: {$response}." ); |
| } |
| |
| return explode( ' ', str_replace( '* CAPABILITY ', '', $result ) ); |
| } |
| |
| /** |
| * Sends an EXPUNGE command to the server. |
| * |
| * This method permanently deletes the messages marked for deletion by |
| * the method {@link delete()}. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox was not selected |
| * or if the server sent a negative response |
| */ |
| public function expunge() |
| { |
| if ( $this->state != self::STATE_SELECTED ) |
| { |
| throw new ezcMailTransportException( "Can not issue EXPUNGE command if a mailbox is not selected." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} EXPUNGE" ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "EXPUNGE failed: {$response}." ); |
| } |
| } |
| |
| /** |
| * Appends $mail to the $mailbox mailbox. |
| * |
| * Use this method to create email messages in a mailbox such as Sent or |
| * Draft. |
| * |
| * $flags is an array of flags to be set to the $mail (if provided): |
| * |
| * $flag can be one of: |
| * - ANSWERED - message has been answered |
| * - DELETED - message is marked to be deleted by later EXPUNGE |
| * - DRAFT - message is marked as a draft |
| * - FLAGGED - message is "flagged" for urgent/special attention |
| * - SEEN - message has been read |
| * |
| * This function automatically adds the '\' in front of each flag when |
| * calling the server command. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully. |
| * |
| * @throws ezcMailTransportException |
| * if user is not authenticated |
| * or if the server sent a negative response |
| * or if $mailbox does not exists |
| * @param string $mailbox |
| * @param string $mail |
| * @param array(string) $flags |
| */ |
| public function append( $mailbox, $mail, $flags = null ) |
| { |
| if ( $this->state != self::STATE_AUTHENTICATED && |
| $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call append() if not authenticated." ); |
| } |
| |
| $tag = $this->getNextTag(); |
| $mailSize = strlen( $mail ); |
| if ( !is_null( $flags ) ) |
| { |
| for ( $i = 0; $i < count( $flags ); $i++ ) |
| { |
| $flags[$i] = '\\' . $this->normalizeFlag( $flags[$i] ); |
| } |
| $flagList = implode( ' ', $flags ); |
| $command = "{$tag} APPEND {$mailbox} ({$flagList}) {{$mailSize}}"; |
| } |
| else |
| { |
| $command = "{$tag} APPEND {$mailbox} {{$mailSize}}"; |
| } |
| |
| $this->connection->sendData( $command ); |
| $response = trim( $this->connection->getLine() ); |
| |
| if ( strpos( $response, 'TRYCREATE' ) !== false ) |
| { |
| throw new ezcMailTransportException( "Mailbox does not exist: {$response}." ); |
| } |
| |
| if ( $this->responseType( $response ) == self::RESPONSE_FEEDBACK ) |
| { |
| $this->connection->sendData( $mail ); |
| $response = trim( $this->getResponse( $tag ) ); |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not append message to mailbox '{$mailbox}': {$response}." ); |
| } |
| } |
| elseif ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not append message to mailbox '{$mailbox}': {$response}." ); |
| } |
| } |
| |
| /** |
| * Clears $flag of unwanted characters and makes it uppercase. |
| * |
| * @param string $flag |
| * @return string |
| */ |
| protected function normalizeFlag( $flag ) |
| { |
| $flag = strtoupper( $flag ); |
| $flag = str_replace( '\\', '', $flag ); |
| return trim( $flag ); |
| } |
| |
| /** |
| * Sorts message numbers array $messages by the specified $sortCriteria. |
| * |
| * This method supports unique IDs instead of message numbers. See |
| * {@link ezcMailImapTransportOptions} for how to enable unique IDs |
| * referencing. |
| * |
| * $messages is an array of message numbers, for example: |
| * <code> |
| * array( 1, 2, 4 ); |
| * </code> |
| * |
| * $sortCriteria is an email header like: Subject, To, From, Date, Sender. |
| * |
| * The sorting is done with the php function natcasesort(). |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established and a user must be authenticated successfully, and a mailbox |
| * must be selected. |
| * |
| * @throws ezcMailTransportException |
| * if a mailbox is not selected |
| * or if the server sent a negative response |
| * or if the array $messages is empty |
| * @param array(int) $messages |
| * @param string $sortCriteria |
| * @param bool $reverse |
| * @return array(string) |
| */ |
| protected function sort( $messages, $sortCriteria, $reverse = false ) |
| { |
| $uid = ( $this->options->uidReferencing ) ? self::UID : self::NO_UID; |
| |
| if ( $this->state != self::STATE_SELECTED && |
| $this->state != self::STATE_SELECTED_READONLY ) |
| { |
| throw new ezcMailTransportException( "Can't call sort() on the IMAP transport when a mailbox is not selected." ); |
| } |
| |
| $result = array(); |
| $query = ucfirst( strtolower( $sortCriteria ) ); |
| $messageNumbers = implode( ',', $messages ); |
| |
| $tag = $this->getNextTag(); |
| $this->connection->sendData( "{$tag} {$uid}FETCH {$messageNumbers} (BODY.PEEK[HEADER.FIELDS ({$query})])" ); |
| |
| $response = trim( $this->connection->getLine() ); |
| while ( strpos( $response, $tag ) === false ) |
| { |
| if ( strpos( $response, ' FETCH (' ) !== false ) |
| { |
| if ( $this->options->uidReferencing ) |
| { |
| preg_match('/^\* [0-9]+ FETCH \(UID ([0-9]+)/', $response, $matches ); |
| } |
| else |
| { |
| preg_match('/^\* ([0-9]+) FETCH/', $response, $matches ); |
| } |
| $messageNumber = $matches[1]; |
| } |
| |
| if ( strpos( $response, $query ) !== false ) |
| { |
| $strippedResponse = trim( trim( str_replace( "{$query}: ", '', $response ) ), '"' ); |
| switch ( $query ) |
| { |
| case 'Date': |
| $strippedResponse = strtotime( $strippedResponse ); |
| break; |
| case 'Subject': |
| case 'From': |
| case 'Sender': |
| case 'To': |
| $strippedResponse = ezcMailTools::mimeDecode( $strippedResponse ); |
| break; |
| default: |
| break; |
| } |
| $result[$messageNumber] = $strippedResponse; |
| } |
| |
| // in case the mail doesn't have the $sortCriteria header (like junk mail missing Subject header) |
| if ( strpos( $response, ')' ) !== false && !isset( $result[$messageNumber] ) ) |
| { |
| $result[$messageNumber] = ''; |
| } |
| |
| $response = trim( $this->connection->getLine() ); |
| } |
| |
| if ( $this->responseType( $response ) != self::RESPONSE_OK ) |
| { |
| throw new ezcMailTransportException( "The IMAP server could not sort the messages: {$response}." ); |
| } |
| |
| if ( $reverse === true ) |
| { |
| natcasesort( $result ); |
| $result = array_reverse( $result, true ); |
| } |
| else |
| { |
| natcasesort( $result ); |
| } |
| return $result; |
| } |
| |
| /** |
| * Parses $line to return the response code. |
| * |
| * Returns one of the following: |
| * - {@link RESPONSE_OK} |
| * - {@link RESPONSE_NO} |
| * - {@link RESPONSE_BAD} |
| * - {@link RESPONSE_UNTAGGED} |
| * - {@link RESPONSE_FEEDBACK} |
| * |
| * @throws ezcMailTransportException |
| * if the IMAP response ($line) is not recognized |
| * @param string $line |
| * @return int |
| */ |
| protected function responseType( $line ) |
| { |
| if ( strpos( $line, 'OK ' ) !== false && strpos( $line, 'OK ' ) == 6 ) |
| { |
| return self::RESPONSE_OK; |
| } |
| if ( strpos( $line, 'NO ' ) !== false && strpos( $line, 'NO ' ) == 6 ) |
| { |
| return self::RESPONSE_NO; |
| } |
| if ( strpos( $line, 'BAD ' ) !== false && strpos( $line, 'BAD ' ) == 6 ) |
| { |
| return self::RESPONSE_BAD; |
| } |
| if ( strpos( $line, '* ' ) !== false && strpos( $line, '* ' ) == 0 ) |
| { |
| return self::RESPONSE_UNTAGGED; |
| } |
| if ( strpos( $line, '+ ' ) !== false && strpos( $line, '+ ' ) == 0 ) |
| { |
| return self::RESPONSE_FEEDBACK; |
| } |
| throw new ezcMailTransportException( "Unrecognized IMAP response in line: {$line}" ); |
| } |
| |
| /** |
| * Reads the responses from the server until encountering $tag. |
| * |
| * In IMAP, each command sent by the client is prepended with a |
| * alphanumeric tag like 'A1234'. The server sends the response |
| * to the client command as lines, and the last line in the response |
| * is prepended with the same tag, and it contains the status of |
| * the command completion ('OK', 'NO' or 'BAD'). |
| * |
| * Sometimes the server sends alerts and response lines from other |
| * commands before sending the tagged line, so this method just |
| * reads all the responses until it encounters $tag. |
| * |
| * It returns the tagged line to be processed by the calling method. |
| * |
| * If $response is specified, then it will not read the response |
| * from the server before searching for $tag in $response. |
| * |
| * Before calling this method, a connection to the IMAP server must be |
| * established. |
| * |
| * @param string $tag |
| * @param string $response |
| * @return string |
| */ |
| protected function getResponse( $tag, $response = null ) |
| { |
| if ( is_null( $response ) ) |
| { |
| $response = $this->connection->getLine(); |
| } |
| while ( strpos( $response, $tag ) === false ) |
| { |
| if ( strpos( $response, ' BAD ' ) !== false || |
| strpos( $response, ' NO ' ) !== false ) |
| { |
| break; |
| } |
| $response = $this->connection->getLine(); |
| } |
| return $response; |
| } |
| |
| /** |
| * Generates the next IMAP tag to prepend to client commands. |
| * |
| * The structure of the IMAP tag is Axxxx, where: |
| * - A is a letter (uppercase for conformity) |
| * - x is a digit from 0 to 9 |
| * |
| * example of generated tag: T5439 |
| * |
| * It uses the class variable $this->currentTag. |
| * |
| * Everytime it is called, the tag increases by 1. |
| * |
| * If it reaches the last tag, it wraps around to the first tag. |
| * |
| * By default, the first generated tag is A0001. |
| * |
| * @return string |
| */ |
| protected function getNextTag() |
| { |
| $tagLetter = substr( $this->currentTag, 0, 1 ); |
| $tagNumber = intval( substr( $this->currentTag, 1 ) ); |
| $tagNumber++; |
| if ( $tagLetter == 'Z' && $tagNumber == 10000 ) |
| { |
| $tagLetter = 'A'; |
| $tagNumber = 1; |
| } |
| if ( $tagNumber == 10000 ) |
| { |
| $tagLetter++; |
| $tagNumber = 0; |
| } |
| $this->currentTag = $tagLetter . sprintf( "%04s", $tagNumber ); |
| return $this->currentTag; |
| } |
| |
| /** |
| * Returns the size of a FETCH section in bytes. |
| * |
| * The section header looks like: * id FETCH (BODY[TEXT] {size} |
| * where size is the size in bytes and id is the message number or ID. |
| * |
| * Example: for " * 2 FETCH (BODY[TEXT] {377}" this function returns 377. |
| * |
| * @return int |
| */ |
| protected function getMessageSectionSize( $response ) |
| { |
| $size = 0; |
| preg_match( '/\{(.*)\}/', $response, $matches ); |
| if ( count( $matches ) > 0 ) |
| { |
| $size = (int) $matches[1]; |
| } |
| return $size; |
| } |
| } |
| ?> |