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