File indexing completed on 2024-12-22 05:36:51

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Mail
0017  * @subpackage Storage
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 
0024 /**
0025  * @see Zend_Mail_Storage_Abstract
0026  */
0027 // require_once 'Zend/Mail/Storage/Abstract.php';
0028 
0029 /**
0030  * @see Zend_Mail_Protocol_Imap
0031  */
0032 // require_once 'Zend/Mail/Protocol/Imap.php';
0033 
0034 /**
0035  * @see Zend_Mail_Storage_Writable_Interface
0036  */
0037 // require_once 'Zend/Mail/Storage/Writable/Interface.php';
0038 
0039 /**
0040  * @see Zend_Mail_Storage_Folder_Interface
0041  */
0042 // require_once 'Zend/Mail/Storage/Folder/Interface.php';
0043 
0044 /**
0045  * @see Zend_Mail_Storage_Folder
0046  */
0047 // require_once 'Zend/Mail/Storage/Folder.php';
0048 
0049 /**
0050  * @see Zend_Mail_Message
0051  */
0052 // require_once 'Zend/Mail/Message.php';
0053 
0054 /**
0055  * @see Zend_Mail_Storage
0056  */
0057 // require_once 'Zend/Mail/Storage.php';
0058 
0059 /**
0060  * @category   Zend
0061  * @package    Zend_Mail
0062  * @subpackage Storage
0063  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0064  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0065  */
0066 class Zend_Mail_Storage_Imap extends Zend_Mail_Storage_Abstract
0067                              implements Zend_Mail_Storage_Folder_Interface, Zend_Mail_Storage_Writable_Interface
0068 {
0069     // TODO: with an internal cache we could optimize this class, or create an extra class with
0070     // such optimizations. Especially the various fetch calls could be combined to one cache call
0071 
0072     /**
0073      * protocol handler
0074      * @var null|Zend_Mail_Protocol_Imap
0075      */
0076     protected $_protocol;
0077 
0078     /**
0079      * name of current folder
0080      * @var string
0081      */
0082     protected $_currentFolder = '';
0083 
0084     /**
0085      * imap flags to constants translation
0086      * @var array
0087      */
0088     protected static $_knownFlags = array('\Passed'   => Zend_Mail_Storage::FLAG_PASSED,
0089                                           '\Answered' => Zend_Mail_Storage::FLAG_ANSWERED,
0090                                           '\Seen'     => Zend_Mail_Storage::FLAG_SEEN,
0091                                           '\Unseen'   => Zend_Mail_Storage::FLAG_UNSEEN,
0092                                           '\Deleted'  => Zend_Mail_Storage::FLAG_DELETED,
0093                                           '\Draft'    => Zend_Mail_Storage::FLAG_DRAFT,
0094                                           '\Flagged'  => Zend_Mail_Storage::FLAG_FLAGGED);
0095 
0096     /**
0097      * map flags to search criterias
0098      * @var array
0099      */
0100     protected static $_searchFlags = array('\Recent'   => 'RECENT',
0101                                            '\Answered' => 'ANSWERED',
0102                                            '\Seen'     => 'SEEN',
0103                                            '\Unseen'   => 'UNSEEN',
0104                                            '\Deleted'  => 'DELETED',
0105                                            '\Draft'    => 'DRAFT',
0106                                            '\Flagged'  => 'FLAGGED');
0107 
0108     /**
0109      * Count messages all messages in current box
0110      *
0111      * @return int number of messages
0112      * @throws Zend_Mail_Storage_Exception
0113      * @throws Zend_Mail_Protocol_Exception
0114      */
0115     public function countMessages($flags = null)
0116     {
0117         if (!$this->_currentFolder) {
0118             /**
0119              * @see Zend_Mail_Storage_Exception
0120              */
0121             // require_once 'Zend/Mail/Storage/Exception.php';
0122             throw new Zend_Mail_Storage_Exception('No selected folder to count');
0123         }
0124 
0125         if ($flags === null) {
0126             return count($this->_protocol->search(array('ALL')));
0127         }
0128 
0129         $params = array();
0130         foreach ((array)$flags as $flag) {
0131             if (isset(self::$_searchFlags[$flag])) {
0132                 $params[] = self::$_searchFlags[$flag];
0133             } else {
0134                 $params[] = 'KEYWORD';
0135                 $params[] = $this->_protocol->escapeString($flag);
0136             }
0137         }
0138         return count($this->_protocol->search($params));
0139     }
0140 
0141     /**
0142      * get a list of messages with number and size
0143      *
0144      * @param int $id number of message
0145      * @return int|array size of given message of list with all messages as array(num => size)
0146      * @throws Zend_Mail_Protocol_Exception
0147      */
0148     public function getSize($id = 0)
0149     {
0150         if ($id) {
0151             return $this->_protocol->fetch('RFC822.SIZE', $id);
0152         }
0153         return $this->_protocol->fetch('RFC822.SIZE', 1, INF);
0154     }
0155 
0156     /**
0157      * Fetch a message
0158      *
0159      * @param int $id number of message
0160      * @return Zend_Mail_Message
0161      * @throws Zend_Mail_Protocol_Exception
0162      */
0163     public function getMessage($id)
0164     {
0165         $data = $this->_protocol->fetch(array('FLAGS', 'RFC822.HEADER'), $id);
0166         $header = $data['RFC822.HEADER'];
0167 
0168         $flags = array();
0169         foreach ($data['FLAGS'] as $flag) {
0170             $flags[] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag;
0171         }
0172 
0173         return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags));
0174     }
0175 
0176     /*
0177      * Get raw header of message or part
0178      *
0179      * @param  int               $id       number of message
0180      * @param  null|array|string $part     path to part or null for messsage header
0181      * @param  int               $topLines include this many lines with header (after an empty line)
0182      * @param  int $topLines include this many lines with header (after an empty line)
0183      * @return string raw header
0184      * @throws Zend_Mail_Protocol_Exception
0185      * @throws Zend_Mail_Storage_Exception
0186      */
0187     public function getRawHeader($id, $part = null, $topLines = 0)
0188     {
0189         if ($part !== null) {
0190             // TODO: implement
0191             /**
0192              * @see Zend_Mail_Storage_Exception
0193              */
0194             // require_once 'Zend/Mail/Storage/Exception.php';
0195             throw new Zend_Mail_Storage_Exception('not implemented');
0196         }
0197 
0198         // TODO: toplines
0199         return $this->_protocol->fetch('RFC822.HEADER', $id);
0200     }
0201 
0202     /*
0203      * Get raw content of message or part
0204      *
0205      * @param  int               $id   number of message
0206      * @param  null|array|string $part path to part or null for messsage content
0207      * @return string raw content
0208      * @throws Zend_Mail_Protocol_Exception
0209      * @throws Zend_Mail_Storage_Exception
0210      */
0211     public function getRawContent($id, $part = null)
0212     {
0213         if ($part !== null) {
0214             // TODO: implement
0215             /**
0216              * @see Zend_Mail_Storage_Exception
0217              */
0218             // require_once 'Zend/Mail/Storage/Exception.php';
0219             throw new Zend_Mail_Storage_Exception('not implemented');
0220         }
0221 
0222         return $this->_protocol->fetch('RFC822.TEXT', $id);
0223     }
0224 
0225     /**
0226      * create instance with parameters
0227      * Supported paramters are
0228      *   - user username
0229      *   - host hostname or ip address of IMAP server [optional, default = 'localhost']
0230      *   - password password for user 'username' [optional, default = '']
0231      *   - port port for IMAP server [optional, default = 110]
0232      *   - ssl 'SSL' or 'TLS' for secure sockets
0233      *   - folder select this folder [optional, default = 'INBOX']
0234      *
0235      * @param  array $params mail reader specific parameters
0236      * @throws Zend_Mail_Storage_Exception
0237      * @throws Zend_Mail_Protocol_Exception
0238      */
0239     public function __construct($params)
0240     {
0241         if (is_array($params)) {
0242             $params = (object)$params;
0243         }
0244 
0245         $this->_has['flags'] = true;
0246 
0247         if ($params instanceof Zend_Mail_Protocol_Imap) {
0248             $this->_protocol = $params;
0249             try {
0250                 $this->selectFolder('INBOX');
0251             } catch(Zend_Mail_Storage_Exception $e) {
0252                 /**
0253                  * @see Zend_Mail_Storage_Exception
0254                  */
0255                 // require_once 'Zend/Mail/Storage/Exception.php';
0256                 throw new Zend_Mail_Storage_Exception('cannot select INBOX, is this a valid transport?', 0, $e);
0257             }
0258             return;
0259         }
0260 
0261         if (!isset($params->user)) {
0262             /**
0263              * @see Zend_Mail_Storage_Exception
0264              */
0265             // require_once 'Zend/Mail/Storage/Exception.php';
0266             throw new Zend_Mail_Storage_Exception('need at least user in params');
0267         }
0268 
0269         $host     = isset($params->host)     ? $params->host     : 'localhost';
0270         $password = isset($params->password) ? $params->password : '';
0271         $port     = isset($params->port)     ? $params->port     : null;
0272         $ssl      = isset($params->ssl)      ? $params->ssl      : false;
0273 
0274         $this->_protocol = new Zend_Mail_Protocol_Imap();
0275         $this->_protocol->connect($host, $port, $ssl);
0276         if (!$this->_protocol->login($params->user, $password)) {
0277             /**
0278              * @see Zend_Mail_Storage_Exception
0279              */
0280             // require_once 'Zend/Mail/Storage/Exception.php';
0281             throw new Zend_Mail_Storage_Exception('cannot login, user or password wrong');
0282         }
0283         $this->selectFolder(isset($params->folder) ? $params->folder : 'INBOX');
0284     }
0285 
0286     /**
0287      * Close resource for mail lib. If you need to control, when the resource
0288      * is closed. Otherwise the destructor would call this.
0289      *
0290      * @return null
0291      */
0292     public function close()
0293     {
0294         $this->_currentFolder = '';
0295         $this->_protocol->logout();
0296     }
0297 
0298     /**
0299      * Keep the server busy.
0300      *
0301      * @return null
0302      * @throws Zend_Mail_Storage_Exception
0303      */
0304     public function noop()
0305     {
0306         if (!$this->_protocol->noop()) {
0307             /**
0308              * @see Zend_Mail_Storage_Exception
0309              */
0310             // require_once 'Zend/Mail/Storage/Exception.php';
0311             throw new Zend_Mail_Storage_Exception('could not do nothing');
0312         }
0313     }
0314 
0315     /**
0316      * Remove a message from server. If you're doing that from a web enviroment
0317      * you should be careful and use a uniqueid as parameter if possible to
0318      * identify the message.
0319      *
0320      * @param   int $id number of message
0321      * @return  null
0322      * @throws  Zend_Mail_Storage_Exception
0323      */
0324     public function removeMessage($id)
0325     {
0326         if (!$this->_protocol->store(array(Zend_Mail_Storage::FLAG_DELETED), $id, null, '+')) {
0327             /**
0328              * @see Zend_Mail_Storage_Exception
0329              */
0330             // require_once 'Zend/Mail/Storage/Exception.php';
0331             throw new Zend_Mail_Storage_Exception('cannot set deleted flag');
0332         }
0333         // TODO: expunge here or at close? we can handle an error here better and are more fail safe
0334         if (!$this->_protocol->expunge()) {
0335             /**
0336              * @see Zend_Mail_Storage_Exception
0337              */
0338             // require_once 'Zend/Mail/Storage/Exception.php';
0339             throw new Zend_Mail_Storage_Exception('message marked as deleted, but could not expunge');
0340         }
0341     }
0342 
0343     /**
0344      * get unique id for one or all messages
0345      *
0346      * if storage does not support unique ids it's the same as the message number
0347      *
0348      * @param int|null $id message number
0349      * @return array|string message number for given message or all messages as array
0350      * @throws Zend_Mail_Storage_Exception
0351      */
0352     public function getUniqueId($id = null)
0353     {
0354         if ($id) {
0355             return $this->_protocol->fetch('UID', $id);
0356         }
0357 
0358         return $this->_protocol->fetch('UID', 1, INF);
0359     }
0360 
0361     /**
0362      * get a message number from a unique id
0363      *
0364      * I.e. if you have a webmailer that supports deleting messages you should use unique ids
0365      * as parameter and use this method to translate it to message number right before calling removeMessage()
0366      *
0367      * @param string $id unique id
0368      * @return int message number
0369      * @throws Zend_Mail_Storage_Exception
0370      */
0371     public function getNumberByUniqueId($id)
0372     {
0373         // TODO: use search to find number directly
0374         $ids = $this->getUniqueId();
0375         foreach ($ids as $k => $v) {
0376             if ($v == $id) {
0377                 return $k;
0378             }
0379         }
0380 
0381         /**
0382          * @see Zend_Mail_Storage_Exception
0383          */
0384         // require_once 'Zend/Mail/Storage/Exception.php';
0385         throw new Zend_Mail_Storage_Exception('unique id not found');
0386     }
0387 
0388 
0389     /**
0390      * get root folder or given folder
0391      *
0392      * @param  string $rootFolder get folder structure for given folder, else root
0393      * @return Zend_Mail_Storage_Folder root or wanted folder
0394      * @throws Zend_Mail_Storage_Exception
0395      * @throws Zend_Mail_Protocol_Exception
0396      */
0397     public function getFolders($rootFolder = null)
0398     {
0399         $folders = $this->_protocol->listMailbox((string)$rootFolder);
0400         if (!$folders) {
0401             /**
0402              * @see Zend_Mail_Storage_Exception
0403              */
0404             // require_once 'Zend/Mail/Storage/Exception.php';
0405             throw new Zend_Mail_Storage_Exception('folder not found');
0406         }
0407 
0408         ksort($folders, SORT_STRING);
0409         $root = new Zend_Mail_Storage_Folder('/', '/', false);
0410         $stack = array(null);
0411         $folderStack = array(null);
0412         $parentFolder = $root;
0413         $parent = '';
0414 
0415         foreach ($folders as $globalName => $data) {
0416             do {
0417                 if (!$parent || strpos($globalName, $parent) === 0) {
0418                     $pos = strrpos($globalName, $data['delim']);
0419                     if ($pos === false) {
0420                         $localName = $globalName;
0421                     } else {
0422                         $localName = substr($globalName, $pos + 1);
0423                     }
0424                     $selectable = !$data['flags'] || !in_array('\\Noselect', $data['flags']);
0425 
0426                     array_push($stack, $parent);
0427                     $parent = $globalName . $data['delim'];
0428                     $folder = new Zend_Mail_Storage_Folder($localName, $globalName, $selectable);
0429                     $parentFolder->$localName = $folder;
0430                     array_push($folderStack, $parentFolder);
0431                     $parentFolder = $folder;
0432                     break;
0433                 } else if ($stack) {
0434                     $parent = array_pop($stack);
0435                     $parentFolder = array_pop($folderStack);
0436                 }
0437             } while ($stack);
0438             if (!$stack) {
0439                 /**
0440                  * @see Zend_Mail_Storage_Exception
0441                  */
0442                 // require_once 'Zend/Mail/Storage/Exception.php';
0443                 throw new Zend_Mail_Storage_Exception('error while constructing folder tree');
0444             }
0445         }
0446 
0447         return $root;
0448     }
0449 
0450     /**
0451      * select given folder
0452      *
0453      * folder must be selectable!
0454      *
0455      * @param  Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
0456      * @return null
0457      * @throws Zend_Mail_Storage_Exception
0458      * @throws Zend_Mail_Protocol_Exception
0459      */
0460     public function selectFolder($globalName)
0461     {
0462         $this->_currentFolder = $globalName;
0463         if (!$this->_protocol->select($this->_currentFolder)) {
0464             $this->_currentFolder = '';
0465             /**
0466              * @see Zend_Mail_Storage_Exception
0467              */
0468             // require_once 'Zend/Mail/Storage/Exception.php';
0469             throw new Zend_Mail_Storage_Exception('cannot change folder, maybe it does not exist');
0470         }
0471     }
0472 
0473 
0474     /**
0475      * get Zend_Mail_Storage_Folder instance for current folder
0476      *
0477      * @return Zend_Mail_Storage_Folder instance of current folder
0478      * @throws Zend_Mail_Storage_Exception
0479      */
0480     public function getCurrentFolder()
0481     {
0482         return $this->_currentFolder;
0483     }
0484 
0485     /**
0486      * create a new folder
0487      *
0488      * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
0489      * may be used as parent or which chars may be used in the folder name
0490      *
0491      * @param  string                          $name         global name of folder, local name if $parentFolder is set
0492      * @param  string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
0493      * @return null
0494      * @throws Zend_Mail_Storage_Exception
0495      */
0496     public function createFolder($name, $parentFolder = null)
0497     {
0498         // TODO: we assume / as the hierarchy delim - need to get that from the folder class!
0499         if ($parentFolder instanceof Zend_Mail_Storage_Folder) {
0500             $folder = $parentFolder->getGlobalName() . '/' . $name;
0501         } else if ($parentFolder != null) {
0502             $folder = $parentFolder . '/' . $name;
0503         } else {
0504             $folder = $name;
0505         }
0506 
0507         if (!$this->_protocol->create($folder)) {
0508             /**
0509              * @see Zend_Mail_Storage_Exception
0510              */
0511             // require_once 'Zend/Mail/Storage/Exception.php';
0512             throw new Zend_Mail_Storage_Exception('cannot create folder');
0513         }
0514     }
0515 
0516     /**
0517      * remove a folder
0518      *
0519      * @param  string|Zend_Mail_Storage_Folder $name      name or instance of folder
0520      * @return null
0521      * @throws Zend_Mail_Storage_Exception
0522      */
0523     public function removeFolder($name)
0524     {
0525         if ($name instanceof Zend_Mail_Storage_Folder) {
0526             $name = $name->getGlobalName();
0527         }
0528 
0529         if (!$this->_protocol->delete($name)) {
0530             /**
0531              * @see Zend_Mail_Storage_Exception
0532              */
0533             // require_once 'Zend/Mail/Storage/Exception.php';
0534             throw new Zend_Mail_Storage_Exception('cannot delete folder');
0535         }
0536     }
0537 
0538     /**
0539      * rename and/or move folder
0540      *
0541      * The new name has the same restrictions as in createFolder()
0542      *
0543      * @param  string|Zend_Mail_Storage_Folder $oldName name or instance of folder
0544      * @param  string                          $newName new global name of folder
0545      * @return null
0546      * @throws Zend_Mail_Storage_Exception
0547      */
0548     public function renameFolder($oldName, $newName)
0549     {
0550         if ($oldName instanceof Zend_Mail_Storage_Folder) {
0551             $oldName = $oldName->getGlobalName();
0552         }
0553 
0554         if (!$this->_protocol->rename($oldName, $newName)) {
0555             /**
0556              * @see Zend_Mail_Storage_Exception
0557              */
0558             // require_once 'Zend/Mail/Storage/Exception.php';
0559             throw new Zend_Mail_Storage_Exception('cannot rename folder');
0560         }
0561     }
0562 
0563     /**
0564      * append a new message to mail storage
0565      *
0566      * @param  string                                     $message message as string or instance of message class
0567      * @param  null|string|Zend_Mail_Storage_Folder       $folder  folder for new message, else current folder is taken
0568      * @param  null|array                                 $flags   set flags for new message, else a default set is used
0569      * @throws Zend_Mail_Storage_Exception
0570      */
0571      // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
0572     public function appendMessage($message, $folder = null, $flags = null)
0573     {
0574         if ($folder === null) {
0575             $folder = $this->_currentFolder;
0576         }
0577 
0578         if ($flags === null) {
0579             $flags = array(Zend_Mail_Storage::FLAG_SEEN);
0580         }
0581 
0582         // TODO: handle class instances for $message
0583         if (!$this->_protocol->append($folder, $message, $flags)) {
0584             /**
0585              * @see Zend_Mail_Storage_Exception
0586              */
0587             // require_once 'Zend/Mail/Storage/Exception.php';
0588             throw new Zend_Mail_Storage_Exception('cannot create message, please check if the folder exists and your flags');
0589         }
0590     }
0591 
0592     /**
0593      * copy an existing message
0594      *
0595      * @param  int                             $id     number of message
0596      * @param  string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
0597      * @return null
0598      * @throws Zend_Mail_Storage_Exception
0599      */
0600     public function copyMessage($id, $folder)
0601     {
0602         if (!$this->_protocol->copy($folder, $id)) {
0603             /**
0604              * @see Zend_Mail_Storage_Exception
0605              */
0606             // require_once 'Zend/Mail/Storage/Exception.php';
0607             throw new Zend_Mail_Storage_Exception('cannot copy message, does the folder exist?');
0608         }
0609     }
0610 
0611     /**
0612      * move an existing message
0613      *
0614      * NOTE: imap has no native move command, thus it's emulated with copy and delete
0615      *
0616      * @param  int                             $id     number of message
0617      * @param  string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
0618      * @return null
0619      * @throws Zend_Mail_Storage_Exception
0620      */
0621     public function moveMessage($id, $folder) {
0622         $this->copyMessage($id, $folder);
0623         $this->removeMessage($id);
0624     }
0625 
0626     /**
0627      * set flags for message
0628      *
0629      * NOTE: this method can't set the recent flag.
0630      *
0631      * @param  int   $id    number of message
0632      * @param  array $flags new flags for message
0633      * @throws Zend_Mail_Storage_Exception
0634      */
0635     public function setFlags($id, $flags)
0636     {
0637         if (!$this->_protocol->store($flags, $id)) {
0638             /**
0639              * @see Zend_Mail_Storage_Exception
0640              */
0641             // require_once 'Zend/Mail/Storage/Exception.php';
0642             throw new Zend_Mail_Storage_Exception('cannot set flags, have you tried to set the recent flag or special chars?');
0643         }
0644     }
0645 }
0646