1<?php 2/** 3 * Copyright 2008-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (GPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/gpl. 7 * 8 * @category Horde 9 * @copyright 2008-2017 Horde LLC 10 * @license http://www.horde.org/licenses/gpl GPL 11 * @package IMP 12 */ 13 14/** 15 * Provides common functions for interaction with IMAP/POP3 servers via the 16 * Horde_Imap_Client package. 17 * 18 * @author Michael Slusarz <slusarz@horde.org> 19 * @category Horde 20 * @copyright 2008-2017 Horde LLC 21 * @license http://www.horde.org/licenses/gpl GPL 22 * @package IMP 23 * 24 * @property-read boolean $changed If true, this object has changed. 25 * @property-read Horde_Imap_Client_Base $client_ob The IMAP client object. 26 * @property-read IMP_Imap_Config $config Base backend config settings. 27 * @property-read boolean $init Has the IMAP object been initialized? 28 * @property-read integer $max_compose_bodysize The maximum size (in bytes) 29 * of the compose message body. 30 * @property-read integer $max_compose_recipients The maximum number of 31 * recipients to send to per 32 * compose message. 33 * @property-read integer $max_compose_timelimit The maximum number of 34 * recipients to send to in the 35 * configured timelimit. 36 * @property-read integer $max_create_mboxes The maximum number of mailboxes 37 * a user can create. 38 * @property-read string $server_key Server key used to login. 39 * @property-read string $thread_algo The threading algorithm to use. 40 * @property-read Horde_Imap_Client_Url $url A URL object. 41 */ 42class IMP_Imap implements Serializable 43{ 44 /* Access constants. */ 45 const ACCESS_FOLDERS = 1; 46 const ACCESS_SEARCH = 2; 47 const ACCESS_FLAGS = 3; 48 const ACCESS_UNSEEN = 4; 49 const ACCESS_TRASH = 5; 50 const ACCESS_CREATEMBOX = 6; 51 const ACCESS_CREATEMBOX_MAX = 7; 52 const ACCESS_COMPOSE_BODYSIZE = 13; 53 const ACCESS_COMPOSE_RECIPIENTS = 8; 54 const ACCESS_COMPOSE_TIMELIMIT = 9; 55 const ACCESS_ACL = 10; 56 const ACCESS_DRAFTS = 11; 57 const ACCESS_REMOTE = 12; 58 const ACCESS_IMPORT = 14; 59 const ACCESS_SORT = 15; 60 61 /* Default namespace. */ 62 const NS_DEFAULT = "\0default"; 63 64 /** 65 * Cached backend configuration. 66 * 67 * @var array 68 */ 69 static protected $_backends = array(); 70 71 /** 72 * Has this object changed? 73 * 74 * @var boolean 75 */ 76 protected $_changed = false; 77 78 /** 79 * Backend config. 80 * 81 * @var IMP_Imap_Config 82 */ 83 protected $_config; 84 85 /** 86 * Object identifier. 87 * 88 * @var string 89 */ 90 protected $_id; 91 92 /** 93 * The IMAP client object. 94 * 95 * @var Horde_Imap_Client_Base 96 */ 97 protected $_ob; 98 99 /** 100 * Temporary data cache (destroyed at end of request). 101 * 102 * @var array 103 */ 104 protected $_temp = array(); 105 106 /** 107 * Constructor. 108 * 109 * @param string $id Object identifier. 110 */ 111 public function __construct($id) 112 { 113 $this->_id = strval($id); 114 } 115 116 /** 117 */ 118 public function __get($key) 119 { 120 switch ($key) { 121 case 'changed': 122 return $this->_changed; 123 124 case 'client_ob': 125 return $this->init 126 ? $this->_ob 127 : null; 128 129 case 'config': 130 return isset($this->_config) 131 ? $this->_config 132 : new Horde_Support_Stub(); 133 134 case 'init': 135 return isset($this->_ob); 136 137 case 'max_compose_bodysize': 138 case 'max_compose_recipients': 139 case 'max_compose_timelimit': 140 $perm = $GLOBALS['injector']->getInstance('Horde_Perms')->getPermissions('imp:' . str_replace('max_compose', 'max', $key), $GLOBALS['registry']->getAuth()); 141 return intval($perm[0]); 142 143 case 'max_create_mboxes': 144 $perm = $GLOBALS['injector']->getInstance('Horde_Perms')->getPermissions('imp:' . $this->_getPerm($key), $GLOBALS['registry']->getAuth()); 145 return intval($perm[0]); 146 147 case 'server_key': 148 return $this->init 149 ? $this->_ob->getParam('imp:backend') 150 : null; 151 152 case 'thread_algo': 153 if (!$this->init) { 154 return 'ORDEREDSUBJECT'; 155 } 156 157 if ($thread = $this->_ob->getParam('imp:thread_algo')) { 158 return $thread; 159 } 160 161 $thread = $this->config->thread; 162 $thread_cap = $this->queryCapability('THREAD'); 163 if (!in_array($thread, is_array($thread_cap) ? $thread_cap : array())) { 164 $thread = 'ORDEREDSUBJECT'; 165 } 166 167 $this->_ob->setParam('imp:thread_algo', $thread); 168 $this->_changed = true; 169 170 return $thread; 171 172 case 'url': 173 $url = new Horde_Imap_Client_Url(); 174 if ($this->init) { 175 $url->hostspec = $this->getParam('hostspec'); 176 $url->port = $this->getParam('port'); 177 $url->protocol = $this->isImap() ? 'imap' : 'pop'; 178 } 179 return $url; 180 } 181 } 182 183 /** 184 */ 185 public function __toString() 186 { 187 return $this->_id; 188 } 189 190 /** 191 * Get the full permission name for a permission. 192 * 193 * @param string $perm The permission. 194 * 195 * @return string The full (backend-specific) permission name. 196 */ 197 protected function _getPerm($perm) 198 { 199 return 'backends:' . ($this->init ? $this->server_key . ':' : '') . $perm; 200 } 201 202 /** 203 * Determine if this is a connection to an IMAP server. 204 * 205 * @return boolean True if connected to IMAP server. 206 */ 207 public function isImap() 208 { 209 return ($this->init && 210 ($this->_ob instanceof Horde_Imap_Client_Socket)); 211 } 212 213 /** 214 * Determine if this is a connection to an IMAP server. 215 * 216 * @return boolean True if connected to IMAP server. 217 */ 218 public function isPop3() 219 { 220 return ($this->init && 221 ($this->_ob instanceof Horde_Imap_Client_Socket_Pop3)); 222 } 223 224 /** 225 * Create the base Horde_Imap_Client object (from an entry in 226 * backends.php). 227 * 228 * @param string $username The username to authenticate with. 229 * @param string $password The password to authenticate with. 230 * @param string $skey Create a new object using this server key. 231 * 232 * @return Horde_Imap_Client_Base Client object. 233 * @throws IMP_Imap_Exception 234 */ 235 public function createBaseImapObject($username, $password, $skey) 236 { 237 if ($this->init) { 238 return $this->client_ob; 239 } 240 241 if (($config = $this->loadServerConfig($skey)) === false) { 242 $error = new IMP_Imap_Exception('Could not load server configuration.'); 243 Horde::log($error); 244 throw $error; 245 } 246 247 $imap_config = array( 248 'hostspec' => $config->hostspec, 249 'id' => $config->id, 250 'password' => new IMP_Imap_Password($password), 251 'port' => $config->port, 252 'secure' => (($secure = $config->secure) ? $secure : false), 253 'username' => $username, 254 // IMP specific config 255 'imp:backend' => $skey 256 ); 257 258 /* Needed here to set config information in createImapObject(). */ 259 $this->_config = $config; 260 261 try { 262 return $this->createImapObject($imap_config, ($config->protocol == 'imap')); 263 } catch (IMP_Imap_Exception $e) { 264 unset($this->_config); 265 throw $e; 266 } 267 } 268 269 /** 270 * Create a Horde_Imap_Client object. 271 * 272 * @param array $config The IMAP configuration. 273 * @param boolean $imap True if IMAP connection, false if POP3. 274 * 275 * @return Horde_Imap_Client_Base Client object. 276 * @throws IMP_Imap_Exception 277 */ 278 public function createImapObject($config, $imap = true) 279 { 280 if ($this->init) { 281 return $this->_ob; 282 } 283 284 $sconfig = $this->config; 285 $config = array_merge(array( 286 'cache' => $sconfig->cache_params, 287 'capability_ignore' => $sconfig->capability_ignore, 288 'comparator' => $sconfig->comparator, 289 'debug' => $sconfig->debug, 290 'debug_literal' => $sconfig->debug_raw, 291 'lang' => $sconfig->lang, 292 'timeout' => $sconfig->timeout, 293 // 'imp:login' - Set in __call() 294 ), $config); 295 296 try { 297 $this->_ob = $imap 298 ? new Horde_Imap_Client_Socket($config) 299 : new Horde_Imap_Client_Socket_Pop3($config); 300 return $this->_ob; 301 } catch (Horde_Imap_Client_Exception $e) { 302 Horde::log($e->raw_msg); 303 throw new IMP_Imap_Exception($e); 304 } 305 } 306 307 /** 308 * Perform post-login tasks. 309 */ 310 public function doPostLoginTasks() 311 { 312 global $prefs; 313 314 switch ($this->_config->protocol) { 315 case 'imap': 316 /* Overwrite default special mailbox names. */ 317 foreach ($this->_config->special_mboxes as $key => $val) { 318 if ($key != IMP_Mailbox::MBOX_USERSPECIAL) { 319 $prefs->setValue($key, $val, array( 320 'force' => true, 321 'nosave' => true 322 )); 323 } 324 } 325 break; 326 327 case 'pop': 328 /* Turn some options off if we are working with POP3. */ 329 foreach (array('newmail_notify', 'save_sent_mail') as $val) { 330 $prefs->setValue($val, false, array( 331 'force' => true, 332 'nosave' => true 333 )); 334 $prefs->setLocked($val, true); 335 } 336 $prefs->setLocked(IMP_Mailbox::MBOX_DRAFTS, true); 337 $prefs->setLocked(IMP_Mailbox::MBOX_SENT, true); 338 $prefs->setLocked(IMP_Mailbox::MBOX_SPAM, true); 339 $prefs->setLocked(IMP_Mailbox::MBOX_TEMPLATES, true); 340 $prefs->setLocked(IMP_Mailbox::MBOX_TRASH, true); 341 break; 342 } 343 344 $this->updateFetchIgnore(); 345 } 346 347 /** 348 * Update the list of mailboxes to ignore when caching FETCH data in the 349 * IMAP client object. 350 */ 351 public function updateFetchIgnore() 352 { 353 if ($this->isImap()) { 354 $special = IMP_Mailbox::getSpecialMailboxes(); 355 $cache = $this->_ob->getParam('cache'); 356 $cache['fetch_ignore'] = array_filter(array( 357 strval($special[IMP_Mailbox::SPECIAL_SPAM]), 358 strval($special[IMP_Mailbox::SPECIAL_TRASH]) 359 )); 360 $this->_ob->setParam('cache', $cache); 361 } 362 } 363 364 /** 365 * Checks access rights for a server. 366 * 367 * @param integer $right Access right. 368 * 369 * @return boolean Does the access right exist? 370 */ 371 public function access($right) 372 { 373 global $injector; 374 375 if (!$this->init) { 376 return false; 377 } 378 379 switch ($right) { 380 case self::ACCESS_ACL: 381 return ($this->config->acl && $this->queryCapability('ACL')); 382 383 case self::ACCESS_CREATEMBOX: 384 return ($this->isImap() && 385 $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('create_mboxes'))); 386 387 case self::ACCESS_CREATEMBOX_MAX: 388 return ($this->isImap() && 389 $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('max_create_mboxes'))); 390 391 case self::ACCESS_DRAFTS: 392 case self::ACCESS_FLAGS: 393 case self::ACCESS_IMPORT: 394 case self::ACCESS_SEARCH: 395 case self::ACCESS_UNSEEN: 396 return $this->isImap(); 397 398 case self::ACCESS_FOLDERS: 399 case self::ACCESS_TRASH: 400 return ($this->isImap() && 401 $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('allow_folders'))); 402 403 case self::ACCESS_REMOTE: 404 return $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('allow_remote')); 405 406 case self::ACCESS_SORT: 407 return ($this->isImap() && 408 ($this->config->sort_force || $this->_ob->queryCapability('SORT'))); 409 } 410 411 return false; 412 } 413 414 /** 415 * Checks compose access rights for a server. 416 * 417 * @param integer $right Access right. 418 * @param integer $data Data required to check the rights: 419 * <pre> 420 * - ACCESS_COMPOSE_BODYSIZE 421 * The size of the body data. 422 * 423 * - ACCESS_COMPOSE_RECIPIENTS 424 * - ACCESS_COMPOSE_TIMELIMIT 425 * The number of e-mail recipients. 426 * </pre> 427 * 428 * @return boolean Is the access allowed? 429 */ 430 public function accessCompose($right, $data) 431 { 432 switch ($right) { 433 case self::ACCESS_COMPOSE_BODYSIZE: 434 $perm_name = 'max_bodysize'; 435 break; 436 437 case self::ACCESS_COMPOSE_RECIPIENTS: 438 $perm_name = 'max_recipients'; 439 break; 440 441 case self::ACCESS_COMPOSE_TIMELIMIT: 442 $perm_name = 'max_timelimit'; 443 break; 444 445 default: 446 return false; 447 } 448 449 return $GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission( 450 $perm_name, 451 array( 452 'opts' => array( 453 'value' => $data 454 ) 455 ) 456 ); 457 } 458 459 /** 460 * Get namespace info for a full mailbox path. 461 * 462 * @param string $mailbox The mailbox path. (self:NS_DEFAULT will 463 * return the default personal namespace.) 464 * @param boolean $personal If true, will return empty namespace only 465 * if it is a personal namespace. 466 * 467 * @return mixed The namespace info for the mailbox path or null if the 468 * path doesn't exist. 469 */ 470 public function getNamespace($mailbox, $personal = false) 471 { 472 if ($this->isImap()) { 473 $ns = $this->getNamespaces(); 474 if ($mailbox !== self::NS_DEFAULT) { 475 return $ns->getNamespace($mailbox, $personal); 476 } 477 478 foreach ($ns as $val) { 479 if ($val->type === $val::NS_PERSONAL) { 480 return $val; 481 } 482 } 483 } 484 485 return null; 486 } 487 488 /** 489 * Return the cache ID for this mailbox. 490 * 491 * @param string $mailbox The mailbox name (UTF-8). 492 * @param array $addl Local IMP metadata to add to the cache ID. 493 * 494 * @return string The cache ID. 495 */ 496 public function getCacheId($mailbox, array $addl = array()) 497 { 498 return $this->getSyncToken($mailbox) . 499 (empty($addl) ? '' : ('|' . implode('|', $addl))); 500 } 501 502 /** 503 * Parses the cache ID for this mailbox. 504 * 505 * @param string $id Cache ID generated by getCacheId(). 506 * 507 * @return array Two element array: 508 * - date: (integer) Date information (day of year), if embedded in 509 * cache ID. 510 * - token: (string) Mailbox sync token. 511 */ 512 public function parseCacheId($id) 513 { 514 $out = array('date' => null); 515 516 if ((($pos = strrpos($id, '|')) !== false) && 517 (substr($id, $pos + 1, 1) == 'D')) { 518 $out['date'] = substr($id, $pos + 2); 519 } 520 521 $out['token'] = (($pos = strpos($id, '|')) === false) 522 ? $id 523 : substr($id, 0, $pos); 524 525 return $out; 526 } 527 528 /** 529 * Returns a list of messages, split into slices based on the total 530 * message size. 531 * 532 * @param string $mbox IMAP mailbox. 533 * @param Horde_Imap_Client_Ids $ids ID list. 534 * @param integer $size Maximum size of a slice. 535 * 536 * @return array An array of Horde_Imap_Client_Ids objects. 537 */ 538 public function getSlices( 539 $mbox, Horde_Imap_Client_Ids $ids, $size = 5242880 540 ) 541 { 542 $imp_imap = IMP_Mailbox::get($mbox)->imp_imap; 543 544 $query = new Horde_Imap_Client_Fetch_Query(); 545 $query->size(); 546 547 try { 548 $res = $imp_imap->fetch($mbox, $query, array( 549 'ids' => $ids, 550 'nocache' => true 551 )); 552 } catch (IMP_Imap_Exception $e) { 553 return array(); 554 } 555 556 $curr = $slices = array(); 557 $curr_size = 0; 558 559 foreach ($res as $key => $val) { 560 $curr_size += $val->getSize(); 561 if ($curr_size > $size) { 562 $slices[] = $imp_imap->getIdsOb($curr, $ids->sequence); 563 $curr = array(); 564 } 565 $curr[] = $key; 566 } 567 568 $slices[] = $imp_imap->getIdsOb($curr, $ids->sequence); 569 570 return $slices; 571 } 572 573 /** 574 * Handle status() calls. This call may hit multiple servers. 575 * 576 * @see Horde_Imap_Client_Base#status() 577 */ 578 protected function _status($args) 579 { 580 global $injector; 581 582 $accounts = $mboxes = $out = array(); 583 $imap_factory = $injector->getInstance('IMP_Factory_Imap'); 584 585 foreach (IMP_Mailbox::get($args[0]) as $val) { 586 if ($raccount = $val->remote_account) { 587 $accounts[strval($raccount)] = $raccount; 588 } 589 $mboxes[strval($raccount)][] = $val; 590 } 591 592 foreach ($mboxes as $key => $val) { 593 $imap = $imap_factory->create($key); 594 if ($imap->init) { 595 foreach (call_user_func_array(array($imap, 'impStatus'), array($val) + $args) as $key2 => $val2) { 596 $out[isset($accounts[$key]) ? $accounts[$key]->mailbox($key2) : $key2] = $val2; 597 } 598 } 599 } 600 601 return $out; 602 } 603 604 /** 605 * All other calls to this class are routed to the underlying 606 * Horde_Imap_Client_Base object. 607 * 608 * @param string $method Method name. 609 * @param array $params Method parameters. 610 * 611 * @return mixed The return from the requested method. 612 * @throws BadMethodCallException 613 * @throws IMP_Imap_Exception 614 */ 615 public function __call($method, $params) 616 { 617 global $injector; 618 619 if (!$this->init) { 620 /* Fallback for these methods. */ 621 switch ($method) { 622 case 'getIdsOb': 623 $ob = new Horde_Imap_Client_Ids(); 624 call_user_func_array(array($ob, 'add'), $params); 625 return $ob; 626 } 627 628 throw new Horde_Exception_AuthenticationFailure( 629 'IMP is marked as authenticated, but no credentials can be found in the session.', 630 Horde_Auth::REASON_SESSION 631 ); 632 } 633 634 switch ($method) { 635 case 'append': 636 case 'createMailbox': 637 case 'deleteMailbox': 638 case 'expunge': 639 case 'fetch': 640 case 'getACL': 641 case 'getMetadata': 642 case 'getMyACLRights': 643 case 'getQuota': 644 case 'getQuotaRoot': 645 case 'getSyncToken': 646 case 'setMetadata': 647 case 'setQuota': 648 case 'store': 649 case 'subscribeMailbox': 650 case 'sync': 651 case 'thread': 652 // Horde_Imap_Client_Mailbox: these calls all have the mailbox as 653 // their first parameter. 654 $params[0] = IMP_Mailbox::getImapMboxOb($params[0]); 655 break; 656 657 case 'copy': 658 case 'renameMailbox': 659 // These calls may hit multiple servers. 660 $source = IMP_Mailbox::get($params[0]); 661 $dest = IMP_Mailbox::get($params[1]); 662 if ($source->remote_account != $dest->remote_account) { 663 return call_user_func_array(array($this, '_' . $method), $params); 664 } 665 666 // Horde_Imap_Client_Mailbox: these calls all have the mailbox as 667 // their first two parameters. 668 $params[0] = $source->imap_mbox_ob; 669 $params[1] = $dest->imap_mbox_ob; 670 break; 671 672 case 'getNamespaces': 673 if (isset($this->_temp['ns'])) { 674 return $this->_temp['ns']; 675 } 676 $nsconfig = $this->config->namespace; 677 $params[0] = is_null($nsconfig) ? array() : $nsconfig; 678 $params[1] = array('ob_return' => true); 679 break; 680 681 case 'impStatus': 682 /* Internal method: allows status call with array of mailboxes, 683 * guaranteeing they are all on this server. */ 684 $params[0] = IMP_Mailbox::getImapMboxOb($params[0]); 685 $method = 'status'; 686 break; 687 688 case 'openMailbox': 689 $mbox = IMP_Mailbox::get($params[0]); 690 if ($mbox->search) { 691 /* Can't open a search mailbox. */ 692 return; 693 } 694 $params[0] = $mbox->imap_mbox_ob; 695 break; 696 697 case 'search': 698 $params = call_user_func_array(array($this, '_search'), $params); 699 break; 700 701 case 'status': 702 if (is_array($params[0])) { 703 return $this->_status($params); 704 } 705 $params[0] = IMP_Mailbox::getImapMboxOb($params[0]); 706 break; 707 708 default: 709 if (!method_exists($this->_ob, $method)) { 710 throw new BadMethodCallException( 711 sprintf('%s: Invalid method call "%s".', __CLASS__, $method) 712 ); 713 } 714 break; 715 } 716 717 try { 718 $result = call_user_func_array(array($this->_ob, $method), $params); 719 } catch (Horde_Imap_Client_Exception $e) { 720 $error = new IMP_Imap_Exception($e); 721 722 if (!$error->authError()) { 723 switch ($method) { 724 case 'getNamespaces': 725 return new Horde_Imap_Client_Namespace_List(); 726 } 727 } 728 729 Horde::log( 730 new Exception( 731 sprintf('[%s] %s', $method, $e->raw_msg), 732 $e->getCode(), 733 $e 734 ), 735 'WARN' 736 ); 737 738 throw $error; 739 } 740 741 /* Special handling for various methods. */ 742 switch ($method) { 743 case 'createMailbox': 744 case 'deleteMailbox': 745 case 'renameMailbox': 746 $injector->getInstance('IMP_Mailbox_SessionCache')->expire( 747 null, 748 // Mailbox is first parameter. 749 IMP_Mailbox::get($params[0]) 750 ); 751 break; 752 753 case 'getNamespaces': 754 $this->_temp['ns'] = $result; 755 break; 756 757 case 'login': 758 if (!$this->_ob->getParam('imp:login')) { 759 /* Check for POP3 UIDL support. */ 760 if ($this->isPop3() && !$this->queryCapability('UIDL')) { 761 Horde::log( 762 sprintf( 763 'The POP3 server does not support the REQUIRED UIDL capability. [server key: %s]', 764 $this->server_key 765 ), 766 'CRIT' 767 ); 768 throw new Horde_Exception_AuthenticationFailure( 769 _("The mail server is not currently avaliable."), 770 Horde_Auth::REASON_MESSAGE 771 ); 772 } 773 774 $this->_ob->setParam('imp:login', true); 775 $this->_changed = true; 776 } 777 break; 778 779 case 'setACL': 780 $injector->getInstance('IMP_Mailbox_SessionCache')->expire( 781 IMP_Mailbox_SessionCache::CACHE_ACL, 782 IMP_Mailbox::get($params[0]) 783 ); 784 break; 785 } 786 787 return $result; 788 } 789 790 /** 791 * Prepares an IMAP search query. Needed because certain configuration 792 * parameters may need to be dynamically altered before passed to the 793 * Imap_Client object. 794 * 795 * @param string $mailbox The mailbox to search. 796 * @param Horde_Imap_Client_Search_Query $query The search query object. 797 * @param array $opts Additional options. 798 * 799 * @return array Parameters to use in the search() call. 800 */ 801 protected function _search($mailbox, $query = null, array $opts = array()) 802 { 803 $mailbox = IMP_Mailbox::get($mailbox); 804 805 if (!empty($opts['sort']) && $mailbox->access_sort) { 806 /* If doing a from/to search, use display sorting if possible. 807 * Although there is a fallback to a PHP-based display sort, for 808 * performance reasons only do a display sort if it is supported 809 * on the server. */ 810 foreach ($opts['sort'] as $key => $val) { 811 switch ($val) { 812 case Horde_Imap_Client::SORT_FROM: 813 $opts['sort'][$key] = Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK; 814 break; 815 816 case Horde_Imap_Client::SORT_TO: 817 $opts['sort'][$key] = Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK; 818 break; 819 } 820 } 821 } 822 823 if (!is_null($query)) { 824 $query->charset('UTF-8', false); 825 } 826 827 return array($mailbox->imap_mbox_ob, $query, $opts); 828 } 829 830 /** 831 * Handle copy() calls that hit multiple servers. 832 * 833 * @see Horde_Imap_Client_Base#copy() 834 */ 835 protected function _copy() 836 { 837 global $injector; 838 839 $args = func_get_args(); 840 $imap_factory = $injector->getInstance('IMP_Factory_Imap'); 841 $source_imap = $imap_factory->create($args[0]); 842 $dest_imap = $imap_factory->create($args[1]); 843 844 $create = !empty($args[2]['create']); 845 $ids = isset($args[2]['ids']) 846 ? $args[2]['ids'] 847 : $source_imap->getIdsOb(Horde_Imap_Client_Ids::ALL); 848 $move = !empty($args[2]['move']); 849 $retval = true; 850 851 $query = new Horde_Imap_Client_Fetch_Query(); 852 $query->fullText(array( 853 'peek' => true 854 )); 855 856 foreach ($this->getSlices($args[0], $ids) as $val) { 857 try { 858 $res = $source_imap->fetch($args[0], $query, array( 859 'ids' => $val, 860 'nocache' => true 861 )); 862 863 $append = array(); 864 foreach ($res as $msg) { 865 $append[] = array( 866 'data' => $msg->getFullMsg(true) 867 ); 868 } 869 870 $dest_imap->append($args[1], $append, array( 871 'create' => $create 872 )); 873 874 if ($move) { 875 $source_imap->expunge($args[0], array( 876 'delete' => true, 877 'ids' => $val 878 )); 879 } 880 } catch (IMP_Imap_Exception $e) { 881 $retval = false; 882 } 883 } 884 885 return $retval; 886 } 887 888 /** 889 * Handle copy() calls. This call may hit multiple servers, so 890 * need to handle separately from other IMAP calls. 891 * 892 * @see Horde_Imap_Client_Base#renameMailbox() 893 */ 894 protected function _renameMailbox() 895 { 896 $args = func_get_args(); 897 $source = IMP_Mailbox::get($args[0]); 898 899 if ($source->create() && $this->copy($source, $args[1])) { 900 $source->delete(); 901 } else { 902 throw new IMP_Imap_Exception(_("Could not move all messages between mailboxes, so the original mailbox was not removed.")); 903 } 904 } 905 906 /* Static methods. */ 907 908 /** 909 * Loads the IMP server configuration from backends.php. 910 * 911 * @param string $server Returns this labeled entry only. 912 * 913 * @return mixed If $server is set return this entry; else, return the 914 * entire servers array. Returns false on error. 915 */ 916 static public function loadServerConfig($server = null) 917 { 918 global $registry; 919 920 if (empty(self::$_backends)) { 921 try { 922 $s = $registry->loadConfigFile('backends.php', 'servers', 'imp')->config['servers']; 923 } catch (Horde_Exception $e) { 924 Horde::log($e, 'ERR'); 925 return false; 926 } 927 928 foreach ($s as $key => $val) { 929 if (empty($val['disabled'])) { 930 self::$_backends[$key] = new IMP_Imap_Config($val); 931 } 932 } 933 } 934 935 return is_null($server) 936 ? self::$_backends 937 : (isset(self::$_backends[$server]) ? self::$_backends[$server] : false); 938 } 939 940 /* Serializable methods. */ 941 942 /** 943 */ 944 public function serialize() 945 { 946 return $GLOBALS['injector']->getInstance('Horde_Pack')->pack( 947 array( 948 $this->_ob, 949 $this->_id, 950 $this->_config 951 ), 952 array( 953 'compression' => false, 954 'phpob' => true 955 ) 956 ); 957 } 958 959 /** 960 */ 961 public function unserialize($data) 962 { 963 list( 964 $this->_ob, 965 $this->_id, 966 $this->_config 967 ) = $GLOBALS['injector']->getInstance('Horde_Pack')->unpack($data); 968 } 969 970} 971