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