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