[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Copyright 2008-2014 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (LGPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * @category Horde 9 * @copyright 2008-2014 Horde LLC 10 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 11 * @package Imap_Client 12 */ 13 14 /** 15 * An abstracted API interface to IMAP backends supporting the IMAP4rev1 16 * protocol (RFC 3501). 17 * 18 * @author Michael Slusarz <slusarz@horde.org> 19 * @category Horde 20 * @copyright 2008-2014 Horde LLC 21 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 22 * @package Imap_Client 23 */ 24 abstract class Horde_Imap_Client_Base implements Serializable 25 { 26 /** Serialized version. */ 27 const VERSION = 2; 28 29 /** Cache names for miscellaneous data. */ 30 const CACHE_MODSEQ = '_m'; 31 const CACHE_SEARCH = '_s'; 32 /* @since 2.9.0 */ 33 const CACHE_SEARCHID = '_i'; 34 35 /** Cache names used exclusively within this class. @since 2.11.0 */ 36 const CACHE_DOWNGRADED = 'HICdg'; 37 38 /** 39 * The list of fetch fields that can be cached, and their cache names. 40 * 41 * @var array 42 */ 43 public $cacheFields = array( 44 Horde_Imap_Client::FETCH_ENVELOPE => 'HICenv', 45 Horde_Imap_Client::FETCH_FLAGS => 'HICflags', 46 Horde_Imap_Client::FETCH_HEADERS => 'HIChdrs', 47 Horde_Imap_Client::FETCH_IMAPDATE => 'HICdate', 48 Horde_Imap_Client::FETCH_SIZE => 'HICsize', 49 Horde_Imap_Client::FETCH_STRUCTURE => 'HICstruct' 50 ); 51 52 /** 53 * Has the internal configuration changed? 54 * 55 * @var boolean 56 */ 57 public $changed = false; 58 59 /** 60 * Horde_Imap_Client is optimized for short (i.e. 1 seconds) scripts. It 61 * makes heavy use of mailbox caching to save on server accesses. This 62 * property should be set to false for long-running scripts, or else 63 * status() data may not reflect the current state of the mailbox on the 64 * server. 65 * 66 * @since 2.14.0 67 * 68 * @var boolean 69 */ 70 public $statuscache = true; 71 72 /** 73 * The Horde_Imap_Client_Cache object. 74 * 75 * @var Horde_Imap_Client_Cache 76 */ 77 protected $_cache = null; 78 79 /** 80 * Connection to the IMAP server. 81 * 82 * @var Horde\Socket\Client 83 */ 84 protected $_connection = null; 85 86 /** 87 * The debug object. 88 * 89 * @var Horde_Imap_Client_Base_Debug 90 */ 91 protected $_debug = null; 92 93 /** 94 * The default ports to use for a connection. 95 * First element is non-secure, second is SSL. 96 * 97 * @var array 98 */ 99 protected $_defaultPorts = array(); 100 101 /** 102 * The fetch data object type to return. 103 * 104 * @var string 105 */ 106 protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch'; 107 108 /** 109 * Cached server data. 110 * 111 * @var array 112 */ 113 protected $_init; 114 115 /** 116 * Is there an active authenticated connection to the IMAP Server? 117 * 118 * @var boolean 119 */ 120 protected $_isAuthenticated = false; 121 122 /** 123 * The current mailbox selection mode. 124 * 125 * @var integer 126 */ 127 protected $_mode = 0; 128 129 /** 130 * Hash containing connection parameters. 131 * This hash never changes. 132 * 133 * @var array 134 */ 135 protected $_params = array(); 136 137 /** 138 * The currently selected mailbox. 139 * 140 * @var Horde_Imap_Client_Mailbox 141 */ 142 protected $_selected = null; 143 144 /** 145 * Temp array (destroyed at end of process). 146 * 147 * @var array 148 */ 149 protected $_temp = array( 150 'enabled' => array() 151 ); 152 153 /** 154 * Constructor. 155 * 156 * @param array $params Configuration parameters: 157 * <pre> 158 * - cache: (array) If set, caches data from fetch(), search(), and 159 * thread() calls. Requires the horde/Cache package to be 160 * installed. The array can contain the following keys (see 161 * Horde_Imap_Client_Cache for default values): 162 * - backend: [REQUIRED (or cacheob)] (Horde_Imap_Client_Cache_Backend) 163 * Backend cache driver [@since 2.9.0]. 164 * - fetch_ignore: (array) A list of mailboxes to ignore when storing 165 * fetch data. 166 * - fields: (array) The fetch criteria to cache. If not defined, all 167 * cacheable data is cached. The following is a list of 168 * criteria that can be cached: 169 * - Horde_Imap_Client::FETCH_ENVELOPE 170 * - Horde_Imap_Client::FETCH_FLAGS 171 * Only if server supports CONDSTORE extension 172 * - Horde_Imap_Client::FETCH_HEADERS 173 * Only for queries that specifically request caching 174 * - Horde_Imap_Client::FETCH_IMAPDATE 175 * - Horde_Imap_Client::FETCH_SIZE 176 * - Horde_Imap_Client::FETCH_STRUCTURE 177 * - capability_ignore: (array) A list of IMAP capabilites to ignore, even 178 * if they are supported on the server. 179 * DEFAULT: No supported capabilities are ignored. 180 * - comparator: (string) The search comparator to use instead of the 181 * default server comparator. See setComparator() for 182 * format. 183 * DEFAULT: Use the server default 184 * - debug: (string) If set, will output debug information to the stream 185 * provided. The value can be any PHP supported wrapper that can 186 * be opened via PHP's fopen() function. 187 * DEFAULT: No debug output 188 * - hostspec: (string) The hostname or IP address of the server. 189 * DEFAULT: 'localhost' 190 * - id: (array) Send ID information to the server (only if server 191 * supports the ID extension). An array with the keys as the fields 192 * to send and the values being the associated values. See RFC 2971 193 * [3.3] for a list of standard field values. 194 * DEFAULT: No info sent to server 195 * - lang: (array) A list of languages (in priority order) to be used to 196 * display human readable messages. 197 * DEFAULT: Messages output in IMAP server default language 198 * - password: (mixed) The user password. Either a string or a 199 * Horde_Imap_Client_Base_Password object [@since 2.14.0]. 200 * - port: (integer) The server port to which we will connect. 201 * DEFAULT: 143 (imap or imap w/TLS) or 993 (imaps) 202 * - secure: (string) Use SSL or TLS to connect. Values: 203 * - false (No encryption) 204 * - 'ssl' (Auto-detect SSL version) 205 * - 'sslv2' (Force SSL version 3) 206 * - 'sslv3' (Force SSL version 2) 207 * - 'tls' (TLS; started via protocol-level negotation over 208 * unencrypted channel; RECOMMENDED way of initiating secure 209 * connection) 210 * - 'tlsv1' (TLS direct version 1.x connection to server) [@since 211 * 2.16.0] 212 * - true (TLS if available/necessary) [@since 2.15.0] 213 * DEFAULT: false 214 * - timeout: (integer) Connection timeout, in seconds. 215 * DEFAULT: 30 seconds 216 * - username: (string) [REQUIRED] The username. 217 * </pre> 218 */ 219 public function __construct(array $params = array()) 220 { 221 if (!isset($params['username'])) { 222 throw new InvalidArgumentException('Horde_Imap_Client requires a username.'); 223 } 224 225 $this->_setInit(); 226 227 // Default values. 228 $params = array_merge(array( 229 'hostspec' => 'localhost', 230 'secure' => false, 231 'timeout' => 30 232 ), array_filter($params)); 233 234 if (!isset($params['port'])) { 235 $params['port'] = (!empty($params['secure']) && in_array($params['secure'], array('ssl', 'sslv2', 'sslv3'), true)) 236 ? $this->_defaultPorts[1] 237 : $this->_defaultPorts[0]; 238 } 239 240 if (empty($params['cache'])) { 241 $params['cache'] = array('fields' => array()); 242 } elseif (empty($params['cache']['fields'])) { 243 $params['cache']['fields'] = $this->cacheFields; 244 } else { 245 $params['cache']['fields'] = array_flip($params['cache']['fields']); 246 } 247 248 if (empty($params['cache']['fetch_ignore'])) { 249 $params['cache']['fetch_ignore'] = array(); 250 } 251 252 $this->_params = $params; 253 if (isset($params['password'])) { 254 $this->setParam('password', $params['password']); 255 } 256 257 $this->changed = true; 258 $this->_initOb(); 259 } 260 261 /** 262 * Get encryption key. 263 * 264 * @deprecated Pass callable into 'password' parameter instead. 265 * 266 * @return string The encryption key. 267 */ 268 protected function _getEncryptKey() 269 { 270 if (is_callable($ekey = $this->getParam('encryptKey'))) { 271 return call_user_func($ekey); 272 } 273 274 throw new InvalidArgumentException('encryptKey parameter is not a valid callback.'); 275 } 276 277 /** 278 * Do initialization tasks. 279 */ 280 protected function _initOb() 281 { 282 register_shutdown_function(array($this, 'shutdown')); 283 $this->_debug = ($debug = $this->getParam('debug')) 284 ? new Horde_Imap_Client_Base_Debug($debug) 285 : new Horde_Support_Stub(); 286 } 287 288 /** 289 * Shutdown actions. 290 */ 291 public function shutdown() 292 { 293 $this->logout(); 294 } 295 296 /** 297 * This object can not be cloned. 298 */ 299 public function __clone() 300 { 301 throw new LogicException('Object cannot be cloned.'); 302 } 303 304 /** 305 */ 306 public function serialize() 307 { 308 return serialize(array( 309 'i' => $this->_init, 310 'p' => $this->_params, 311 'v' => self::VERSION 312 )); 313 } 314 315 /** 316 */ 317 public function unserialize($data) 318 { 319 $data = @unserialize($data); 320 if (!is_array($data) || 321 !isset($data['v']) || 322 ($data['v'] != self::VERSION)) { 323 throw new Exception('Cache version change'); 324 } 325 326 $this->_init = $data['i']; 327 $this->_params = $data['p']; 328 329 $this->_initOb(); 330 } 331 332 /** 333 * Set an initialization value. 334 * 335 * @param string $key The initialization key. If null, resets all keys. 336 * @param mixed $val The cached value. If null, removes the key. 337 */ 338 public function _setInit($key = null, $val = null) 339 { 340 if (is_null($key)) { 341 $this->_init = array( 342 'namespace' => array(), 343 's_charset' => array() 344 ); 345 } elseif (is_null($val)) { 346 unset($this->_init[$key]); 347 348 switch ($key) { 349 case 'capability': 350 unset($this->_init['cmdlength']); 351 break; 352 } 353 } else { 354 switch ($key) { 355 case 'capability': 356 if ($ci = $this->getParam('capability_ignore')) { 357 if ($this->_debug->debug && 358 ($ignored = array_intersect_key($val, array_flip($ci)))) { 359 $this->_debug->info(sprintf( 360 'CONFIG: IGNORING these IMAP capabilities: %s', 361 implode(', ', array_keys($ignored)) 362 )); 363 } 364 365 $val = array_diff_key($val, array_flip($ci)); 366 } 367 368 /* RFC 7162 [3.2.3] - QRESYNC implies CONDSTORE and ENABLE, 369 * even if not listed as a capability. */ 370 if (!empty($val['QRESYNC'])) { 371 $val['CONDSTORE'] = true; 372 $val['ENABLE'] = true; 373 } 374 375 /* RFC 2683 [3.2.1.5] originally recommended that lines should 376 * be limited to "approximately 1000 octets". However, servers 377 * should allow a command line of at least "8000 octets". 378 * RFC 7162 [4] updates the recommendation to 8192 octets. 379 * As a compromise, assume all modern IMAP servers handle 380 * ~2000 octets and, if CONDSTORE/ENABLE is supported, assume 381 * they can handle ~8000 octets. */ 382 $this->_init['cmdlength'] = (isset($val['CONDSTORE']) || isset($val['QRESYNC'])) 383 ? 8000 384 : 2000; 385 break; 386 } 387 388 /* Nothing has changed. */ 389 if (isset($this->_init[$key]) && ($this->_init[$key] == $val)) { 390 return; 391 } 392 393 $this->_init[$key] = $val; 394 } 395 396 $this->changed = true; 397 } 398 399 /** 400 * Set the list of enabled extensions. 401 * 402 * @param array $exts The list of extensions. 403 * @param integer $status 1 means to report as ENABLED, although it has 404 * not been formally enabled on server yet. 2 is 405 * verified enabled on the server. 406 */ 407 protected function _enabled($exts, $status) 408 { 409 /* RFC 7162 [3.2.3] - Enabling QRESYNC also implies enabling of 410 * CONDSTORE. */ 411 if (in_array('QRESYNC', $exts)) { 412 $exts[] = 'CONDSTORE'; 413 } 414 415 switch ($status) { 416 case 2: 417 $enabled_list = array_intersect(array(2), $this->_temp['enabled']); 418 break; 419 420 case 1: 421 default: 422 $enabled_list = $this->_temp['enabled']; 423 $status = 1; 424 break; 425 } 426 427 $this->_temp['enabled'] = array_merge( 428 $enabled_list, 429 array_fill_keys($exts, $status) 430 ); 431 } 432 433 /** 434 * Initialize the Horde_Imap_Client_Cache object, if necessary. 435 * 436 * @param boolean $current If true, we are going to update the currently 437 * selected mailbox. Add an additional check to 438 * see if caching is available in current 439 * mailbox. 440 * 441 * @return boolean Returns true if caching is enabled. 442 */ 443 protected function _initCache($current = false) 444 { 445 $c = $this->getParam('cache'); 446 447 if (empty($c['fields'])) { 448 return false; 449 } 450 451 if (is_null($this->_cache)) { 452 if (isset($c['backend'])) { 453 $backend = $c['backend']; 454 } elseif (isset($c['cacheob'])) { 455 /* Deprecated */ 456 $backend = new Horde_Imap_Client_Cache_Backend_Cache($c); 457 } else { 458 return false; 459 } 460 461 $this->_cache = new Horde_Imap_Client_Cache(array( 462 'backend' => $backend, 463 'baseob' => $this, 464 'debug' => $this->_debug 465 )); 466 } 467 468 return $current 469 /* If UIDs are labeled as not sticky, don't cache since UIDs will 470 * change on every access. */ 471 ? !($this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_UIDNOTSTICKY)) 472 : true; 473 } 474 475 /** 476 * Returns a value from the internal params array. 477 * 478 * @param string $key The param key. 479 * 480 * @return mixed The param value, or null if not found. 481 */ 482 public function getParam($key) 483 { 484 /* Passwords may be stored encrypted. */ 485 switch ($key) { 486 case 'password': 487 if (isset($this->_params[$key]) && 488 ($this->_params[$key] instanceof Horde_Imap_Client_Base_Password)) { 489 return $this->_params[$key]->getPassword(); 490 } 491 492 // DEPRECATED 493 if (!empty($this->_params['_passencrypt'])) { 494 try { 495 $secret = new Horde_Secret(); 496 return $secret->read($this->_getEncryptKey(), $this->_params['password']); 497 } catch (Exception $e) { 498 return null; 499 } 500 } 501 break; 502 } 503 504 return isset($this->_params[$key]) 505 ? $this->_params[$key] 506 : null; 507 } 508 509 /** 510 * Sets a configuration parameter value. 511 * 512 * @param string $key The param key. 513 * @param mixed $val The param value. 514 */ 515 public function setParam($key, $val) 516 { 517 switch ($key) { 518 case 'password': 519 if ($val instanceof Horde_Imap_Client_Base_Password) { 520 break; 521 } 522 523 // DEPRECATED: Encrypt password. 524 try { 525 $encrypt_key = $this->_getEncryptKey(); 526 if (strlen($encrypt_key)) { 527 $secret = new Horde_Secret(); 528 $val = $secret->write($encrypt_key, $val); 529 $this->_params['_passencrypt'] = true; 530 } 531 } catch (Exception $e) {} 532 break; 533 } 534 535 $this->_params[$key] = $val; 536 $this->changed = true; 537 } 538 539 /** 540 * Returns the Horde_Imap_Client_Cache object used, if available. 541 * 542 * @return mixed Either the cache object or null. 543 */ 544 public function getCache() 545 { 546 $this->_initCache(); 547 return $this->_cache; 548 } 549 550 /** 551 * Returns the correct IDs object for use with this driver. 552 * 553 * @param mixed $ids See add(). 554 * @param boolean $sequence Are $ids message sequence numbers? 555 * 556 * @return Horde_Imap_Client_Ids The IDs object. 557 */ 558 public function getIdsOb($ids = null, $sequence = false) 559 { 560 return new Horde_Imap_Client_Ids($ids, $sequence); 561 } 562 563 /** 564 * Returns whether the IMAP server supports the given capability 565 * (See RFC 3501 [6.1.1]). 566 * 567 * @param string $capability The capability string to query. 568 * 569 * @return mixed True if the server supports the queried capability, 570 * false if it doesn't, or an array if the capability can 571 * contain multiple values. 572 */ 573 public function queryCapability($capability) 574 { 575 // @todo: Remove this catch(); if capability fails due to connection 576 // error, should throw an exception. 577 try { 578 $this->capability(); 579 } catch (Horde_Imap_Client_Exception $e) { 580 return false; 581 } 582 583 $capability = strtoupper($capability); 584 585 if (!isset($this->_init['capability'][$capability])) { 586 return false; 587 } 588 589 /* Check for capability requirements. */ 590 if (isset(Horde_Imap_Client::$capability_deps[$capability])) { 591 foreach (Horde_Imap_Client::$capability_deps[$capability] as $val) { 592 if (!$this->queryCapability($val)) { 593 return false; 594 } 595 } 596 } 597 598 return $this->_init['capability'][$capability]; 599 } 600 601 /** 602 * Get CAPABILITY information from the IMAP server. 603 * 604 * @return array The capability array. 605 * 606 * @throws Horde_Imap_Client_Exception 607 */ 608 public function capability() 609 { 610 if (!isset($this->_init['capability'])) { 611 $this->_capability(); 612 } 613 614 return $this->_init['capability']; 615 } 616 617 /** 618 * Retrieve CAPABILITY information from the IMAP server. 619 * 620 * @throws Horde_Imap_Client_Exception 621 */ 622 abstract protected function _capability(); 623 624 /** 625 * Send a NOOP command (RFC 3501 [6.1.2]). 626 * 627 * @throws Horde_Imap_Client_Exception 628 */ 629 public function noop() 630 { 631 if (!$this->_connection) { 632 // NOOP can be called in the unauthenticated state. 633 $this->_connect(); 634 } 635 $this->_noop(); 636 } 637 638 /** 639 * Send a NOOP command. 640 * 641 * @throws Horde_Imap_Client_Exception 642 */ 643 abstract protected function _noop(); 644 645 /** 646 * Get the NAMESPACE information from the IMAP server (RFC 2342). 647 * 648 * @param array $additional If the server supports namespaces, any 649 * additional namespaces to add to the 650 * namespace list that are not broadcast by 651 * the server. The namespaces must be UTF-8 652 * strings. 653 * @param array $opts Additional options: 654 * - ob_return: (boolean) If true, returns a 655 * Horde_Imap_Client_Namespace_List object instead of an 656 * array. 657 * 658 * @return mixed A Horde_Imap_Client_Namespace_List object if 659 * 'ob_return', is true. Otherwise, an array of namespace 660 * objects (@deprecated) with the name as the key (UTF-8) 661 * and the following values: 662 * <pre> 663 * - delimiter: (string) The namespace delimiter. 664 * - hidden: (boolean) Is this a hidden namespace? 665 * - name: (string) The namespace name (UTF-8). 666 * - translation: (string) Returns the translated name of the namespace 667 * (UTF-8). Requires RFC 5255 and a previous call to 668 * setLanguage(). 669 * - type: (integer) The namespace type. Either: 670 * - Horde_Imap_Client::NS_PERSONAL 671 * - Horde_Imap_Client::NS_OTHER 672 * - Horde_Imap_Client::NS_SHARED 673 * </pre> 674 * 675 * @throws Horde_Imap_Client_Exception 676 */ 677 public function getNamespaces( 678 array $additional = array(), array $opts = array() 679 ) 680 { 681 $additional = array_map('strval', $additional); 682 $sig = hash( 683 (PHP_MINOR_VERSION >= 4) ? 'fnv132' : 'sha1', 684 json_encode($additional) . intval(empty($opts['ob_return'])) 685 ); 686 687 if (isset($this->_init['namespace'][$sig])) { 688 $ns = $this->_init['namespace'][$sig]; 689 } else { 690 $this->login(); 691 692 $ns = $this->_getNamespaces(); 693 694 /* Skip namespaces if we have already auto-detected them. Also, 695 * hidden namespaces cannot be empty. */ 696 $to_process = array_diff(array_filter($additional, 'strlen'), array_map('strlen', iterator_to_array($ns))); 697 if (!empty($to_process)) { 698 foreach ($this->listMailboxes($to_process, Horde_Imap_Client::MBOX_ALL, array('delimiter' => true)) as $val) { 699 $ob = new Horde_Imap_Client_Data_Namespace(); 700 $ob->delimiter = $val['delimiter']; 701 $ob->hidden = true; 702 $ob->name = $val; 703 $ob->type = $ob::NS_SHARED; 704 $ns[$val] = $ob; 705 } 706 } 707 708 if (!count($ns)) { 709 /* This accurately determines the namespace information of the 710 * base namespace if the NAMESPACE command is not supported. 711 * See: RFC 3501 [6.3.8] */ 712 $mbox = $this->listMailboxes('', Horde_Imap_Client::MBOX_ALL, array('delimiter' => true)); 713 $first = reset($mbox); 714 715 $ob = new Horde_Imap_Client_Data_Namespace(); 716 $ob->delimiter = $first['delimiter']; 717 $ns[''] = $ob; 718 } 719 720 $this->_init['namespace'][$sig] = $ns; 721 $this->_setInit('namespace', $this->_init['namespace']); 722 } 723 724 if (!empty($opts['ob_return'])) { 725 return $ns; 726 } 727 728 /* @todo Remove for 3.0 */ 729 $out = array(); 730 foreach ($ns as $key => $val) { 731 $out[$key] = array( 732 'delimiter' => $val->delimiter, 733 'hidden' => $val->hidden, 734 'name' => $val->name, 735 'translation' => $val->translation, 736 'type' => $val->type 737 ); 738 } 739 740 return $out; 741 } 742 743 /** 744 * Get the NAMESPACE information from the IMAP server. 745 * 746 * @return Horde_Imap_Client_Namespace_List Namespace list object. 747 * 748 * @throws Horde_Imap_Client_Exception 749 */ 750 abstract protected function _getNamespaces(); 751 752 /** 753 * Display if connection to the server has been secured via TLS or SSL. 754 * 755 * @return boolean True if the IMAP connection is secured. 756 */ 757 public function isSecureConnection() 758 { 759 return ($this->_connection && $this->_connection->secure); 760 } 761 762 /** 763 * Connect to the remote server. 764 * 765 * @throws Horde_Imap_Client_Exception 766 */ 767 abstract protected function _connect(); 768 769 /** 770 * Return a list of alerts that MUST be presented to the user (RFC 3501 771 * [7.1]). 772 * 773 * @return array An array of alert messages. 774 */ 775 abstract public function alerts(); 776 777 /** 778 * Login to the IMAP server. 779 * 780 * @throws Horde_Imap_Client_Exception 781 */ 782 public function login() 783 { 784 if (!$this->_isAuthenticated && $this->_login()) { 785 if ($this->getParam('id')) { 786 try { 787 $this->sendID(); 788 } catch (Horde_Imap_Client_Exception_NoSupportExtension $e) { 789 // Ignore if server doesn't support ID extension. 790 } 791 } 792 793 if ($this->getParam('comparator')) { 794 try { 795 $this->setComparator(); 796 } catch (Horde_Imap_Client_Exception_NoSupportExtension $e) { 797 // Ignore if server doesn't support I18NLEVEL=2 798 } 799 } 800 } 801 802 $this->_isAuthenticated = true; 803 } 804 805 /** 806 * Login to the IMAP server. 807 * 808 * @return boolean Return true if global login tasks should be run. 809 * 810 * @throws Horde_Imap_Client_Exception 811 */ 812 abstract protected function _login(); 813 814 /** 815 * Logout from the IMAP server (see RFC 3501 [6.1.3]). 816 */ 817 public function logout() 818 { 819 if ($this->_isAuthenticated && $this->_connection->connected) { 820 $this->_logout(); 821 $this->_connection->close(); 822 } 823 824 $this->_connection = $this->_selected = null; 825 $this->_isAuthenticated = false; 826 $this->_mode = 0; 827 } 828 829 /** 830 * Logout from the IMAP server (see RFC 3501 [6.1.3]). 831 */ 832 abstract protected function _logout(); 833 834 /** 835 * Send ID information to the IMAP server (RFC 2971). 836 * 837 * @param array $info Overrides the value of the 'id' param and sends 838 * this information instead. 839 * 840 * @throws Horde_Imap_Client_Exception 841 * @throws Horde_Imap_Client_Exception_NoSupportExtension 842 */ 843 public function sendID($info = null) 844 { 845 if (!$this->queryCapability('ID')) { 846 throw new Horde_Imap_Client_Exception_NoSupportExtension('ID'); 847 } 848 849 $this->_sendID(is_null($info) ? ($this->getParam('id') ?: array()) : $info); 850 } 851 852 /** 853 * Send ID information to the IMAP server (RFC 2971). 854 * 855 * @param array $info The information to send to the server. 856 * 857 * @throws Horde_Imap_Client_Exception 858 */ 859 abstract protected function _sendID($info); 860 861 /** 862 * Return ID information from the IMAP server (RFC 2971). 863 * 864 * @return array An array of information returned, with the keys as the 865 * 'field' and the values as the 'value'. 866 * 867 * @throws Horde_Imap_Client_Exception 868 * @throws Horde_Imap_Client_Exception_NoSupportExtension 869 */ 870 public function getID() 871 { 872 if (!$this->queryCapability('ID')) { 873 throw new Horde_Imap_Client_Exception_NoSupportExtension('ID'); 874 } 875 876 return $this->_getID(); 877 } 878 879 /** 880 * Return ID information from the IMAP server (RFC 2971). 881 * 882 * @return array An array of information returned, with the keys as the 883 * 'field' and the values as the 'value'. 884 * 885 * @throws Horde_Imap_Client_Exception 886 */ 887 abstract protected function _getID(); 888 889 /** 890 * Sets the preferred language for server response messages (RFC 5255). 891 * 892 * @param array $langs Overrides the value of the 'lang' param and sends 893 * this list of preferred languages instead. The 894 * special string 'i-default' can be used to restore 895 * the language to the server default. 896 * 897 * @return string The language accepted by the server, or null if the 898 * default language is used. 899 * 900 * @throws Horde_Imap_Client_Exception 901 */ 902 public function setLanguage($langs = null) 903 { 904 $lang = null; 905 906 if ($this->queryCapability('LANGUAGE')) { 907 $lang = is_null($langs) 908 ? $this->getParam('lang') 909 : $langs; 910 } 911 912 return is_null($lang) 913 ? null 914 : $this->_setLanguage($lang); 915 } 916 917 /** 918 * Sets the preferred language for server response messages (RFC 5255). 919 * 920 * @param array $langs The preferred list of languages. 921 * 922 * @return string The language accepted by the server, or null if the 923 * default language is used. 924 * 925 * @throws Horde_Imap_Client_Exception 926 */ 927 abstract protected function _setLanguage($langs); 928 929 /** 930 * Gets the preferred language for server response messages (RFC 5255). 931 * 932 * @param array $list If true, return the list of available languages. 933 * 934 * @return mixed If $list is true, the list of languages available on the 935 * server (may be empty). If false, the language used by 936 * the server, or null if the default language is used. 937 * 938 * @throws Horde_Imap_Client_Exception 939 */ 940 public function getLanguage($list = false) 941 { 942 if (!$this->queryCapability('LANGUAGE')) { 943 return $list ? array() : null; 944 } 945 946 return $this->_getLanguage($list); 947 } 948 949 /** 950 * Gets the preferred language for server response messages (RFC 5255). 951 * 952 * @param array $list If true, return the list of available languages. 953 * 954 * @return mixed If $list is true, the list of languages available on the 955 * server (may be empty). If false, the language used by 956 * the server, or null if the default language is used. 957 * 958 * @throws Horde_Imap_Client_Exception 959 */ 960 abstract protected function _getLanguage($list); 961 962 /** 963 * Open a mailbox. 964 * 965 * @param mixed $mailbox The mailbox to open. Either a 966 * Horde_Imap_Client_Mailbox object or a string 967 * (UTF-8). 968 * @param integer $mode The access mode. Either 969 * - Horde_Imap_Client::OPEN_READONLY 970 * - Horde_Imap_Client::OPEN_READWRITE 971 * - Horde_Imap_Client::OPEN_AUTO 972 * 973 * @throws Horde_Imap_Client_Exception 974 */ 975 public function openMailbox($mailbox, $mode = Horde_Imap_Client::OPEN_AUTO) 976 { 977 $this->login(); 978 979 $change = false; 980 $mailbox = Horde_Imap_Client_Mailbox::get($mailbox); 981 982 if ($mode == Horde_Imap_Client::OPEN_AUTO) { 983 if (is_null($this->_selected) || 984 !$mailbox->equals($this->_selected)) { 985 $mode = Horde_Imap_Client::OPEN_READONLY; 986 $change = true; 987 } 988 } else { 989 $change = (is_null($this->_selected) || 990 !$mailbox->equals($this->_selected) || 991 ($mode != $this->_mode)); 992 } 993 994 if ($change) { 995 $this->_openMailbox($mailbox, $mode); 996 $this->_mailboxOb()->open = true; 997 if ($this->_initCache(true)) { 998 $this->_condstoreSync(); 999 } 1000 } 1001 } 1002 1003 /** 1004 * Open a mailbox. 1005 * 1006 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to open. 1007 * @param integer $mode The access mode. 1008 * 1009 * @throws Horde_Imap_Client_Exception 1010 */ 1011 abstract protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox, 1012 $mode); 1013 1014 /** 1015 * Called when the selected mailbox is changed. 1016 * 1017 * @param mixed $mailbox The selected mailbox or null. 1018 * @param integer $mode The access mode. 1019 */ 1020 protected function _changeSelected($mailbox = null, $mode = null) 1021 { 1022 $this->_mode = $mode; 1023 if (is_null($mailbox)) { 1024 $this->_selected = null; 1025 } else { 1026 $this->_selected = clone $mailbox; 1027 $this->_mailboxOb()->reset(); 1028 } 1029 } 1030 1031 /** 1032 * Return the Horde_Imap_Client_Base_Mailbox object. 1033 * 1034 * @param string $mailbox The mailbox name. Defaults to currently 1035 * selected mailbox. 1036 * 1037 * @return Horde_Imap_Client_Base_Mailbox Mailbox object. 1038 */ 1039 protected function _mailboxOb($mailbox = null) 1040 { 1041 $name = is_null($mailbox) 1042 ? strval($this->_selected) 1043 : strval($mailbox); 1044 1045 if (!isset($this->_temp['mailbox_ob'][$name])) { 1046 $this->_temp['mailbox_ob'][$name] = new Horde_Imap_Client_Base_Mailbox(); 1047 } 1048 1049 return $this->_temp['mailbox_ob'][$name]; 1050 } 1051 1052 /** 1053 * Return the currently opened mailbox and access mode. 1054 * 1055 * @return mixed Null if no mailbox selected, or an array with two 1056 * elements: 1057 * - mailbox: (Horde_Imap_Client_Mailbox) The mailbox object. 1058 * - mode: (integer) Current mode. 1059 * 1060 * @throws Horde_Imap_Client_Exception 1061 */ 1062 public function currentMailbox() 1063 { 1064 return is_null($this->_selected) 1065 ? null 1066 : array( 1067 'mailbox' => clone $this->_selected, 1068 'mode' => $this->_mode 1069 ); 1070 } 1071 1072 /** 1073 * Create a mailbox. 1074 * 1075 * @param mixed $mailbox The mailbox to create. Either a 1076 * Horde_Imap_Client_Mailbox object or a string 1077 * (UTF-8). 1078 * @param array $opts Additional options: 1079 * - special_use: (array) An array of special-use flags to mark the 1080 * mailbox with. The server MUST support RFC 6154. 1081 * 1082 * @throws Horde_Imap_Client_Exception 1083 */ 1084 public function createMailbox($mailbox, array $opts = array()) 1085 { 1086 $this->login(); 1087 1088 if (!$this->queryCapability('CREATE-SPECIAL-USE')) { 1089 unset($opts['special_use']); 1090 } 1091 1092 $this->_createMailbox(Horde_Imap_Client_Mailbox::get($mailbox), $opts); 1093 } 1094 1095 /** 1096 * Create a mailbox. 1097 * 1098 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to create. 1099 * @param array $opts Additional options. See 1100 * createMailbox(). 1101 * 1102 * @throws Horde_Imap_Client_Exception 1103 */ 1104 abstract protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox, 1105 $opts); 1106 1107 /** 1108 * Delete a mailbox. 1109 * 1110 * @param mixed $mailbox The mailbox to delete. Either a 1111 * Horde_Imap_Client_Mailbox object or a string 1112 * (UTF-8). 1113 * 1114 * @throws Horde_Imap_Client_Exception 1115 */ 1116 public function deleteMailbox($mailbox) 1117 { 1118 $this->login(); 1119 1120 $mailbox = Horde_Imap_Client_Mailbox::get($mailbox); 1121 1122 $this->_deleteMailbox($mailbox); 1123 $this->_deleteMailboxPost($mailbox); 1124 } 1125 1126 /** 1127 * Delete a mailbox. 1128 * 1129 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to delete. 1130 * 1131 * @throws Horde_Imap_Client_Exception 1132 */ 1133 abstract protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox); 1134 1135 /** 1136 * Actions to perform after a mailbox delete. 1137 * 1138 * @param Horde_Imap_Client_Mailbox $mailbox The deleted mailbox. 1139 */ 1140 protected function _deleteMailboxPost(Horde_Imap_Client_Mailbox $mailbox) 1141 { 1142 /* Delete mailbox caches. */ 1143 if ($this->_initCache()) { 1144 $this->_cache->deleteMailbox($mailbox); 1145 } 1146 unset($this->_temp['mailbox_ob'][strval($mailbox)]); 1147 1148 /* Unsubscribe from mailbox. */ 1149 try { 1150 $this->subscribeMailbox($mailbox, false); 1151 } catch (Horde_Imap_Client_Exception $e) { 1152 // Ignore failed unsubscribe request 1153 } 1154 } 1155 1156 /** 1157 * Rename a mailbox. 1158 * 1159 * @param mixed $old The old mailbox name. Either a 1160 * Horde_Imap_Client_Mailbox object or a string (UTF-8). 1161 * @param mixed $new The new mailbox name. Either a 1162 * Horde_Imap_Client_Mailbox object or a string (UTF-8). 1163 * 1164 * @throws Horde_Imap_Client_Exception 1165 */ 1166 public function renameMailbox($old, $new) 1167 { 1168 // Login will be handled by first listMailboxes() call. 1169 1170 $old = Horde_Imap_Client_Mailbox::get($old); 1171 $new = Horde_Imap_Client_Mailbox::get($new); 1172 1173 /* Check if old mailbox(es) were subscribed to. */ 1174 $base = $this->listMailboxes($old, Horde_Imap_Client::MBOX_SUBSCRIBED, array('delimiter' => true)); 1175 if (empty($base)) { 1176 $base = $this->listMailboxes($old, Horde_Imap_Client::MBOX_ALL, array('delimiter' => true)); 1177 $base = reset($base); 1178 $subscribed = array(); 1179 } else { 1180 $base = reset($base); 1181 $subscribed = array($base['mailbox']); 1182 } 1183 1184 $all_mboxes = array($base['mailbox']); 1185 if (strlen($base['delimiter'])) { 1186 $search = $old->list_escape . $base['delimiter'] . '*'; 1187 $all_mboxes = array_merge($all_mboxes, $this->listMailboxes($search, Horde_Imap_Client::MBOX_ALL, array('flat' => true))); 1188 $subscribed = array_merge($subscribed, $this->listMailboxes($search, Horde_Imap_Client::MBOX_SUBSCRIBED, array('flat' => true))); 1189 } 1190 1191 $this->_renameMailbox($old, $new); 1192 1193 /* Delete mailbox actions. */ 1194 foreach ($all_mboxes as $val) { 1195 $this->_deleteMailboxPost($val); 1196 } 1197 1198 foreach ($subscribed as $val) { 1199 try { 1200 $this->subscribeMailbox(new Horde_Imap_Client_Mailbox(substr_replace($val, $new, 0, strlen($old)))); 1201 } catch (Horde_Imap_Client_Exception $e) { 1202 // Ignore failed subscription requests 1203 } 1204 } 1205 } 1206 1207 /** 1208 * Rename a mailbox. 1209 * 1210 * @param Horde_Imap_Client_Mailbox $old The old mailbox name. 1211 * @param Horde_Imap_Client_Mailbox $new The new mailbox name. 1212 * 1213 * @throws Horde_Imap_Client_Exception 1214 */ 1215 abstract protected function _renameMailbox(Horde_Imap_Client_Mailbox $old, 1216 Horde_Imap_Client_Mailbox $new); 1217 1218 /** 1219 * Manage subscription status for a mailbox. 1220 * 1221 * @param mixed $mailbox The mailbox to [un]subscribe to. Either a 1222 * Horde_Imap_Client_Mailbox object or a string 1223 * (UTF-8). 1224 * @param boolean $subscribe True to subscribe, false to unsubscribe. 1225 * 1226 * @throws Horde_Imap_Client_Exception 1227 */ 1228 public function subscribeMailbox($mailbox, $subscribe = true) 1229 { 1230 $this->login(); 1231 $this->_subscribeMailbox(Horde_Imap_Client_Mailbox::get($mailbox), (bool)$subscribe); 1232 } 1233 1234 /** 1235 * Manage subscription status for a mailbox. 1236 * 1237 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to [un]subscribe 1238 * to. 1239 * @param boolean $subscribe True to subscribe, false to 1240 * unsubscribe. 1241 * 1242 * @throws Horde_Imap_Client_Exception 1243 */ 1244 abstract protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox, 1245 $subscribe); 1246 1247 /** 1248 * Obtain a list of mailboxes matching a pattern. 1249 * 1250 * @param mixed $pattern The mailbox search pattern(s) (see RFC 3501 1251 * [6.3.8] for the format). A UTF-8 string or an 1252 * array of strings. If a Horde_Imap_Client_Mailbox 1253 * object is given, it is escaped (i.e. wildcard 1254 * patterns are converted to return the miminal 1255 * number of matches possible). 1256 * @param integer $mode Which mailboxes to return. Either: 1257 * - Horde_Imap_Client::MBOX_SUBSCRIBED 1258 * - Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS 1259 * - Horde_Imap_Client::MBOX_UNSUBSCRIBED 1260 * - Horde_Imap_Client::MBOX_ALL 1261 * @param array $options Additional options: 1262 * <pre> 1263 * - attributes: (boolean) If true, return attribute information under 1264 * the 'attributes' key. 1265 * DEFAULT: Do not return this information. 1266 * - children: (boolean) Tell server to return children attribute 1267 * information (\HasChildren, \HasNoChildren). Requires the 1268 * LIST-EXTENDED extension to guarantee this information is 1269 * returned. Server MAY return this attribute without this 1270 * option, or if the CHILDREN extension is available, but it 1271 * is not guaranteed. 1272 * DEFAULT: false 1273 * - delimiter: (boolean) If true, return delimiter information under 1274 * the'delimiter' key. 1275 * DEFAULT: Do not return this information. 1276 * - flat: (boolean) If true, return a flat list of mailbox names only. 1277 * Overrides both the 'attributes' and 'delimiter' options. 1278 * DEFAULT: Do not return flat list. 1279 * - recursivematch: (boolean) Force the server to return information 1280 * about parent mailboxes that don't match other 1281 * selection options, but have some sub-mailboxes that 1282 * do. Information about children is returned in the 1283 * CHILDINFO extended data item ('extended'). Requires 1284 * the LIST-EXTENDED extension. 1285 * DEFAULT: false 1286 * - remote: (boolean) Tell server to return mailboxes that reside on 1287 * another server. Requires the LIST-EXTENDED extension. 1288 * DEFAULT: false 1289 * - special_use: (boolean) Tell server to return special-use attribute 1290 * information (see Horde_Imap_Client SPECIALUSE_* 1291 * constants). Server must support the SPECIAL-USE return 1292 * option for this setting to have any effect. 1293 * DEFAULT: false 1294 * - status: (integer) Tell server to return status information. The 1295 * value is a bitmask that may contain any of: 1296 * - Horde_Imap_Client::STATUS_MESSAGES 1297 * - Horde_Imap_Client::STATUS_RECENT 1298 * - Horde_Imap_Client::STATUS_UIDNEXT 1299 * - Horde_Imap_Client::STATUS_UIDVALIDITY 1300 * - Horde_Imap_Client::STATUS_UNSEEN 1301 * - Horde_Imap_Client::STATUS_HIGHESTMODSEQ 1302 * DEFAULT: 0 1303 * - sort: (boolean) If true, return a sorted list of mailboxes? 1304 * DEFAULT: Do not sort the list. 1305 * - sort_delimiter: (string) If 'sort' is true, this is the delimiter 1306 * used to sort the mailboxes. 1307 * DEFAULT: '.' 1308 * </pre> 1309 * 1310 * @return array If 'flat' option is true, the array values are a list 1311 * of Horde_Imap_Client_Mailbox objects. Otherwise, the 1312 * keys are UTF-8 mailbox names and the values are arrays 1313 * with these keys: 1314 * - attributes: (array) List of lower-cased attributes [only if 1315 * 'attributes' option is true]. 1316 * - delimiter: (string) The delimiter for the mailbox [only if 1317 * 'delimiter' option is true]. 1318 * - extended: (TODO) TODO [only if 'recursivematch' option is true and 1319 * LIST-EXTENDED extension is supported on the server]. 1320 * - mailbox: (Horde_Imap_Client_Mailbox) The mailbox object. 1321 * - status: (array) See status() [only if 'status' option is true]. 1322 * 1323 * @throws Horde_Imap_Client_Exception 1324 */ 1325 public function listMailboxes($pattern, 1326 $mode = Horde_Imap_Client::MBOX_ALL, 1327 array $options = array()) 1328 { 1329 $this->login(); 1330 1331 $pattern = is_array($pattern) 1332 ? array_unique($pattern) 1333 : array($pattern); 1334 1335 /* Prepare patterns. */ 1336 $plist = array(); 1337 foreach ($pattern as $val) { 1338 if ($val instanceof Horde_Imap_Client_Mailbox) { 1339 $val = $val->list_escape; 1340 } 1341 $plist[] = Horde_Imap_Client_Mailbox::get(preg_replace( 1342 array("/\*{2,}/", "/\%{2,}/"), 1343 array('*', '%'), 1344 Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($val) 1345 ), true); 1346 } 1347 1348 if (isset($options['special_use']) && 1349 !$this->queryCapability('SPECIAL-USE')) { 1350 unset($options['special_use']); 1351 } 1352 1353 $ret = $this->_listMailboxes($plist, $mode, $options); 1354 1355 if (!empty($options['status']) && 1356 !$this->queryCapability('LIST-STATUS')) { 1357 foreach ($this->status(array_keys($ret), $options['status']) as $key => $val) { 1358 $ret[$key]['status'] = $val; 1359 } 1360 } 1361 1362 if (empty($options['sort'])) { 1363 return $ret; 1364 } 1365 1366 $list_ob = new Horde_Imap_Client_Mailbox_List(empty($options['flat']) ? array_keys($ret) : $ret); 1367 $sorted = $list_ob->sort(array( 1368 'delimiter' => empty($options['sort_delimiter']) ? '.' : $options['sort_delimiter'] 1369 )); 1370 1371 if (!empty($options['flat'])) { 1372 return $sorted; 1373 } 1374 1375 $out = array(); 1376 foreach ($sorted as $val) { 1377 $out[$val] = $ret[$val]; 1378 } 1379 1380 return $out; 1381 } 1382 1383 /** 1384 * Obtain a list of mailboxes matching a pattern. 1385 * 1386 * @param array $pattern The mailbox search patterns 1387 * (Horde_Imap_Client_Mailbox objects). 1388 * @param integer $mode Which mailboxes to return. 1389 * @param array $options Additional options. 1390 * 1391 * @return array See listMailboxes(). 1392 * 1393 * @throws Horde_Imap_Client_Exception 1394 */ 1395 abstract protected function _listMailboxes($pattern, $mode, $options); 1396 1397 /** 1398 * Obtain status information for a mailbox. 1399 * 1400 * @param mixed $mailbox The mailbox(es) to query. Either a 1401 * Horde_Imap_Client_Mailbox object, a string 1402 * (UTF-8), or an array of objects/strings (since 1403 * 2.10.0). 1404 * @param integer $flags A bitmask of information requested from the 1405 * server. Allowed flags: 1406 * <pre> 1407 * - Horde_Imap_Client::STATUS_MESSAGES 1408 * Return key: messages 1409 * Return format: (integer) The number of messages in the mailbox. 1410 * 1411 * - Horde_Imap_Client::STATUS_RECENT 1412 * Return key: recent 1413 * Return format: (integer) The number of messages with the \Recent 1414 * flag set as currently reported in the mailbox 1415 * 1416 * - Horde_Imap_Client::STATUS_RECENT_TOTAL 1417 * Return key: recent_total 1418 * Return format: (integer) The number of messages with the \Recent 1419 * flag set. This returns the total number of messages 1420 * that have been marked as recent in this mailbox 1421 * since the PHP process began. (since 2.12.0) 1422 * 1423 * - Horde_Imap_Client::STATUS_UIDNEXT 1424 * Return key: uidnext 1425 * Return format: (integer) The next UID to be assigned in the 1426 * mailbox. Only returned if the server automatically 1427 * provides the data. 1428 * 1429 * - Horde_Imap_Client::STATUS_UIDNEXT_FORCE 1430 * Return key: uidnext 1431 * Return format: (integer) The next UID to be assigned in the 1432 * mailbox. This option will always determine this 1433 * value, even if the server does not automatically 1434 * provide this data. 1435 * 1436 * - Horde_Imap_Client::STATUS_UIDVALIDITY 1437 * Return key: uidvalidity 1438 * Return format: (integer) The unique identifier validity of the 1439 * mailbox. 1440 * 1441 * - Horde_Imap_Client::STATUS_UNSEEN 1442 * Return key: unseen 1443 * Return format: (integer) The number of messages which do not have 1444 * the \Seen flag set. 1445 * 1446 * - Horde_Imap_Client::STATUS_FIRSTUNSEEN 1447 * Return key: firstunseen 1448 * Return format: (integer) The sequence number of the first unseen 1449 * message in the mailbox. 1450 * 1451 * - Horde_Imap_Client::STATUS_FLAGS 1452 * Return key: flags 1453 * Return format: (array) The list of defined flags in the mailbox 1454 * (all flags are in lowercase). 1455 * 1456 * - Horde_Imap_Client::STATUS_PERMFLAGS 1457 * Return key: permflags 1458 * Return format: (array) The list of flags that a client can change 1459 * permanently (all flags are in lowercase). 1460 * 1461 * - Horde_Imap_Client::STATUS_HIGHESTMODSEQ 1462 * Return key: highestmodseq 1463 * Return format: (integer) If the server supports the CONDSTORE 1464 * IMAP extension, this will be the highest 1465 * mod-sequence value of all messages in the mailbox. 1466 * Else 0 if CONDSTORE not available or the mailbox 1467 * does not support mod-sequences. 1468 * 1469 * - Horde_Imap_Client::STATUS_SYNCMODSEQ 1470 * Return key: syncmodseq 1471 * Return format: (integer) If caching, and the server supports the 1472 * CONDSTORE IMAP extension, this is the cached 1473 * mod-sequence value of the mailbox when it was opened 1474 * for the first time in this access. Will be null if 1475 * not caching, CONDSTORE not available, or the mailbox 1476 * does not support mod-sequences. 1477 * 1478 * - Horde_Imap_Client::STATUS_SYNCFLAGUIDS 1479 * Return key: syncflaguids 1480 * Return format: (Horde_Imap_Client_Ids) If caching, the server 1481 * supports the CONDSTORE IMAP extension, and the 1482 * mailbox contained cached data when opened for the 1483 * first time in this access, this is the list of UIDs 1484 * in which flags have changed since STATUS_SYNCMODSEQ. 1485 * 1486 * - Horde_Imap_Client::STATUS_SYNCVANISHED 1487 * Return key: syncvanished 1488 * Return format: (Horde_Imap_Client_Ids) If caching, the server 1489 * supports the CONDSTORE IMAP extension, and the 1490 * mailbox contained cached data when opened for the 1491 * first time in this access, this is the list of UIDs 1492 * which have been deleted since STATUS_SYNCMODSEQ. 1493 * 1494 * - Horde_Imap_Client::STATUS_UIDNOTSTICKY 1495 * Return key: uidnotsticky 1496 * Return format: (boolean) If the server supports the UIDPLUS IMAP 1497 * extension, and the queried mailbox does not support 1498 * persistent UIDs, this value will be true. In all 1499 * other cases, this value will be false. 1500 * 1501 * - Horde_Imap_Client::STATUS_FORCE_REFRESH 1502 * Normally, the status information will be cached for a given 1503 * mailbox. Since most PHP requests are generally less than a second, 1504 * this is fine. However, if your script is long running, the status 1505 * information may not be up-to-date. Specifying this flag will ensure 1506 * that the server is always polled for the current mailbox status 1507 * before results are returned. (since 2.14.0) 1508 * 1509 * - Horde_Imap_Client::STATUS_ALL (DEFAULT) 1510 * Shortcut to return 'messages', 'recent', 'uidnext', 'uidvalidity', 1511 * and 'unseen' values. 1512 * </ul> 1513 * @param array $opts Additional options: 1514 * <pre> 1515 * - sort: (boolean) If true, sort the list of mailboxes? (since 2.10.0) 1516 * DEFAULT: Do not sort the list. 1517 * - sort_delimiter: (string) If 'sort' is true, this is the delimiter 1518 * used to sort the mailboxes. (since 2.10.0) 1519 * DEFAULT: '.' 1520 * </pre> 1521 * 1522 * @return array If $mailbox contains multiple mailboxes, an array with 1523 * keys being the UTF-8 mailbox name and values as arrays 1524 * containing the requested keys (see above). 1525 * Otherwise, an array with keys as the requested keys (see 1526 * above) and values as the key data. 1527 * 1528 * @throws Horde_Imap_Client_Exception 1529 */ 1530 public function status($mailbox, $flags = Horde_Imap_Client::STATUS_ALL, 1531 array $opts = array()) 1532 { 1533 $opts = array_merge(array( 1534 'sort' => false, 1535 'sort_delimiter' => '.' 1536 ), $opts); 1537 1538 $this->login(); 1539 1540 if (is_array($mailbox)) { 1541 if (empty($mailbox)) { 1542 return array(); 1543 } 1544 $ret_array = true; 1545 } else { 1546 $mailbox = array($mailbox); 1547 $ret_array = false; 1548 } 1549 1550 $mlist = array_map(array('Horde_Imap_Client_Mailbox', 'get'), $mailbox); 1551 1552 $unselected_flags = array( 1553 'messages' => Horde_Imap_Client::STATUS_MESSAGES, 1554 'recent' => Horde_Imap_Client::STATUS_RECENT, 1555 'uidnext' => Horde_Imap_Client::STATUS_UIDNEXT, 1556 'uidvalidity' => Horde_Imap_Client::STATUS_UIDVALIDITY, 1557 'unseen' => Horde_Imap_Client::STATUS_UNSEEN 1558 ); 1559 1560 if (!$this->statuscache) { 1561 $flags |= Horde_Imap_Client::STATUS_FORCE_REFRESH; 1562 } 1563 1564 if ($flags & Horde_Imap_Client::STATUS_ALL) { 1565 foreach ($unselected_flags as $val) { 1566 $flags |= $val; 1567 } 1568 } 1569 1570 $master = $ret = array(); 1571 1572 /* Catch flags that are not supported. */ 1573 if (($flags & Horde_Imap_Client::STATUS_HIGHESTMODSEQ) && 1574 !isset($this->_temp['enabled']['CONDSTORE'])) { 1575 $master['highestmodseq'] = 0; 1576 $flags &= ~Horde_Imap_Client::STATUS_HIGHESTMODSEQ; 1577 } 1578 1579 if (($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY) && 1580 !$this->queryCapability('UIDPLUS')) { 1581 $master['uidnotsticky'] = false; 1582 $flags &= ~Horde_Imap_Client::STATUS_UIDNOTSTICKY; 1583 } 1584 1585 /* UIDNEXT return options. */ 1586 if ($flags & Horde_Imap_Client::STATUS_UIDNEXT_FORCE) { 1587 $flags |= Horde_Imap_Client::STATUS_UIDNEXT; 1588 } 1589 1590 foreach ($mlist as $val) { 1591 $name = strval($val); 1592 $tmp_flags = $flags; 1593 1594 if ($val->equals($this->_selected)) { 1595 /* Check if already in mailbox. */ 1596 $opened = true; 1597 1598 if ($flags & Horde_Imap_Client::STATUS_FORCE_REFRESH) { 1599 $this->noop(); 1600 } 1601 } else { 1602 /* A list of STATUS options (other than those handled directly 1603 * below) that require the mailbox to be explicitly opened. */ 1604 $opened = ($flags & Horde_Imap_Client::STATUS_FIRSTUNSEEN) || 1605 ($flags & Horde_Imap_Client::STATUS_FLAGS) || 1606 ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) || 1607 ($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY) || 1608 /* Force mailboxes containing wildcards to be accessed via 1609 * STATUS so that wildcards do not return a bunch of 1610 * mailboxes in the LIST-STATUS response. */ 1611 (strpbrk($name, '*%') !== false); 1612 } 1613 1614 $ret[$name] = $master; 1615 $ptr = &$ret[$name]; 1616 1617 /* STATUS_PERMFLAGS requires a read/write mailbox. */ 1618 if ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) { 1619 $this->openMailbox($val, Horde_Imap_Client::OPEN_READWRITE); 1620 $opened = true; 1621 } 1622 1623 /* Handle SYNC related return options. These require the mailbox 1624 * to be opened at least once. */ 1625 if ($flags & Horde_Imap_Client::STATUS_SYNCMODSEQ) { 1626 $this->openMailbox($val); 1627 $ptr['syncmodseq'] = $this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCMODSEQ); 1628 $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCMODSEQ; 1629 $opened = true; 1630 } 1631 1632 if ($flags & Horde_Imap_Client::STATUS_SYNCFLAGUIDS) { 1633 $this->openMailbox($val); 1634 $ptr['syncflaguids'] = $this->getIdsOb($this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCFLAGUIDS)); 1635 $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCFLAGUIDS; 1636 $opened = true; 1637 } 1638 1639 if ($flags & Horde_Imap_Client::STATUS_SYNCVANISHED) { 1640 $this->openMailbox($val); 1641 $ptr['syncvanished'] = $this->getIdsOb($this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCVANISHED)); 1642 $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCVANISHED; 1643 $opened = true; 1644 } 1645 1646 /* Handle RECENT_TOTAL option. */ 1647 if ($flags & Horde_Imap_Client::STATUS_RECENT_TOTAL) { 1648 $this->openMailbox($val); 1649 $ptr['recent_total'] = $this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_RECENT_TOTAL); 1650 $tmp_flags &= ~Horde_Imap_Client::STATUS_RECENT_TOTAL; 1651 $opened = true; 1652 } 1653 1654 if ($opened) { 1655 if ($tmp_flags) { 1656 $tmp = $this->_status(array($val), $tmp_flags); 1657 $ptr += reset($tmp); 1658 } 1659 } else { 1660 $to_process[] = $val; 1661 } 1662 } 1663 1664 if ($flags && !empty($to_process)) { 1665 if ((count($to_process) > 1) && 1666 $this->queryCapability('LIST-STATUS')) { 1667 foreach ($this->listMailboxes($to_process, Horde_Imap_Client::MBOX_ALL, array('status' => $flags)) as $key => $val) { 1668 if (isset($val['status'])) { 1669 $ret[$key] += $val['status']; 1670 } 1671 } 1672 } else { 1673 foreach ($this->_status($to_process, $flags) as $key => $val) { 1674 $ret[$key] += $val; 1675 } 1676 } 1677 } 1678 1679 if (!$opts['sort'] || (count($ret) === 1)) { 1680 return $ret_array 1681 ? $ret 1682 : reset($ret); 1683 } 1684 1685 $list_ob = new Horde_Imap_Client_Mailbox_List(array_keys($ret)); 1686 $sorted = $list_ob->sort(array( 1687 'delimiter' => $opts['sort_delimiter'] 1688 )); 1689 1690 $out = array(); 1691 foreach ($sorted as $val) { 1692 $out[$val] = $ret[$val]; 1693 } 1694 1695 return $out; 1696 } 1697 1698 /** 1699 * Obtain status information for mailboxes. 1700 * 1701 * @param array $mboxes The list of mailbox objects to query. 1702 * @param integer $flags A bitmask of information requested from the 1703 * server. 1704 * 1705 * @return array See array return for status(). 1706 * 1707 * @throws Horde_Imap_Client_Exception 1708 */ 1709 abstract protected function _status($mboxes, $flags); 1710 1711 /** 1712 * Perform a STATUS call on multiple mailboxes at the same time. 1713 * 1714 * This method leverages the LIST-EXTENDED and LIST-STATUS extensions on 1715 * the IMAP server to improve the efficiency of this operation. 1716 * 1717 * @deprecated Use status() instead. 1718 * 1719 * @param array $mailboxes The mailboxes to query. Either 1720 * Horde_Imap_Client_Mailbox objects, strings 1721 * (UTF-8), or a combination of the two. 1722 * @param integer $flags See status(). 1723 * @param array $opts Additional options: 1724 * - sort: (boolean) If true, sort the list of mailboxes? 1725 * DEFAULT: Do not sort the list. 1726 * - sort_delimiter: (string) If 'sort' is true, this is the delimiter 1727 * used to sort the mailboxes. 1728 * DEFAULT: '.' 1729 * 1730 * @return array An array with the keys as the mailbox names (UTF-8) and 1731 * the values as arrays with the requested keys (from the 1732 * mask given in $flags). 1733 */ 1734 public function statusMultiple($mailboxes, 1735 $flags = Horde_Imap_Client::STATUS_ALL, 1736 array $opts = array()) 1737 { 1738 return $this->status($mailboxes, $flags, $opts); 1739 } 1740 1741 /** 1742 * Append message(s) to a mailbox. 1743 * 1744 * @param mixed $mailbox The mailbox to append the message(s) to. Either 1745 * a Horde_Imap_Client_Mailbox object or a string 1746 * (UTF-8). 1747 * @param array $data The message data to append, along with 1748 * additional options. An array of arrays with 1749 * each embedded array having the following 1750 * entries: 1751 * <pre> 1752 * - data: (mixed) The data to append. If a string or a stream resource, 1753 * this will be used as the entire contents of a single message. 1754 * If an array, will catenate all given parts into a single 1755 * message. This array contains one or more arrays with 1756 * two keys: 1757 * - t: (string) Either 'url' or 'text'. 1758 * - v: (mixed) If 't' is 'url', this is the IMAP URL to the message 1759 * part to append. If 't' is 'text', this is either a string or 1760 * resource representation of the message part data. 1761 * DEFAULT: NONE (entry is MANDATORY) 1762 * - flags: (array) An array of flags/keywords to set on the appended 1763 * message. 1764 * DEFAULT: Only the \Recent flag is set. 1765 * - internaldate: (DateTime) The internaldate to set for the appended 1766 * message. 1767 * DEFAULT: internaldate will be the same date as when 1768 * the message was appended. 1769 * </pre> 1770 * @param array $options Additonal options: 1771 * <pre> 1772 * - create: (boolean) Try to create $mailbox if it does not exist? 1773 * DEFAULT: No. 1774 * </pre> 1775 * 1776 * @return Horde_Imap_Client_Ids The UIDs of the appended messages. 1777 * 1778 * @throws Horde_Imap_Client_Exception 1779 */ 1780 public function append($mailbox, $data, array $options = array()) 1781 { 1782 $this->login(); 1783 1784 $mailbox = Horde_Imap_Client_Mailbox::get($mailbox); 1785 1786 $ret = $this->_append($mailbox, $data, $options); 1787 1788 if ($ret instanceof Horde_Imap_Client_Ids) { 1789 return $ret; 1790 } 1791 1792 $uids = $this->getIdsOb(); 1793 1794 while (list(,$val) = each($data)) { 1795 if (is_resource($val['data'])) { 1796 rewind($val['data']); 1797 } 1798 1799 $uids->add($this->_getUidByMessageId( 1800 $mailbox, 1801 Horde_Mime_Headers::parseHeaders($val['data'])->getValue('message-id') 1802 )); 1803 } 1804 1805 return $uids; 1806 } 1807 1808 /** 1809 * Append message(s) to a mailbox. 1810 * 1811 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to append the 1812 * message(s) to. 1813 * @param array $data The message data. 1814 * @param array $options Additional options. 1815 * 1816 * @return mixed A Horde_Imap_Client_Ids object containing the UIDs of 1817 * the appended messages (if server supports UIDPLUS 1818 * extension) or true. 1819 * 1820 * @throws Horde_Imap_Client_Exception 1821 */ 1822 abstract protected function _append(Horde_Imap_Client_Mailbox $mailbox, 1823 $data, $options); 1824 1825 /** 1826 * Request a checkpoint of the currently selected mailbox (RFC 3501 1827 * [6.4.1]). 1828 * 1829 * @throws Horde_Imap_Client_Exception 1830 */ 1831 public function check() 1832 { 1833 // CHECK only useful if we are already authenticated. 1834 if ($this->_isAuthenticated) { 1835 $this->_check(); 1836 } 1837 } 1838 1839 /** 1840 * Request a checkpoint of the currently selected mailbox. 1841 * 1842 * @throws Horde_Imap_Client_Exception 1843 */ 1844 abstract protected function _check(); 1845 1846 /** 1847 * Close the connection to the currently selected mailbox, optionally 1848 * expunging all deleted messages (RFC 3501 [6.4.2]). 1849 * 1850 * @param array $options Additional options: 1851 * - expunge: (boolean) Expunge all messages flagged as deleted? 1852 * DEFAULT: No 1853 * 1854 * @throws Horde_Imap_Client_Exception 1855 */ 1856 public function close(array $options = array()) 1857 { 1858 // This check catches the non-logged in case. 1859 if (is_null($this->_selected)) { 1860 return; 1861 } 1862 1863 /* If we are caching, search for deleted messages. */ 1864 if (!empty($options['expunge']) && $this->_initCache(true)) { 1865 /* Make sure mailbox is read-write to expunge. */ 1866 $this->openMailbox($this->_selected, Horde_Imap_Client::OPEN_READWRITE); 1867 if ($this->_mode == Horde_Imap_Client::OPEN_READONLY) { 1868 throw new Horde_Imap_Client_Exception( 1869 Horde_Imap_Client_Translation::r("Cannot expunge read-only mailbox."), 1870 Horde_Imap_Client_Exception::MAILBOX_READONLY 1871 ); 1872 } 1873 1874 $search_query = new Horde_Imap_Client_Search_Query(); 1875 $search_query->flag(Horde_Imap_Client::FLAG_DELETED, true); 1876 $search_res = $this->search($this->_selected, $search_query); 1877 $mbox = $this->_selected; 1878 } else { 1879 $search_res = null; 1880 } 1881 1882 $this->_close($options); 1883 $this->_selected = null; 1884 $this->_mode = 0; 1885 1886 if (!is_null($search_res)) { 1887 $this->_deleteMsgs($mbox, $search_res['match']); 1888 } 1889 } 1890 1891 /** 1892 * Close the connection to the currently selected mailbox, optionally 1893 * expunging all deleted messages (RFC 3501 [6.4.2]). 1894 * 1895 * @param array $options Additional options. 1896 * 1897 * @throws Horde_Imap_Client_Exception 1898 */ 1899 abstract protected function _close($options); 1900 1901 /** 1902 * Expunge deleted messages from the given mailbox. 1903 * 1904 * @param mixed $mailbox The mailbox to expunge. Either a 1905 * Horde_Imap_Client_Mailbox object or a string 1906 * (UTF-8). 1907 * @param array $options Additional options: 1908 * - delete: (boolean) If true, will flag all messages in 'ids' as 1909 * deleted (since 2.10.0). 1910 * DEFAULT: false 1911 * - ids: (Horde_Imap_Client_Ids) A list of messages to expunge. These 1912 * messages must already be flagged as deleted (unless 'delete' 1913 * is true). 1914 * DEFAULT: All messages marked as deleted will be expunged. 1915 * - list: (boolean) If true, returns the list of expunged messages 1916 * (UIDs only). 1917 * DEFAULT: false 1918 * 1919 * @return Horde_Imap_Client_Ids If 'list' option is true, returns the 1920 * UID list of expunged messages. 1921 * 1922 * @throws Horde_Imap_Client_Exception 1923 */ 1924 public function expunge($mailbox, array $options = array()) 1925 { 1926 // Open mailbox call will handle the login. 1927 $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_READWRITE); 1928 1929 /* Don't expunge if the mailbox is readonly. */ 1930 if ($this->_mode == Horde_Imap_Client::OPEN_READONLY) { 1931 throw new Horde_Imap_Client_Exception( 1932 Horde_Imap_Client_Translation::r("Cannot expunge read-only mailbox."), 1933 Horde_Imap_Client_Exception::MAILBOX_READONLY 1934 ); 1935 } 1936 1937 if (empty($options['ids'])) { 1938 $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL); 1939 } elseif ($options['ids']->isEmpty()) { 1940 return $this->getIdsOb(); 1941 } 1942 1943 return $this->_expunge($options); 1944 } 1945 1946 /** 1947 * Expunge all deleted messages from the given mailbox. 1948 * 1949 * @param array $options Additional options. 1950 * 1951 * @return Horde_Imap_Client_Ids If 'list' option is true, returns the 1952 * list of expunged messages. 1953 * 1954 * @throws Horde_Imap_Client_Exception 1955 */ 1956 abstract protected function _expunge($options); 1957 1958 /** 1959 * Search a mailbox. 1960 * 1961 * @param mixed $mailbox The mailbox to search. 1962 * Either a 1963 * Horde_Imap_Client_Mailbox 1964 * object or a string 1965 * (UTF-8). 1966 * @param Horde_Imap_Client_Search_Query $query The search query. 1967 * Defaults to an ALL 1968 * search. 1969 * @param array $options Additional options: 1970 * <pre> 1971 * - nocache: (boolean) Don't cache the results. 1972 * DEFAULT: false (results cached, if possible) 1973 * - partial: (mixed) The range of results to return (message sequence 1974 * numbers) Only a single range is supported (represented by 1975 * the minimum and maximum values contained in the range 1976 * given). 1977 * DEFAULT: All messages are returned. 1978 * - results: (array) The data to return. Consists of zero or more of 1979 * the following flags: 1980 * - Horde_Imap_Client::SEARCH_RESULTS_COUNT 1981 * - Horde_Imap_Client::SEARCH_RESULTS_MATCH (DEFAULT) 1982 * - Horde_Imap_Client::SEARCH_RESULTS_MAX 1983 * - Horde_Imap_Client::SEARCH_RESULTS_MIN 1984 * - Horde_Imap_Client::SEARCH_RESULTS_SAVE 1985 * - Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY 1986 * - sequence: (boolean) If true, returns an array of sequence numbers. 1987 * DEFAULT: Returns an array of UIDs 1988 * - sort: (array) Sort the returned list of messages. Multiple sort 1989 * criteria can be specified. Any sort criteria can be sorted in 1990 * reverse order (instead of the default ascending order) by 1991 * adding a Horde_Imap_Client::SORT_REVERSE element to the array 1992 * directly before adding the sort element. The following sort 1993 * criteria are available: 1994 * - Horde_Imap_Client::SORT_ARRIVAL 1995 * - Horde_Imap_Client::SORT_CC 1996 * - Horde_Imap_Client::SORT_DATE 1997 * - Horde_Imap_Client::SORT_DISPLAYFROM 1998 * On servers that don't support SORT=DISPLAY, this criteria will 1999 * fallback to doing client-side sorting. 2000 * - Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK 2001 * On servers that don't support SORT=DISPLAY, this criteria will 2002 * fallback to Horde_Imap_Client::SORT_FROM [since 2.4.0]. 2003 * - Horde_Imap_Client::SORT_DISPLAYTO 2004 * On servers that don't support SORT=DISPLAY, this criteria will 2005 * fallback to doing client-side sorting. 2006 * - Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK 2007 * On servers that don't support SORT=DISPLAY, this criteria will 2008 * fallback to Horde_Imap_Client::SORT_TO [since 2.4.0]. 2009 * - Horde_Imap_Client::SORT_FROM 2010 * - Horde_Imap_Client::SORT_SEQUENCE 2011 * - Horde_Imap_Client::SORT_SIZE 2012 * - Horde_Imap_Client::SORT_SUBJECT 2013 * - Horde_Imap_Client::SORT_TO 2014 * 2015 * [On servers that support SEARCH=FUZZY, this criteria is also 2016 * available:] 2017 * - Horde_Imap_Client::SORT_RELEVANCY 2018 * </pre> 2019 * 2020 * @return array An array with the following keys: 2021 * <pre> 2022 * - count: (integer) The number of messages that match the search 2023 * criteria. Always returned. 2024 * - match: (Horde_Imap_Client_Ids) The IDs that match $criteria, sorted 2025 * if the 'sort' modifier was set. Returned if 2026 * Horde_Imap_Client::SEARCH_RESULTS_MATCH is set. 2027 * - max: (integer) The UID (default) or message sequence number (if 2028 * 'sequence' is true) of the highest message that satisifies 2029 * $criteria. Returns null if no matches found. Returned if 2030 * Horde_Imap_Client::SEARCH_RESULTS_MAX is set. 2031 * - min: (integer) The UID (default) or message sequence number (if 2032 * 'sequence' is true) of the lowest message that satisifies 2033 * $criteria. Returns null if no matches found. Returned if 2034 * Horde_Imap_Client::SEARCH_RESULTS_MIN is set. 2035 * - modseq: (integer) The highest mod-sequence for all messages being 2036 * returned. Returned if 'sort' is false, the search query 2037 * includes a MODSEQ command, and the server supports the 2038 * CONDSTORE IMAP extension. 2039 * - relevancy: (array) The list of relevancy scores. Returned if 2040 * Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY is set and 2041 * the server supports FUZZY search matching. 2042 * - save: (boolean) Whether the search results were saved. Returned if 2043 * Horde_Imap_Client::SEARCH_RESULTS_SAVE is set. 2044 * </pre> 2045 * 2046 * @throws Horde_Imap_Client_Exception 2047 */ 2048 public function search($mailbox, $query = null, array $options = array()) 2049 { 2050 $this->login(); 2051 2052 if (empty($options['results'])) { 2053 $options['results'] = array( 2054 Horde_Imap_Client::SEARCH_RESULTS_MATCH, 2055 Horde_Imap_Client::SEARCH_RESULTS_COUNT 2056 ); 2057 } elseif (!in_array(Horde_Imap_Client::SEARCH_RESULTS_COUNT, $options['results'])) { 2058 $options['results'][] = Horde_Imap_Client::SEARCH_RESULTS_COUNT; 2059 } 2060 2061 // Default to an ALL search. 2062 if (is_null($query)) { 2063 $query = new Horde_Imap_Client_Search_Query(); 2064 } 2065 2066 // Check for SEARCHRES support. 2067 if ((($pos = array_search(Horde_Imap_Client::SEARCH_RESULTS_SAVE, $options['results'])) !== false) && 2068 !$this->queryCapability('SEARCHRES')) { 2069 unset($options['results'][$pos]); 2070 } 2071 2072 // Check for SORT-related options. 2073 if (!empty($options['sort'])) { 2074 $sort = $this->queryCapability('SORT'); 2075 foreach ($options['sort'] as $key => $val) { 2076 switch ($val) { 2077 case Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK: 2078 $options['sort'][$key] = (!is_array($sort) || !in_array('DISPLAY', $sort)) 2079 ? Horde_Imap_Client::SORT_FROM 2080 : Horde_Imap_Client::SORT_DISPLAYFROM; 2081 break; 2082 2083 case Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK: 2084 $options['sort'][$key] = (!is_array($sort) || !in_array('DISPLAY', $sort)) 2085 ? Horde_Imap_Client::SORT_TO 2086 : Horde_Imap_Client::SORT_DISPLAYTO; 2087 break; 2088 } 2089 } 2090 } 2091 2092 // Check for supported charset. 2093 $options['_query'] = $query->build($this->capability()); 2094 if (!is_null($options['_query']['charset']) && 2095 array_key_exists($options['_query']['charset'], $this->_init['s_charset']) && 2096 !$this->_init['s_charset'][$options['_query']['charset']]) { 2097 foreach (array_merge(array_keys(array_filter($this->_init['s_charset'])), array('US-ASCII')) as $val) { 2098 try { 2099 $new_query = clone $query; 2100 $new_query->charset($val); 2101 break; 2102 } catch (Horde_Imap_Client_Exception_SearchCharset $e) { 2103 unset($new_query); 2104 } 2105 } 2106 2107 if (!isset($new_query)) { 2108 throw $e; 2109 } 2110 2111 $query = $new_query; 2112 $options['_query'] = $query->build($this->capability()); 2113 } 2114 2115 /* RFC 6203: MUST NOT request relevancy results if we are not using 2116 * FUZZY searching. */ 2117 if (in_array(Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY, $options['results']) && 2118 !in_array('SEARCH=FUZZY', $options['_query']['exts_used'])) { 2119 throw new InvalidArgumentException('Cannot specify RELEVANCY results if not doing a FUZZY search.'); 2120 } 2121 2122 /* Check for partial matching. */ 2123 if (!empty($options['partial'])) { 2124 $pids = $this->getIdsOb($options['partial'], true)->range_string; 2125 if (!strlen($pids)) { 2126 throw new InvalidArgumentException('Cannot specify empty sequence range for a PARTIAL search.'); 2127 } 2128 2129 if (strpos($pids, ':') === false) { 2130 $pids .= ':' . $pids; 2131 } 2132 2133 $options['partial'] = $pids; 2134 } 2135 2136 /* Optimization - if query is just for a count of either RECENT or 2137 * ALL messages, we can send status information instead. Can't 2138 * optimize with unseen queries because we may cause an infinite loop 2139 * between here and the status() call. */ 2140 if ((count($options['results']) === 1) && 2141 (reset($options['results']) == Horde_Imap_Client::SEARCH_RESULTS_COUNT)) { 2142 switch ($options['_query']['query']) { 2143 case 'ALL': 2144 $ret = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES); 2145 return array('count' => $ret['messages']); 2146 2147 case 'RECENT': 2148 $ret = $this->status($this->_selected, Horde_Imap_Client::STATUS_RECENT); 2149 return array('count' => $ret['recent']); 2150 } 2151 } 2152 2153 $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO); 2154 2155 /* Take advantage of search result caching. If CONDSTORE available, 2156 * we can cache all queries and invalidate the cache when the MODSEQ 2157 * changes. If CONDSTORE not available, we can only store queries 2158 * that don't involve flags. We store results by hashing the options 2159 * array - the generated query is already added to '_query' key 2160 * above. */ 2161 $cache = null; 2162 if (empty($options['nocache']) && 2163 $this->_initCache(true) && 2164 (isset($this->_temp['enabled']['CONDSTORE']) || 2165 !$query->flagSearch())) { 2166 $cache = $this->_getSearchCache('search', $options); 2167 if (isset($cache['data'])) { 2168 if (isset($cache['data']['match'])) { 2169 $cache['data']['match'] = $this->getIdsOb($cache['data']['match']); 2170 } 2171 return $cache['data']; 2172 } 2173 } 2174 2175 /* Optimization: Catch when there are no messages in a mailbox. */ 2176 $status_res = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES | Horde_Imap_Client::STATUS_HIGHESTMODSEQ); 2177 if ($status_res['messages'] || 2178 in_array(Horde_Imap_Client::SEARCH_RESULTS_SAVE, $options['results'])) { 2179 /* RFC 7162 [3.1.2.2] - trying to do a MODSEQ SEARCH on a mailbox 2180 * that doesn't support it will return BAD. */ 2181 if (in_array('CONDSTORE', $options['_query']['exts']) && 2182 !$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) { 2183 throw new Horde_Imap_Client_Exception( 2184 Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."), 2185 Horde_Imap_Client_Exception::MBOXNOMODSEQ 2186 ); 2187 } 2188 2189 $ret = $this->_search($query, $options); 2190 } else { 2191 $ret = array( 2192 'count' => 0, 2193 'match' => $this->getIdsOb(), 2194 'max' => null, 2195 'min' => null, 2196 'relevancy' => array() 2197 ); 2198 if (isset($status_res['highestmodseq'])) { 2199 $ret['modseq'] = $status_res['highestmodseq']; 2200 } 2201 } 2202 2203 if ($cache) { 2204 $save = $ret; 2205 if (isset($save['match'])) { 2206 $save['match'] = strval($ret['match']); 2207 } 2208 $this->_setSearchCache($save, $cache); 2209 } 2210 2211 return $ret; 2212 } 2213 2214 /** 2215 * Search a mailbox. 2216 * 2217 * @param object $query The search query. 2218 * @param array $options Additional options. The '_query' key contains 2219 * the value of $query->build(). 2220 * 2221 * @return Horde_Imap_Client_Ids An array of IDs. 2222 * 2223 * @throws Horde_Imap_Client_Exception 2224 */ 2225 abstract protected function _search($query, $options); 2226 2227 /** 2228 * Set the comparator to use for searching/sorting (RFC 5255). 2229 * 2230 * @param string $comparator The comparator string (see RFC 4790 [3.1] - 2231 * "collation-id" - for format). The reserved 2232 * string 'default' can be used to select 2233 * the default comparator. 2234 * 2235 * @throws Horde_Imap_Client_Exception 2236 * @throws Horde_Imap_Client_Exception_NoSupportExtension 2237 */ 2238 public function setComparator($comparator = null) 2239 { 2240 $comp = is_null($comparator) 2241 ? $this->getParam('comparator') 2242 : $comparator; 2243 if (is_null($comp)) { 2244 return; 2245 } 2246 2247 $this->login(); 2248 2249 $i18n = $this->queryCapability('I18NLEVEL'); 2250 if (empty($i18n) || (max($i18n) < 2)) { 2251 throw new Horde_Imap_Client_Exception_NoSupportExtension( 2252 'I18NLEVEL', 2253 'The IMAP server does not support changing SEARCH/SORT comparators.' 2254 ); 2255 } 2256 2257 $this->_setComparator($comp); 2258 } 2259 2260 /** 2261 * Set the comparator to use for searching/sorting (RFC 5255). 2262 * 2263 * @param string $comparator The comparator string (see RFC 4790 [3.1] - 2264 * "collation-id" - for format). The reserved 2265 * string 'default' can be used to select 2266 * the default comparator. 2267 * 2268 * @throws Horde_Imap_Client_Exception 2269 */ 2270 abstract protected function _setComparator($comparator); 2271 2272 /** 2273 * Get the comparator used for searching/sorting (RFC 5255). 2274 * 2275 * @return mixed Null if the default comparator is being used, or an 2276 * array of comparator information (see RFC 5255 [4.8]). 2277 * 2278 * @throws Horde_Imap_Client_Exception 2279 */ 2280 public function getComparator() 2281 { 2282 $this->login(); 2283 2284 $i18n = $this->queryCapability('I18NLEVEL'); 2285 if (empty($i18n) || (max($i18n) < 2)) { 2286 return null; 2287 } 2288 2289 return $this->_getComparator(); 2290 } 2291 2292 /** 2293 * Get the comparator used for searching/sorting (RFC 5255). 2294 * 2295 * @return mixed Null if the default comparator is being used, or an 2296 * array of comparator information (see RFC 5255 [4.8]). 2297 * 2298 * @throws Horde_Imap_Client_Exception 2299 */ 2300 abstract protected function _getComparator(); 2301 2302 /** 2303 * Thread sort a given list of messages (RFC 5256). 2304 * 2305 * @param mixed $mailbox The mailbox to query. Either a 2306 * Horde_Imap_Client_Mailbox object or a string 2307 * (UTF-8). 2308 * @param array $options Additional options: 2309 * <pre> 2310 * - criteria: (mixed) The following thread criteria are available: 2311 * - Horde_Imap_Client::THREAD_ORDEREDSUBJECT 2312 * - Horde_Imap_Client::THREAD_REFERENCES 2313 * - Horde_Imap_Client::THREAD_REFS 2314 * Other algorithms can be explicitly specified by passing the IMAP 2315 * thread algorithm in as a string value. 2316 * DEFAULT: Horde_Imap_Client::THREAD_ORDEREDSUBJECT 2317 * - search: (Horde_Imap_Client_Search_Query) The search query. 2318 * DEFAULT: All messages in mailbox included in thread sort. 2319 * - sequence: (boolean) If true, each message is stored and referred to 2320 * by its message sequence number. 2321 * DEFAULT: Stored/referred to by UID. 2322 * </pre> 2323 * 2324 * @return Horde_Imap_Client_Data_Thread A thread data object. 2325 * 2326 * @throws Horde_Imap_Client_Exception 2327 */ 2328 public function thread($mailbox, array $options = array()) 2329 { 2330 // Open mailbox call will handle the login. 2331 $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO); 2332 2333 /* Take advantage of search result caching. If CONDSTORE available, 2334 * we can cache all queries and invalidate the cache when the MODSEQ 2335 * changes. If CONDSTORE not available, we can only store queries 2336 * that don't involve flags. See search() for similar caching. */ 2337 $cache = null; 2338 if ($this->_initCache(true) && 2339 (isset($this->_temp['enabled']['CONDSTORE']) || 2340 empty($options['search']) || 2341 !$options['search']->flagSearch())) { 2342 $cache = $this->_getSearchCache('thread', $options); 2343 if (isset($cache['data']) && 2344 ($cache['data'] instanceof Horde_Imap_Client_Data_Thread)) { 2345 return $cache['data']; 2346 } 2347 } 2348 2349 $status_res = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES); 2350 2351 $ob = $status_res['messages'] 2352 ? $this->_thread($options) 2353 : new Horde_Imap_Client_Data_Thread(array(), empty($options['sequence']) ? 'uid' : 'sequence'); 2354 2355 if ($cache) { 2356 $this->_setSearchCache($ob, $cache); 2357 } 2358 2359 return $ob; 2360 } 2361 2362 /** 2363 * Thread sort a given list of messages (RFC 5256). 2364 * 2365 * @param array $options Additional options. See thread(). 2366 * 2367 * @return Horde_Imap_Client_Data_Thread A thread data object. 2368 * 2369 * @throws Horde_Imap_Client_Exception 2370 */ 2371 abstract protected function _thread($options); 2372 2373 /** 2374 * Fetch message data (see RFC 3501 [6.4.5]). 2375 * 2376 * @param mixed $mailbox The mailbox to search. 2377 * Either a 2378 * Horde_Imap_Client_Mailbox 2379 * object or a string (UTF-8). 2380 * @param Horde_Imap_Client_Fetch_Query $query Fetch query object. 2381 * @param array $options Additional options: 2382 * - changedsince: (integer) Only return messages that have a 2383 * mod-sequence larger than this value. This option 2384 * requires the CONDSTORE IMAP extension (if not present, 2385 * this value is ignored). Additionally, the mailbox 2386 * must support mod-sequences or an exception will be 2387 * thrown. If valid, this option implicity adds the 2388 * mod-sequence fetch criteria to the fetch command. 2389 * DEFAULT: Mod-sequence values are ignored. 2390 * - exists: (boolean) Ensure that all ids returned exist on the server. 2391 * If false, the list of ids returned in the results object 2392 * is not guaranteed to reflect the current state of the 2393 * remote mailbox. 2394 * DEFAULT: false 2395 * - ids: (Horde_Imap_Client_Ids) A list of messages to fetch data from. 2396 * DEFAULT: All messages in $mailbox will be fetched. 2397 * - nocache: (boolean) If true, will not cache the results (previously 2398 * cached data will still be used to generate results) [since 2399 * 2.8.0]. 2400 * DEFAULT: false 2401 * 2402 * @return Horde_Imap_Client_Fetch_Results A results object. 2403 * 2404 * @throws Horde_Imap_Client_Exception 2405 * @throws Horde_Imap_Client_Exception_NoSupportExtension 2406 */ 2407 public function fetch($mailbox, $query, array $options = array()) 2408 { 2409 try { 2410 $ret = $this->_fetchWrapper($mailbox, $query, $options); 2411 unset($this->_temp['fetch_nocache']); 2412 return $ret; 2413 } catch (Exception $e) { 2414 unset($this->_temp['fetch_nocache']); 2415 throw $e; 2416 } 2417 } 2418 2419 /** 2420 * Wrapper for fetch() to allow internal state to be reset on exception. 2421 * 2422 * @internal 2423 * @see fetch() 2424 */ 2425 private function _fetchWrapper($mailbox, $query, $options) 2426 { 2427 $this->login(); 2428 2429 $query = clone $query; 2430 2431 $cache_array = $header_cache = $new_query = array(); 2432 2433 if (empty($options['ids'])) { 2434 $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL); 2435 } elseif ($options['ids']->isEmpty()) { 2436 return new Horde_Imap_Client_Fetch_Results($this->_fetchDataClass); 2437 } elseif ($options['ids']->search_res && 2438 !$this->queryCapability('SEARCHRES')) { 2439 /* SEARCHRES requires server support. */ 2440 throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES'); 2441 } 2442 2443 $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO); 2444 $mbox_ob = $this->_mailboxOb(); 2445 2446 if (!empty($options['nocache'])) { 2447 $this->_temp['fetch_nocache'] = true; 2448 } 2449 2450 $cf = $this->_initCache(true) 2451 ? $this->_cacheFields() 2452 : array(); 2453 2454 if (!empty($cf)) { 2455 /* If using cache, we store by UID so we need to return UIDs. */ 2456 $query->uid(); 2457 } 2458 2459 $modseq_check = !empty($options['changedsince']); 2460 if ($query->contains(Horde_Imap_Client::FETCH_MODSEQ)) { 2461 if (!isset($this->_temp['enabled']['CONDSTORE'])) { 2462 unset($query[Horde_Imap_Client::FETCH_MODSEQ]); 2463 } elseif (empty($options['changedsince'])) { 2464 $modseq_check = true; 2465 } 2466 } 2467 2468 if ($modseq_check && 2469 !$mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) { 2470 /* RFC 7162 [3.1.2.2] - trying to do a MODSEQ FETCH on a mailbox 2471 * that doesn't support it will return BAD. */ 2472 throw new Horde_Imap_Client_Exception( 2473 Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."), 2474 Horde_Imap_Client_Exception::MBOXNOMODSEQ 2475 ); 2476 } 2477 2478 /* Determine if caching is available and if anything in $query is 2479 * cacheable. */ 2480 foreach ($cf as $k => $v) { 2481 if (isset($query[$k])) { 2482 switch ($k) { 2483 case Horde_Imap_Client::FETCH_ENVELOPE: 2484 case Horde_Imap_Client::FETCH_FLAGS: 2485 case Horde_Imap_Client::FETCH_IMAPDATE: 2486 case Horde_Imap_Client::FETCH_SIZE: 2487 case Horde_Imap_Client::FETCH_STRUCTURE: 2488 $cache_array[$k] = $v; 2489 break; 2490 2491 case Horde_Imap_Client::FETCH_HEADERS: 2492 $this->_temp['headers_caching'] = array(); 2493 2494 foreach ($query[$k] as $key => $val) { 2495 /* Only cache if directly requested. Iterate through 2496 * requests to ensure at least one can be cached. */ 2497 if (!empty($val['cache']) && !empty($val['peek'])) { 2498 $cache_array[$k] = $v; 2499 ksort($val); 2500 $header_cache[$key] = hash( 2501 (PHP_MINOR_VERSION >= 4) ? 'fnv132' : 'sha1', 2502 serialize($val) 2503 ); 2504 } 2505 } 2506 break; 2507 } 2508 } 2509 } 2510 2511 $ret = new Horde_Imap_Client_Fetch_Results( 2512 $this->_fetchDataClass, 2513 $options['ids']->sequence ? Horde_Imap_Client_Fetch_Results::SEQUENCE : Horde_Imap_Client_Fetch_Results::UID 2514 ); 2515 2516 /* If nothing is cacheable, we can do a straight search. */ 2517 if (empty($cache_array)) { 2518 $options['_query'] = $query; 2519 $this->_fetch($ret, array($options)); 2520 return $ret; 2521 } 2522 2523 $cs_ret = empty($options['changedsince']) 2524 ? null 2525 : clone $ret; 2526 2527 /* Convert special searches to UID lists and create mapping. */ 2528 $ids = $this->resolveIds($this->_selected, $options['ids'], empty($options['exists']) ? 1 : 2); 2529 2530 /* Add non-user settable cache fields. */ 2531 $cache_array[Horde_Imap_Client::FETCH_DOWNGRADED] = self::CACHE_DOWNGRADED; 2532 2533 /* Get the cached values. */ 2534 $data = $this->_cache->get($this->_selected, $ids->ids, array_values($cache_array), $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY)); 2535 2536 /* Build a list of what we still need. */ 2537 $map = array_flip($mbox_ob->map->map); 2538 $sequence = $options['ids']->sequence; 2539 foreach ($ids as $uid) { 2540 $crit = clone $query; 2541 2542 if ($sequence) { 2543 if (!isset($map[$uid])) { 2544 continue; 2545 } 2546 $entry_idx = $map[$uid]; 2547 } else { 2548 $entry_idx = $uid; 2549 unset($crit[Horde_Imap_Client::FETCH_UID]); 2550 } 2551 2552 $entry = $ret->get($entry_idx); 2553 2554 if (isset($map[$uid])) { 2555 $entry->setSeq($map[$uid]); 2556 unset($crit[Horde_Imap_Client::FETCH_SEQ]); 2557 } 2558 2559 $entry->setUid($uid); 2560 2561 foreach ($cache_array as $key => $cid) { 2562 switch ($key) { 2563 case Horde_Imap_Client::FETCH_DOWNGRADED: 2564 if (!empty($data[$uid][$cid])) { 2565 $entry->setDowngraded(true); 2566 } 2567 break; 2568 2569 case Horde_Imap_Client::FETCH_ENVELOPE: 2570 if (isset($data[$uid][$cid]) && 2571 ($data[$uid][$cid] instanceof Horde_Imap_Client_Data_Envelope)) { 2572 $entry->setEnvelope($data[$uid][$cid]); 2573 unset($crit[$key]); 2574 } 2575 break; 2576 2577 case Horde_Imap_Client::FETCH_FLAGS: 2578 if (isset($data[$uid][$cid]) && 2579 is_array($data[$uid][$cid])) { 2580 $entry->setFlags($data[$uid][$cid]); 2581 unset($crit[$key]); 2582 } 2583 break; 2584 2585 case Horde_Imap_Client::FETCH_HEADERS: 2586 foreach ($header_cache as $hkey => $hval) { 2587 if (isset($data[$uid][$cid][$hval])) { 2588 /* We have found a cached entry with the same 2589 * MD5 sum. */ 2590 $entry->setHeaders($hkey, $data[$uid][$cid][$hval]); 2591 $crit->remove($key, $hkey); 2592 } else { 2593 $this->_temp['headers_caching'][$hkey] = $hval; 2594 } 2595 } 2596 break; 2597 2598 case Horde_Imap_Client::FETCH_IMAPDATE: 2599 if (isset($data[$uid][$cid]) && 2600 ($data[$uid][$cid] instanceof Horde_Imap_Client_DateTime)) { 2601 $entry->setImapDate($data[$uid][$cid]); 2602 unset($crit[$key]); 2603 } 2604 break; 2605 2606 case Horde_Imap_Client::FETCH_SIZE: 2607 if (isset($data[$uid][$cid])) { 2608 $entry->setSize($data[$uid][$cid]); 2609 unset($crit[$key]); 2610 } 2611 break; 2612 2613 case Horde_Imap_Client::FETCH_STRUCTURE: 2614 if (isset($data[$uid][$cid]) && 2615 ($data[$uid][$cid] instanceof Horde_Mime_Part)) { 2616 $entry->setStructure($data[$uid][$cid]); 2617 unset($crit[$key]); 2618 } 2619 break; 2620 } 2621 } 2622 2623 if (count($crit)) { 2624 $sig = $crit->hash(); 2625 if (isset($new_query[$sig])) { 2626 $new_query[$sig]['i'][] = $entry_idx; 2627 } else { 2628 $new_query[$sig] = array( 2629 'c' => $crit, 2630 'i' => array($entry_idx) 2631 ); 2632 } 2633 } 2634 } 2635 2636 $to_fetch = array(); 2637 foreach ($new_query as $val) { 2638 $ids_ob = $this->getIdsOb(null, $sequence); 2639 $ids_ob->duplicates = true; 2640 $ids_ob->add($val['i']); 2641 $to_fetch[] = array_merge($options, array( 2642 '_query' => $val['c'], 2643 'ids' => $ids_ob 2644 )); 2645 } 2646 2647 if (!empty($to_fetch)) { 2648 $this->_fetch(is_null($cs_ret) ? $ret : $cs_ret, $to_fetch); 2649 } 2650 2651 if (is_null($cs_ret)) { 2652 return $ret; 2653 } 2654 2655 /* If doing changedsince query, and all other data is cached, we still 2656 * need to hit IMAP server to determine proper results set. */ 2657 if (empty($new_query)) { 2658 $squery = new Horde_Imap_Client_Search_Query(); 2659 $squery->modseq($options['changedsince'] + 1); 2660 $squery->ids($options['ids']); 2661 2662 $cs = $this->search($this->_selected, $squery, array( 2663 'sequence' => $sequence 2664 )); 2665 2666 foreach ($cs['match'] as $val) { 2667 $entry = $ret->get($val); 2668 if ($sequence) { 2669 $entry->setSeq($val); 2670 } else { 2671 $entry->setUid($val); 2672 } 2673 $cs_ret[$val] = $entry; 2674 } 2675 } else { 2676 foreach ($cs_ret as $key => $val) { 2677 $val->merge($ret->get($key)); 2678 } 2679 } 2680 2681 return $cs_ret; 2682 } 2683 2684 /** 2685 * Fetch message data. 2686 * 2687 * Fetch queries should be grouped in the $queries argument. Each value 2688 * is an array of fetch options, with the fetch query stored in the 2689 * '_query' parameter. IMPORTANT: All queries must have the same ID 2690 * type (either sequence or UID). 2691 * 2692 * @param Horde_Imap_Client_Fetch_Results $results Fetch results. 2693 * @param array $queries The list of queries. 2694 * 2695 * @throws Horde_Imap_Client_Exception 2696 */ 2697 abstract protected function _fetch(Horde_Imap_Client_Fetch_Results $results, 2698 $queries); 2699 2700 /** 2701 * Get the list of vanished messages (UIDs that have been expunged since a 2702 * given mod-sequence value). 2703 * 2704 * @param mixed $mailbox The mailbox to query. Either a 2705 * Horde_Imap_Client_Mailbox object or a string 2706 * (UTF-8). 2707 * @param integer $modseq Search for expunged messages after this 2708 * mod-sequence value. 2709 * @param array $opts Additional options: 2710 * - ids: (Horde_Imap_Client_Ids) Restrict to these UIDs. 2711 * DEFAULT: Returns full list of UIDs vanished (QRESYNC only). 2712 * This option is REQUIRED for non-QRESYNC servers or 2713 * else an empty list will be returned. 2714 * 2715 * @return Horde_Imap_Client_Ids List of UIDs that have vanished. 2716 * 2717 * @throws Horde_Imap_Client_NoSupportExtension 2718 */ 2719 public function vanished($mailbox, $modseq, array $opts = array()) 2720 { 2721 $this->login(); 2722 2723 $qresync = $this->queryCapability('QRESYNC'); 2724 2725 if (empty($opts['ids'])) { 2726 if (!$qresync) { 2727 return $this->getIdsOb(); 2728 } 2729 $opts['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL); 2730 } elseif ($opts['ids']->isEmpty()) { 2731 return $this->getIdsOb(); 2732 } elseif ($opts['ids']->sequence) { 2733 throw new InvalidArgumentException('Vanished requires UIDs.'); 2734 } 2735 2736 $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO); 2737 2738 if ($qresync) { 2739 if (!$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) { 2740 throw new Horde_Imap_Client_Exception( 2741 Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."), 2742 Horde_Imap_Client_Exception::MBOXNOMODSEQ 2743 ); 2744 } 2745 2746 return $this->_vanished(max(1, $modseq), $opts['ids']); 2747 } 2748 2749 $ids = $this->resolveIds($mailbox, $opts['ids']); 2750 2751 $squery = new Horde_Imap_Client_Search_Query(); 2752 $squery->ids($this->getIdsOb($ids->range_string)); 2753 $search = $this->search($mailbox, $squery, array( 2754 'nocache' => true 2755 )); 2756 2757 return $this->getIdsOb(array_diff($ids->ids, $search['match']->ids)); 2758 } 2759 2760 /** 2761 * Get the list of vanished messages. 2762 * 2763 * @param integer $modseq Mod-sequence value. 2764 * @param Horde_Imap_Client_Ids $ids UIDs. 2765 * 2766 * @return Horde_Imap_Client_Ids List of UIDs that have vanished. 2767 */ 2768 abstract protected function _vanished($modseq, Horde_Imap_Client_Ids $ids); 2769 2770 /** 2771 * Store message flag data (see RFC 3501 [6.4.6]). 2772 * 2773 * @param mixed $mailbox The mailbox containing the messages to modify. 2774 * Either a Horde_Imap_Client_Mailbox object or a 2775 * string (UTF-8). 2776 * @param array $options Additional options: 2777 * - add: (array) An array of flags to add. 2778 * DEFAULT: No flags added. 2779 * - ids: (Horde_Imap_Client_Ids) The list of messages to modify. 2780 * DEFAULT: All messages in $mailbox will be modified. 2781 * - remove: (array) An array of flags to remove. 2782 * DEFAULT: No flags removed. 2783 * - replace: (array) Replace the current flags with this set 2784 * of flags. Overrides both the 'add' and 'remove' options. 2785 * DEFAULT: No replace is performed. 2786 * - unchangedsince: (integer) Only changes flags if the mod-sequence ID 2787 * of the message is equal or less than this value. 2788 * Requires the CONDSTORE IMAP extension on the server. 2789 * Also requires the mailbox to support mod-sequences. 2790 * Will throw an exception if either condition is not 2791 * met. 2792 * DEFAULT: mod-sequence is ignored when applying 2793 * changes 2794 * 2795 * @return Horde_Imap_Client_Ids A Horde_Imap_Client_Ids object 2796 * containing the list of IDs that failed 2797 * the 'unchangedsince' test. 2798 * 2799 * @throws Horde_Imap_Client_Exception 2800 * @throws Horde_Imap_Client_Exception_NoSupportExtension 2801 */ 2802 public function store($mailbox, array $options = array()) 2803 { 2804 // Open mailbox call will handle the login. 2805 $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_READWRITE); 2806 2807 /* SEARCHRES requires server support. */ 2808 if (empty($options['ids'])) { 2809 $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL); 2810 } elseif ($options['ids']->isEmpty()) { 2811 return $this->getIdsOb(); 2812 } elseif ($options['ids']->search_res && 2813 !$this->queryCapability('SEARCHRES')) { 2814 throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES'); 2815 } 2816 2817 if (!empty($options['unchangedsince'])) { 2818 if (!isset($this->_temp['enabled']['CONDSTORE'])) { 2819 throw new Horde_Imap_Client_Exception_NoSupportExtension('CONDSTORE'); 2820 } 2821 2822 /* RFC 7162 [3.1.2.2] - trying to do a UNCHANGEDSINCE STORE on a 2823 * mailbox that doesn't support it will return BAD. */ 2824 if (!$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) { 2825 throw new Horde_Imap_Client_Exception( 2826 Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."), 2827 Horde_Imap_Client_Exception::MBOXNOMODSEQ 2828 ); 2829 } 2830 } 2831 2832 return $this->_store($options); 2833 } 2834 2835 /** 2836 * Store message flag data. 2837 * 2838 * @param array $options Additional options. 2839 * 2840 * @return Horde_Imap_Client_Ids A Horde_Imap_Client_Ids object 2841 * containing the list of IDs that failed 2842 * the 'unchangedsince' test. 2843 * 2844 * @throws Horde_Imap_Client_Exception 2845 */ 2846 abstract protected function _store($options); 2847 2848 /** 2849 * Copy messages to another mailbox. 2850 * 2851 * @param mixed $source The source mailbox. Either a 2852 * Horde_Imap_Client_Mailbox object or a string 2853 * (UTF-8). 2854 * @param mixed $dest The destination mailbox. Either a 2855 * Horde_Imap_Client_Mailbox object or a string 2856 * (UTF-8). 2857 * @param array $options Additional options: 2858 * - create: (boolean) Try to create $dest if it does not exist? 2859 * DEFAULT: No. 2860 * - force_map: (boolean) Forces the array mapping to always be 2861 * returned. [@since 2.19.0] 2862 * - ids: (Horde_Imap_Client_Ids) The list of messages to copy. 2863 * DEFAULT: All messages in $mailbox will be copied. 2864 * - move: (boolean) If true, delete the original messages. 2865 * DEFAULT: Original messages are not deleted. 2866 * 2867 * @return mixed An array mapping old UIDs (keys) to new UIDs (values) on 2868 * success (only guaranteed if 'force_map' is true) or 2869 * true. 2870 * 2871 * @throws Horde_Imap_Client_Exception 2872 * @throws Horde_Imap_Client_Exception_NoSupportExtension 2873 */ 2874 public function copy($source, $dest, array $options = array()) 2875 { 2876 // Open mailbox call will handle the login. 2877 $this->openMailbox($source, empty($options['move']) ? Horde_Imap_Client::OPEN_AUTO : Horde_Imap_Client::OPEN_READWRITE); 2878 2879 /* SEARCHRES requires server support. */ 2880 if (empty($options['ids'])) { 2881 $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL); 2882 } elseif ($options['ids']->isEmpty()) { 2883 return array(); 2884 } elseif ($options['ids']->search_res && 2885 !$this->queryCapability('SEARCHRES')) { 2886 throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES'); 2887 } 2888 2889 $dest = Horde_Imap_Client_Mailbox::get($dest); 2890 $res = $this->_copy($dest, $options); 2891 2892 if (($res === true) && !empty($options['force_map'])) { 2893 /* Need to manually create mapping from Message-ID data. */ 2894 $query = new Horde_Imap_Client_Fetch_Query(); 2895 $query->envelope(); 2896 $fetch = $this->fetch($source, $query, array( 2897 'ids' => $options['ids'] 2898 )); 2899 2900 $res = array(); 2901 foreach ($fetch as $val) { 2902 if ($uid = $this->_getUidByMessageId($dest, $val->getEnvelope()->message_id)) { 2903 $res[$val->getUid()] = $uid; 2904 } 2905 } 2906 } 2907 2908 return $res; 2909 } 2910 2911 /** 2912 * Copy messages to another mailbox. 2913 * 2914 * @param Horde_Imap_Client_Mailbox $dest The destination mailbox. 2915 * @param array $options Additional options. 2916 * 2917 * @return mixed An array mapping old UIDs (keys) to new UIDs (values) on 2918 * success (if the IMAP server and/or driver support the 2919 * UIDPLUS extension) or true. 2920 * 2921 * @throws Horde_Imap_Client_Exception 2922 */ 2923 abstract protected function _copy(Horde_Imap_Client_Mailbox $dest, 2924 $options); 2925 2926 /** 2927 * Set quota limits. The server must support the IMAP QUOTA extension 2928 * (RFC 2087). 2929 * 2930 * @param mixed $root The quota root. Either a 2931 * Horde_Imap_Client_Mailbox object or a string 2932 * (UTF-8). 2933 * @param array $resources The resource values to set. Keys are the 2934 * resource atom name; value is the resource 2935 * value. 2936 * 2937 * @throws Horde_Imap_Client_Exception 2938 * @throws Horde_Imap_Client_Exception_NoSupportExtension 2939 */ 2940 public function setQuota($root, array $resources = array()) 2941 { 2942 $this->login(); 2943 2944 if (!$this->queryCapability('QUOTA')) { 2945 throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA'); 2946 } 2947 2948 if (!empty($resources)) { 2949 $this->_setQuota(Horde_Imap_Client_Mailbox::get($root), $resources); 2950 } 2951 } 2952 2953 /** 2954 * Set quota limits. 2955 * 2956 * @param Horde_Imap_Client_Mailbox $root The quota root. 2957 * @param array $resources The resource values to set. 2958 * 2959 * @return boolean True on success. 2960 * 2961 * @throws Horde_Imap_Client_Exception 2962 */ 2963 abstract protected function _setQuota(Horde_Imap_Client_Mailbox $root, 2964 $resources); 2965 2966 /** 2967 * Get quota limits. The server must support the IMAP QUOTA extension 2968 * (RFC 2087). 2969 * 2970 * @param mixed $root The quota root. Either a Horde_Imap_Client_Mailbox 2971 * object or a string (UTF-8). 2972 * 2973 * @return mixed An array with resource keys. Each key holds an array 2974 * with 2 values: 'limit' and 'usage'. 2975 * 2976 * @throws Horde_Imap_Client_Exception 2977 * @throws Horde_Imap_Client_Exception_NoSupportExtension 2978 */ 2979 public function getQuota($root) 2980 { 2981 $this->login(); 2982 2983 if (!$this->queryCapability('QUOTA')) { 2984 throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA'); 2985 } 2986 2987 return $this->_getQuota(Horde_Imap_Client_Mailbox::get($root)); 2988 } 2989 2990 /** 2991 * Get quota limits. 2992 * 2993 * @param Horde_Imap_Client_Mailbox $root The quota root. 2994 * 2995 * @return mixed An array with resource keys. Each key holds an array 2996 * with 2 values: 'limit' and 'usage'. 2997 * 2998 * @throws Horde_Imap_Client_Exception 2999 */ 3000 abstract protected function _getQuota(Horde_Imap_Client_Mailbox $root); 3001 3002 /** 3003 * Get quota limits for a mailbox. The server must support the IMAP QUOTA 3004 * extension (RFC 2087). 3005 * 3006 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3007 * object or a string (UTF-8). 3008 * 3009 * @return mixed An array with the keys being the quota roots. Each key 3010 * holds an array with resource keys: each of these keys 3011 * holds an array with 2 values: 'limit' and 'usage'. 3012 * 3013 * @throws Horde_Imap_Client_Exception 3014 * @throws Horde_Imap_Client_Exception_NoSupportExtension 3015 */ 3016 public function getQuotaRoot($mailbox) 3017 { 3018 $this->login(); 3019 3020 if (!$this->queryCapability('QUOTA')) { 3021 throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA'); 3022 } 3023 3024 return $this->_getQuotaRoot(Horde_Imap_Client_Mailbox::get($mailbox)); 3025 } 3026 3027 /** 3028 * Get quota limits for a mailbox. 3029 * 3030 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3031 * 3032 * @return mixed An array with the keys being the quota roots. Each key 3033 * holds an array with resource keys: each of these keys 3034 * holds an array with 2 values: 'limit' and 'usage'. 3035 * 3036 * @throws Horde_Imap_Client_Exception 3037 */ 3038 abstract protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox); 3039 3040 /** 3041 * Get the ACL rights for a given mailbox. The server must support the 3042 * IMAP ACL extension (RFC 2086/4314). 3043 * 3044 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3045 * object or a string (UTF-8). 3046 * 3047 * @return array An array with identifiers as the keys and 3048 * Horde_Imap_Client_Data_Acl objects as the values. 3049 * 3050 * @throws Horde_Imap_Client_Exception 3051 */ 3052 public function getACL($mailbox) 3053 { 3054 $this->login(); 3055 return $this->_getACL(Horde_Imap_Client_Mailbox::get($mailbox)); 3056 } 3057 3058 /** 3059 * Get ACL rights for a given mailbox. 3060 * 3061 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3062 * 3063 * @return array An array with identifiers as the keys and 3064 * Horde_Imap_Client_Data_Acl objects as the values. 3065 * 3066 * @throws Horde_Imap_Client_Exception 3067 */ 3068 abstract protected function _getACL(Horde_Imap_Client_Mailbox $mailbox); 3069 3070 /** 3071 * Set ACL rights for a given mailbox/identifier. 3072 * 3073 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3074 * object or a string (UTF-8). 3075 * @param string $identifier The identifier to alter (UTF-8). 3076 * @param array $options Additional options: 3077 * - rights: (string) The rights to alter or set. 3078 * - action: (string, optional) If 'add' or 'remove', adds or removes the 3079 * specified rights. Sets the rights otherwise. 3080 * 3081 * @throws Horde_Imap_Client_Exception 3082 * @throws Horde_Imap_Client_Exception_NoSupportExtension 3083 */ 3084 public function setACL($mailbox, $identifier, $options) 3085 { 3086 $this->login(); 3087 3088 if (!$this->queryCapability('ACL')) { 3089 throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL'); 3090 } 3091 3092 if (empty($options['rights'])) { 3093 if (!isset($options['action']) || 3094 (($options['action'] != 'add') && 3095 $options['action'] != 'remove')) { 3096 $this->_deleteACL( 3097 Horde_Imap_Client_Mailbox::get($mailbox), 3098 Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier) 3099 ); 3100 } 3101 return; 3102 } 3103 3104 $acl = ($options['rights'] instanceof Horde_Imap_Client_Data_Acl) 3105 ? $options['rights'] 3106 : new Horde_Imap_Client_Data_Acl(strval($options['rights'])); 3107 3108 $options['rights'] = $acl->getString( 3109 $this->queryCapability('RIGHTS') 3110 ? Horde_Imap_Client_Data_AclCommon::RFC_4314 3111 : Horde_Imap_Client_Data_AclCommon::RFC_2086 3112 ); 3113 if (isset($options['action'])) { 3114 switch ($options['action']) { 3115 case 'add': 3116 $options['rights'] = '+' . $options['rights']; 3117 break; 3118 case 'remove': 3119 $options['rights'] = '-' . $options['rights']; 3120 break; 3121 } 3122 } 3123 3124 $this->_setACL( 3125 Horde_Imap_Client_Mailbox::get($mailbox), 3126 Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier), 3127 $options 3128 ); 3129 } 3130 3131 /** 3132 * Set ACL rights for a given mailbox/identifier. 3133 * 3134 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3135 * @param string $identifier The identifier to alter 3136 * (UTF7-IMAP). 3137 * @param array $options Additional options. 'rights' 3138 * contains the string of 3139 * rights to set on the server. 3140 * 3141 * @throws Horde_Imap_Client_Exception 3142 */ 3143 abstract protected function _setACL(Horde_Imap_Client_Mailbox $mailbox, 3144 $identifier, $options); 3145 3146 /** 3147 * Deletes ACL rights for a given mailbox/identifier. 3148 * 3149 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3150 * object or a string (UTF-8). 3151 * @param string $identifier The identifier to delete (UTF-8). 3152 * 3153 * @throws Horde_Imap_Client_Exception 3154 * @throws Horde_Imap_Client_Exception_NoSupportExtension 3155 */ 3156 public function deleteACL($mailbox, $identifier) 3157 { 3158 $this->login(); 3159 3160 if (!$this->queryCapability('ACL')) { 3161 throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL'); 3162 } 3163 3164 $this->_deleteACL( 3165 Horde_Imap_Client_Mailbox::get($mailbox), 3166 Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier) 3167 ); 3168 } 3169 3170 /** 3171 * Deletes ACL rights for a given mailbox/identifier. 3172 * 3173 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3174 * @param string $identifier The identifier to delete 3175 * (UTF7-IMAP). 3176 * 3177 * @throws Horde_Imap_Client_Exception 3178 */ 3179 abstract protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox, 3180 $identifier); 3181 3182 /** 3183 * List the ACL rights for a given mailbox/identifier. The server must 3184 * support the IMAP ACL extension (RFC 2086/4314). 3185 * 3186 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3187 * object or a string (UTF-8). 3188 * @param string $identifier The identifier to query (UTF-8). 3189 * 3190 * @return Horde_Imap_Client_Data_AclRights An ACL data rights object. 3191 * 3192 * @throws Horde_Imap_Client_Exception 3193 * @throws Horde_Imap_Client_Exception_NoSupportExtension 3194 */ 3195 public function listACLRights($mailbox, $identifier) 3196 { 3197 $this->login(); 3198 3199 if (!$this->queryCapability('ACL')) { 3200 throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL'); 3201 } 3202 3203 return $this->_listACLRights( 3204 Horde_Imap_Client_Mailbox::get($mailbox), 3205 Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier) 3206 ); 3207 } 3208 3209 /** 3210 * Get ACL rights for a given mailbox/identifier. 3211 * 3212 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3213 * @param string $identifier The identifier to query 3214 * (UTF7-IMAP). 3215 * 3216 * @return Horde_Imap_Client_Data_AclRights An ACL data rights object. 3217 * 3218 * @throws Horde_Imap_Client_Exception 3219 */ 3220 abstract protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox, 3221 $identifier); 3222 3223 /** 3224 * Get the ACL rights for the current user for a given mailbox. The 3225 * server must support the IMAP ACL extension (RFC 2086/4314). 3226 * 3227 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3228 * object or a string (UTF-8). 3229 * 3230 * @return Horde_Imap_Client_Data_Acl An ACL data object. 3231 * 3232 * @throws Horde_Imap_Client_Exception 3233 * @throws Horde_Imap_Client_Exception_NoSupportExtension 3234 */ 3235 public function getMyACLRights($mailbox) 3236 { 3237 $this->login(); 3238 3239 if (!$this->queryCapability('ACL')) { 3240 throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL'); 3241 } 3242 3243 return $this->_getMyACLRights(Horde_Imap_Client_Mailbox::get($mailbox)); 3244 } 3245 3246 /** 3247 * Get the ACL rights for the current user for a given mailbox. 3248 * 3249 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3250 * 3251 * @return Horde_Imap_Client_Data_Acl An ACL data object. 3252 * 3253 * @throws Horde_Imap_Client_Exception 3254 */ 3255 abstract protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox); 3256 3257 /** 3258 * Return master list of ACL rights available on the server. 3259 * 3260 * @return array A list of ACL rights. 3261 */ 3262 public function allAclRights() 3263 { 3264 $this->login(); 3265 3266 $rights = array( 3267 Horde_Imap_Client::ACL_LOOKUP, 3268 Horde_Imap_Client::ACL_READ, 3269 Horde_Imap_Client::ACL_SEEN, 3270 Horde_Imap_Client::ACL_WRITE, 3271 Horde_Imap_Client::ACL_INSERT, 3272 Horde_Imap_Client::ACL_POST, 3273 Horde_Imap_Client::ACL_ADMINISTER 3274 ); 3275 3276 if ($capability = $this->queryCapability('RIGHTS')) { 3277 // Add rights defined in CAPABILITY string (RFC 4314). 3278 return array_merge($rights, str_split(reset($capability))); 3279 } 3280 3281 // Add RFC 2086 rights (deprecated by RFC 4314, but need to keep for 3282 // compatibility with old servers). 3283 return array_merge($rights, array( 3284 Horde_Imap_Client::ACL_CREATE, 3285 Horde_Imap_Client::ACL_DELETE 3286 )); 3287 } 3288 3289 /** 3290 * Get metadata for a given mailbox. The server must support either the 3291 * IMAP METADATA extension (RFC 5464) or the ANNOTATEMORE extension 3292 * (http://ietfreport.isoc.org/idref/draft-daboo-imap-annotatemore/). 3293 * 3294 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3295 * object or a string (UTF-8). 3296 * @param array $entries The entries to fetch (UTF-8 strings). 3297 * @param array $options Additional options: 3298 * - depth: (string) Either "0", "1" or "infinity". Returns only the 3299 * given value (0), only values one level below the specified 3300 * value (1) or all entries below the specified value 3301 * (infinity). 3302 * - maxsize: (integer) The maximal size the returned values may have. 3303 * DEFAULT: No maximal size. 3304 * 3305 * @return array An array with metadata names as the keys and metadata 3306 * values as the values. If 'maxsize' is set, and entries 3307 * exist on the server larger than this size, the size will 3308 * be returned in the key '*longentries'. 3309 * 3310 * @throws Horde_Imap_Client_Exception 3311 */ 3312 public function getMetadata($mailbox, $entries, array $options = array()) 3313 { 3314 $this->login(); 3315 3316 if (!is_array($entries)) { 3317 $entries = array($entries); 3318 } 3319 3320 return $this->_getMetadata(Horde_Imap_Client_Mailbox::get($mailbox), array_map(array('Horde_Imap_Client_Utf7imap', 'Utf8ToUtf7Imap'), $entries), $options); 3321 } 3322 3323 /** 3324 * Get metadata for a given mailbox. 3325 * 3326 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3327 * @param array $entries The entries to fetch 3328 * (UTF7-IMAP strings). 3329 * @param array $options Additional options. 3330 * 3331 * @return array An array with metadata names as the keys and metadata 3332 * values as the values. 3333 * 3334 * @throws Horde_Imap_Client_Exception 3335 */ 3336 abstract protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox, 3337 $entries, $options); 3338 3339 /** 3340 * Set metadata for a given mailbox/identifier. 3341 * 3342 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3343 * object or a string (UTF-8). If empty, sets a 3344 * server annotation. 3345 * @param array $data A set of data values. The metadata values 3346 * corresponding to the keys of the array will 3347 * be set to the values in the array. 3348 * 3349 * @throws Horde_Imap_Client_Exception 3350 */ 3351 public function setMetadata($mailbox, $data) 3352 { 3353 $this->login(); 3354 $this->_setMetadata(Horde_Imap_Client_Mailbox::get($mailbox), $data); 3355 } 3356 3357 /** 3358 * Set metadata for a given mailbox/identifier. 3359 * 3360 * @param Horde_Imap_Client_Mailbox $mailbox A mailbox. 3361 * @param array $data A set of data values. See 3362 * setMetadata() for format. 3363 * 3364 * @throws Horde_Imap_Client_Exception 3365 */ 3366 abstract protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox, 3367 $data); 3368 3369 /* Public utility functions. */ 3370 3371 /** 3372 * Returns a unique identifier for the current mailbox status. 3373 * 3374 * @deprecated 3375 * 3376 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3377 * object or a string (UTF-8). 3378 * @param array $addl Additional cache info to add to the cache ID 3379 * string. 3380 * 3381 * @return string The cache ID string, which will change when the 3382 * composition of the mailbox changes. The uidvalidity 3383 * will always be the first element, and will be delimited 3384 * by the '|' character. 3385 * 3386 * @throws Horde_Imap_Client_Exception 3387 */ 3388 public function getCacheId($mailbox, array $addl = array()) 3389 { 3390 return Horde_Imap_Client_Base_Deprecated::getCacheId($this, $mailbox, isset($this->_temp['enabled']['CONDSTORE']), $addl); 3391 } 3392 3393 /** 3394 * Parses a cacheID created by getCacheId(). 3395 * 3396 * @deprecated 3397 * 3398 * @param string $id The cache ID. 3399 * 3400 * @return array An array with the following information: 3401 * - highestmodseq: (integer) 3402 * - messages: (integer) 3403 * - uidnext: (integer) 3404 * - uidvalidity: (integer) Always present 3405 */ 3406 public function parseCacheId($id) 3407 { 3408 return Horde_Imap_Client_Base_Deprecated::parseCacheId($id); 3409 } 3410 3411 /** 3412 * Resolves an IDs object into a list of IDs. 3413 * 3414 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox. 3415 * @param Horde_Imap_Client_Ids $ids The Ids object. 3416 * @param integer $convert Convert to UIDs? 3417 * - 0: No 3418 * - 1: Only if $ids is not already a UIDs object 3419 * - 2: Always 3420 * 3421 * @return Horde_Imap_Client_Ids The list of IDs. 3422 */ 3423 public function resolveIds(Horde_Imap_Client_Mailbox $mailbox, 3424 Horde_Imap_Client_Ids $ids, $convert = 0) 3425 { 3426 $map = $this->_mailboxOb($mailbox)->map; 3427 3428 if ($ids->special) { 3429 /* Optimization for ALL sequence searches. */ 3430 if (!$convert && $ids->all && $ids->sequence) { 3431 $res = $this->status($mailbox, Horde_Imap_Client::STATUS_MESSAGES); 3432 return $this->getIdsOb($res['messages'] ? ('1:' . $res['messages']) : array(), true); 3433 } 3434 3435 $convert = 2; 3436 } elseif (!$convert || 3437 (!$ids->sequence && ($convert == 1)) || 3438 $ids->isEmpty()) { 3439 return clone $ids; 3440 } else { 3441 /* Do an all or nothing: either we have all the numbers/UIDs in 3442 * memory and can return, or just send the whole ID query to the 3443 * server. Any advantage we would get by a partial search are 3444 * outweighed by the complexities needed to make the search and 3445 * then merge back into the original results. */ 3446 $lookup = $map->lookup($ids); 3447 if (count($lookup) === count($ids)) { 3448 return $this->getIdsOb(array_values($lookup)); 3449 } 3450 } 3451 3452 $query = new Horde_Imap_Client_Search_Query(); 3453 $query->ids($ids); 3454 3455 $res = $this->search($mailbox, $query, array( 3456 'results' => array( 3457 Horde_Imap_Client::SEARCH_RESULTS_MATCH, 3458 Horde_Imap_Client::SEARCH_RESULTS_SAVE 3459 ), 3460 'sequence' => (!$convert && $ids->sequence), 3461 'sort' => array(Horde_Imap_Client::SORT_SEQUENCE) 3462 )); 3463 3464 /* Update mapping. */ 3465 if ($convert) { 3466 if ($ids->all) { 3467 $ids = $this->getIdsOb('1:' . count($res['match'])); 3468 } elseif ($ids->special) { 3469 return $res['match']; 3470 } 3471 3472 /* Sanity checking (Bug #12911). */ 3473 $list1 = array_slice($ids->ids, 0, count($res['match'])); 3474 $list2 = $res['match']->ids; 3475 if (!empty($list1) && 3476 !empty($list2) && 3477 (count($list1) === count($list2))) { 3478 $map->update(array_combine($list1, $list2)); 3479 } 3480 } 3481 3482 return $res['match']; 3483 } 3484 3485 /** 3486 * Determines if the given charset is valid for search-related queries. 3487 * This check pertains just to the basic IMAP SEARCH command. 3488 * 3489 * @param string $charset The query charset. 3490 * 3491 * @return boolean True if server supports this charset. 3492 */ 3493 public function validSearchCharset($charset) 3494 { 3495 $charset = strtoupper($charset); 3496 3497 if ($charset == 'US-ASCII') { 3498 return true; 3499 } 3500 3501 if (!isset($this->_init['s_charset'][$charset])) { 3502 $s_charset = $this->_init['s_charset']; 3503 3504 /* Use a dummy search query and search for BADCHARSET response. */ 3505 $query = new Horde_Imap_Client_Search_Query(); 3506 $query->charset($charset, false); 3507 $query->ids($this->getIdsOb(1, true)); 3508 $query->text('a'); 3509 try { 3510 $this->search('INBOX', $query, array( 3511 'nocache' => true, 3512 'sequence' => true 3513 )); 3514 $s_charset[$charset] = true; 3515 } catch (Horde_Imap_Client_Exception $e) { 3516 $s_charset[$charset] = ($e->getCode() !== Horde_Imap_Client_Exception::BADCHARSET); 3517 } 3518 3519 $this->_setInit('s_charset', $s_charset); 3520 } 3521 3522 return $this->_init['s_charset'][$charset]; 3523 } 3524 3525 /* Mailbox syncing functions. */ 3526 3527 /** 3528 * Returns a unique token for the current mailbox synchronization status. 3529 * 3530 * @since 2.2.0 3531 * 3532 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3533 * object or a string (UTF-8). 3534 * 3535 * @return string The sync token. 3536 * 3537 * @throws Horde_Imap_Client_Exception 3538 */ 3539 public function getSyncToken($mailbox) 3540 { 3541 $out = array(); 3542 3543 foreach ($this->_syncStatus($mailbox) as $key => $val) { 3544 $out[] = $key . $val; 3545 } 3546 3547 return base64_encode(implode(',', $out)); 3548 } 3549 3550 /** 3551 * Synchronize a mailbox from a sync token. 3552 * 3553 * @since 2.2.0 3554 * 3555 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3556 * object or a string (UTF-8). 3557 * @param string $token A sync token generated by getSyncToken(). 3558 * @param array $opts Additional options: 3559 * - criteria: (integer) Mask of Horde_Imap_Client::SYNC_* criteria to 3560 * return. Defaults to SYNC_ALL. 3561 * - ids: (Horde_Imap_Client_Ids) A cached list of UIDs. Unless QRESYNC 3562 * is available on the server, failure to specify this option 3563 * means SYNC_VANISHEDUIDS information cannot be returned. 3564 * 3565 * @return Horde_Imap_Client_Data_Sync A sync object. 3566 * 3567 * @throws Horde_Imap_Client_Exception 3568 * @throws Horde_Imap_Client_Exception_Sync 3569 */ 3570 public function sync($mailbox, $token, array $opts = array()) 3571 { 3572 if (($token = base64_decode($token, true)) === false) { 3573 throw new Horde_Imap_Client_Exception_Sync('Bad token.', Horde_Imap_Client_Exception_Sync::BAD_TOKEN); 3574 } 3575 3576 $sync = array(); 3577 foreach (explode(',', $token) as $val) { 3578 $sync[substr($val, 0, 1)] = substr($val, 1); 3579 } 3580 3581 return new Horde_Imap_Client_Data_Sync( 3582 $this, 3583 $mailbox, 3584 $sync, 3585 $this->_syncStatus($mailbox), 3586 (isset($opts['criteria']) ? $opts['criteria'] : Horde_Imap_Client::SYNC_ALL), 3587 (isset($opts['ids']) ? $opts['ids'] : null) 3588 ); 3589 } 3590 3591 /* Private utility functions. */ 3592 3593 /** 3594 * Store FETCH data in cache. 3595 * 3596 * @param Horde_Imap_Client_Fetch_Results $data The fetch results. 3597 * 3598 * @throws Horde_Imap_Client_Exception 3599 */ 3600 protected function _updateCache(Horde_Imap_Client_Fetch_Results $data) 3601 { 3602 if (!empty($this->_temp['fetch_nocache']) || 3603 empty($this->_selected) || 3604 !count($data) || 3605 !$this->_initCache(true)) { 3606 return; 3607 } 3608 3609 $c = $this->getParam('cache'); 3610 if (in_array(strval($this->_selected), $c['fetch_ignore'])) { 3611 $this->_debug->info(sprintf( 3612 'CACHE: Ignoring FETCH data [%s]', 3613 $this->_selected 3614 )); 3615 return; 3616 } 3617 3618 /* Optimization: we can directly use getStatus() here since we know 3619 * these values are initialized. */ 3620 $mbox_ob = $this->_mailboxOb(); 3621 $highestmodseq = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ); 3622 $uidvalidity = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY); 3623 3624 $mapping = $modseq = $tocache = array(); 3625 if (count($data)) { 3626 $cf = $this->_cacheFields(); 3627 } 3628 3629 foreach ($data as $v) { 3630 /* It is possible that we received FETCH information that doesn't 3631 * contain UID data. This is uncacheable so don't process. */ 3632 if (!($uid = $v->getUid())) { 3633 return; 3634 } 3635 3636 $tmp = array(); 3637 3638 if ($v->isDowngraded()) { 3639 $tmp[self::CACHE_DOWNGRADED] = true; 3640 } 3641 3642 foreach ($cf as $key => $val) { 3643 if ($v->exists($key)) { 3644 switch ($key) { 3645 case Horde_Imap_Client::FETCH_ENVELOPE: 3646 $tmp[$val] = $v->getEnvelope(); 3647 break; 3648 3649 case Horde_Imap_Client::FETCH_FLAGS: 3650 if ($highestmodseq) { 3651 $modseq[$uid] = $v->getModSeq(); 3652 $tmp[$val] = $v->getFlags(); 3653 } 3654 break; 3655 3656 case Horde_Imap_Client::FETCH_HEADERS: 3657 foreach ($this->_temp['headers_caching'] as $label => $hash) { 3658 if ($hdr = $v->getHeaders($label)) { 3659 $tmp[$val][$hash] = $hdr; 3660 } 3661 } 3662 break; 3663 3664 case Horde_Imap_Client::FETCH_IMAPDATE: 3665 $tmp[$val] = $v->getImapDate(); 3666 break; 3667 3668 case Horde_Imap_Client::FETCH_SIZE: 3669 $tmp[$val] = $v->getSize(); 3670 break; 3671 3672 case Horde_Imap_Client::FETCH_STRUCTURE: 3673 $tmp[$val] = clone $v->getStructure(); 3674 break; 3675 } 3676 } 3677 } 3678 3679 if (!empty($tmp)) { 3680 $tocache[$uid] = $tmp; 3681 } 3682 3683 $mapping[$v->getSeq()] = $uid; 3684 } 3685 3686 if (!empty($mapping)) { 3687 if (!empty($tocache)) { 3688 $this->_cache->set($this->_selected, $tocache, $uidvalidity); 3689 } 3690 3691 $this->_mailboxOb()->map->update($mapping); 3692 } 3693 3694 if (!empty($modseq)) { 3695 $this->_updateModSeq(max(array_merge($modseq, array($highestmodseq)))); 3696 $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCFLAGUIDS, array_keys($modseq)); 3697 } 3698 } 3699 3700 /** 3701 * Moves cache entries from the current mailbox to another mailbox. 3702 * 3703 * @param Horde_Imap_Client_Mailbox $to The destination mailbox. 3704 * @param array $map Mapping of source UIDs (keys) to 3705 * destination UIDs (values). 3706 * @param string $uidvalid UIDVALIDITY of destination 3707 * mailbox. 3708 * 3709 * @throws Horde_Imap_Client_Exception 3710 */ 3711 protected function _moveCache(Horde_Imap_Client_Mailbox $to, $map, 3712 $uidvalid) 3713 { 3714 if (!$this->_initCache()) { 3715 return; 3716 } 3717 3718 $c = $this->getParam('cache'); 3719 if (in_array(strval($to), $c['fetch_ignore'])) { 3720 $this->_debug->info(sprintf( 3721 'CACHE: Ignoring moving FETCH data (%s => %s)', 3722 $this->_selected, 3723 $to 3724 )); 3725 return; 3726 } 3727 3728 $old = $this->_cache->get($this->_selected, array_keys($map), null); 3729 $new = array(); 3730 3731 foreach ($map as $key => $val) { 3732 if (!empty($old[$key])) { 3733 $new[$val] = $old[$key]; 3734 } 3735 } 3736 3737 if (!empty($new)) { 3738 $this->_cache->set($to, $new, $uidvalid); 3739 } 3740 } 3741 3742 /** 3743 * Delete messages in the cache. 3744 * 3745 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox. 3746 * @param Horde_Imap_Client_Ids $ids The list of IDs to delete in 3747 * $mailbox. 3748 * @param array $opts Additional options (not used 3749 * in base class). 3750 * 3751 * @return Horde_Imap_Client_Ids UIDs that were deleted. 3752 * @throws Horde_Imap_Client_Exception 3753 */ 3754 protected function _deleteMsgs(Horde_Imap_Client_Mailbox $mailbox, 3755 Horde_Imap_Client_Ids $ids, 3756 array $opts = array()) 3757 { 3758 if (!$this->_initCache()) { 3759 return $ids; 3760 } 3761 3762 $mbox_ob = $this->_mailboxOb(); 3763 $ids_ob = $ids->sequence 3764 ? $this->getIdsOb($mbox_ob->map->lookup($ids)) 3765 : $ids; 3766 3767 $this->_cache->deleteMsgs($mailbox, $ids_ob->ids); 3768 $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCVANISHED, $ids_ob->ids); 3769 $mbox_ob->map->remove($ids); 3770 3771 return $ids_ob; 3772 } 3773 3774 /** 3775 * Retrieve data from the search cache. 3776 * 3777 * @param string $type The cache type ('search' or 'thread'). 3778 * @param array $options The options array of the calling function. 3779 * 3780 * @return mixed Returns search cache metadata. If search was retrieved, 3781 * data is in key 'data'. 3782 * Returns null if caching is not available. 3783 */ 3784 protected function _getSearchCache($type, $options) 3785 { 3786 $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDVALIDITY); 3787 3788 /* Search caching requires MODSEQ, which may not be active for a 3789 * mailbox. */ 3790 if (empty($status['highestmodseq'])) { 3791 return null; 3792 } 3793 3794 ksort($options); 3795 $cache = hash( 3796 (PHP_MINOR_VERSION >= 4) ? 'fnv132' : 'sha1', 3797 $type . serialize($options) 3798 ); 3799 $cacheid = $this->getSyncToken($this->_selected); 3800 $ret = array(); 3801 3802 $md = $this->_cache->getMetaData( 3803 $this->_selected, 3804 $status['uidvalidity'], 3805 array(self::CACHE_SEARCH, self::CACHE_SEARCHID) 3806 ); 3807 3808 if (!isset($md[self::CACHE_SEARCHID]) || 3809 ($md[self::CACHE_SEARCHID] != $cacheid)) { 3810 $md[self::CACHE_SEARCH] = array(); 3811 $md[self::CACHE_SEARCHID] = $cacheid; 3812 if ($this->_debug->debug && 3813 !isset($this->_temp['searchcacheexpire'][strval($this->_selected)])) { 3814 $this->_debug->info(sprintf( 3815 'SEARCH: Expired from cache [%s]', 3816 $this->_selected 3817 )); 3818 $this->_temp['searchcacheexpire'][strval($this->_selected)] = true; 3819 } 3820 } elseif (isset($md[self::CACHE_SEARCH][$cache])) { 3821 $this->_debug->info(sprintf( 3822 'SEARCH: Retrieved %s from cache (%s [%s])', 3823 $type, 3824 $cache, 3825 $this->_selected 3826 )); 3827 $ret['data'] = $md[self::CACHE_SEARCH][$cache]; 3828 unset($md[self::CACHE_SEARCHID]); 3829 } 3830 3831 return array_merge($ret, array( 3832 'id' => $cache, 3833 'metadata' => $md, 3834 'type' => $type 3835 )); 3836 } 3837 3838 /** 3839 * Set data in the search cache. 3840 * 3841 * @param mixed $data The cache data to store. 3842 * @param string $sdata The search data returned from _getSearchCache(). 3843 */ 3844 protected function _setSearchCache($data, $sdata) 3845 { 3846 $sdata['metadata'][self::CACHE_SEARCH][$sdata['id']] = $data; 3847 3848 $this->_cache->setMetaData($this->_selected, null, $sdata['metadata']); 3849 3850 if ($this->_debug->debug) { 3851 $this->_debug->info(sprintf( 3852 'SEARCH: Saved %s to cache (%s [%s])', 3853 $sdata['type'], 3854 $sdata['id'], 3855 $this->_selected 3856 )); 3857 unset($this->_temp['searchcacheexpire'][strval($this->_selected)]); 3858 } 3859 } 3860 3861 /** 3862 * Updates the cached MODSEQ value. 3863 * 3864 * @param integer $modseq MODSEQ value to store. 3865 * 3866 * @return mixed The MODSEQ of the old value if it was replaced (or false 3867 * if it didn't exist or is the same). 3868 */ 3869 protected function _updateModSeq($modseq) 3870 { 3871 if (!$this->_initCache(true)) { 3872 return false; 3873 } 3874 3875 $mbox_ob = $this->_mailboxOb(); 3876 $uidvalid = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY); 3877 $md = $this->_cache->getMetaData($this->_selected, $uidvalid, array(self::CACHE_MODSEQ)); 3878 3879 if (isset($md[self::CACHE_MODSEQ])) { 3880 if ($md[self::CACHE_MODSEQ] < $modseq) { 3881 $set = true; 3882 $sync = $md[self::CACHE_MODSEQ]; 3883 } else { 3884 $set = false; 3885 $sync = 0; 3886 } 3887 $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCMODSEQ, $md[self::CACHE_MODSEQ]); 3888 } else { 3889 $set = true; 3890 $sync = 0; 3891 } 3892 3893 if ($set) { 3894 $this->_cache->setMetaData($this->_selected, $uidvalid, array( 3895 self::CACHE_MODSEQ => $modseq 3896 )); 3897 } 3898 3899 return $sync; 3900 } 3901 3902 /** 3903 * Synchronizes the current mailbox cache with the server (using CONDSTORE 3904 * or QRESYNC). 3905 */ 3906 protected function _condstoreSync() 3907 { 3908 $mbox_ob = $this->_mailboxOb(); 3909 3910 /* Check that modseqs are available in mailbox. */ 3911 if (!($highestmodseq = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) || 3912 !($modseq = $this->_updateModSeq($highestmodseq))) { 3913 $mbox_ob->sync = true; 3914 } 3915 3916 if ($mbox_ob->sync) { 3917 return; 3918 } 3919 3920 $uids_ob = $this->getIdsOb($this->_cache->get($this->_selected, array(), array(), $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY))); 3921 3922 /* Are we caching flags? */ 3923 if (array_key_exists(Horde_Imap_Client::FETCH_FLAGS, $this->_cacheFields())) { 3924 $fquery = new Horde_Imap_Client_Fetch_Query(); 3925 $fquery->flags(); 3926 3927 /* Update flags in cache. Cache will be updated in _fetch(). */ 3928 $this->_fetch(new Horde_Imap_Client_Fetch_Results(), array( 3929 array( 3930 '_query' => $fquery, 3931 'changedsince' => $modseq, 3932 'ids' => $uids_ob 3933 ) 3934 )); 3935 } 3936 3937 /* Search for deleted messages, and remove from cache. */ 3938 $vanished = $this->vanished($this->_selected, $modseq, array( 3939 'ids' => $uids_ob 3940 )); 3941 $disappear = array_diff($uids_ob->ids, $vanished->ids); 3942 if (!empty($disappear)) { 3943 $this->_deleteMsgs($this->_selected, $this->getIdsOb($disappear)); 3944 } 3945 3946 $mbox_ob->sync = true; 3947 } 3948 3949 /** 3950 * Provide the list of available caching fields. 3951 * 3952 * @return array The list of available caching fields (fields are in the 3953 * key). 3954 */ 3955 protected function _cacheFields() 3956 { 3957 $c = $this->getParam('cache'); 3958 $out = $c['fields']; 3959 3960 if (!isset($this->_temp['enabled']['CONDSTORE'])) { 3961 unset($out[Horde_Imap_Client::FETCH_FLAGS]); 3962 } 3963 3964 return $out; 3965 } 3966 3967 /** 3968 * Return the current mailbox synchronization status. 3969 * 3970 * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox 3971 * object or a string (UTF-8). 3972 * 3973 * @return array An array with status data. (This data is not guaranteed 3974 * to have any specific format). 3975 */ 3976 protected function _syncStatus($mailbox) 3977 { 3978 $status = $this->status( 3979 $mailbox, 3980 Horde_Imap_Client::STATUS_HIGHESTMODSEQ | 3981 Horde_Imap_Client::STATUS_MESSAGES | 3982 Horde_Imap_Client::STATUS_UIDNEXT_FORCE | 3983 Horde_Imap_Client::STATUS_UIDVALIDITY 3984 ); 3985 3986 $fields = array('uidnext', 'uidvalidity'); 3987 if (empty($status['highestmodseq'])) { 3988 $fields[] = 'messages'; 3989 } else { 3990 $fields[] = 'highestmodseq'; 3991 } 3992 3993 $out = array(); 3994 $sync_map = array_flip(Horde_Imap_Client_Data_Sync::$map); 3995 3996 foreach ($fields as $val) { 3997 $out[$sync_map[$val]] = $status[$val]; 3998 } 3999 4000 return array_filter($out); 4001 } 4002 4003 /** 4004 * Get a message UID by the Message-ID. Returns the last message in a 4005 * mailbox that matches. 4006 * 4007 * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to search 4008 * @param string $msgid Message-ID. 4009 * 4010 * @return string UID (null if not found). 4011 */ 4012 protected function _getUidByMessageId($mailbox, $msgid) 4013 { 4014 if (!$msgid) { 4015 return null; 4016 } 4017 4018 $query = new Horde_Imap_Client_Search_Query(); 4019 $query->headerText('Message-ID', $msgid); 4020 $res = $this->search($mailbox, $query, array( 4021 'results' => array(Horde_Imap_Client::SEARCH_RESULTS_MAX) 4022 )); 4023 4024 return $res['max']; 4025 } 4026 4027 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |