1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
42: class IMP_Imap implements Serializable
43: {
44:
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:
62: const NS_DEFAULT = "\0default";
63:
64: 65: 66: 67: 68:
69: protected static $_backends = array();
70:
71: 72: 73: 74: 75:
76: protected $_changed = false;
77:
78: 79: 80: 81: 82:
83: protected $_config;
84:
85: 86: 87: 88: 89:
90: protected $_id;
91:
92: 93: 94: 95: 96:
97: protected $_ob;
98:
99: 100: 101: 102: 103:
104: protected $_temp = array();
105:
106: 107: 108: 109: 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:
174: $url = new Horde_Imap_Client_Url();
175: if ($this->init) {
176: $url->hostspec = $this->getParam('hostspec');
177: $url->port = $this->getParam('port');
178: $url->protocol = $this->isImap() ? 'imap' : 'pop';
179: }
180: return $url;
181: }
182: }
183:
184: 185:
186: public function __toString()
187: {
188: return $this->_id;
189: }
190:
191: 192: 193: 194: 195: 196: 197:
198: protected function _getPerm($perm)
199: {
200: return 'backends:' . ($this->init ? $this->server_key . ':' : '') . $perm;
201: }
202:
203: 204: 205: 206: 207:
208: public function isImap()
209: {
210: return ($this->init &&
211: ($this->_ob instanceof Horde_Imap_Client_Socket));
212: }
213:
214: 215: 216: 217: 218:
219: public function isPop3()
220: {
221: return ($this->init &&
222: ($this->_ob instanceof Horde_Imap_Client_Socket_Pop3));
223: }
224:
225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235:
236: public function createBaseImapObject($username, $password, $skey)
237: {
238: if ($this->init) {
239: return $this->client_ob;
240: }
241:
242: if (($config = $this->loadServerConfig($skey)) === false) {
243: $error = new IMP_Imap_Exception('Could not load server configuration.');
244: Horde::log($error);
245: throw $error;
246: }
247:
248: $imap_config = array(
249: 'hostspec' => $config->hostspec,
250: 'id' => $config->id,
251: 'password' => new IMP_Imap_Password($password),
252: 'port' => $config->port,
253: 'secure' => (($secure = $config->secure) ? $secure : false),
254: 'username' => $username,
255:
256: 'imp:backend' => $skey
257: );
258:
259:
260: $this->_config = $config;
261:
262: try {
263: return $this->createImapObject($imap_config, ($config->protocol == 'imap'));
264: } catch (IMP_Imap_Exception $e) {
265: unset($this->_config);
266: throw $e;
267: }
268: }
269:
270: 271: 272: 273: 274: 275: 276: 277: 278:
279: public function createImapObject($config, $imap = true)
280: {
281: if ($this->init) {
282: return $this->_ob;
283: }
284:
285: $sconfig = $this->config;
286: $config = array_merge(array(
287: 'cache' => $sconfig->cache_params,
288: 'capability_ignore' => $sconfig->capability_ignore,
289: 'comparator' => $sconfig->comparator,
290: 'debug' => $sconfig->debug,
291: 'debug_literal' => $sconfig->debug_raw,
292: 'lang' => $sconfig->lang,
293: 'timeout' => $sconfig->timeout,
294:
295: ), $config);
296:
297: try {
298: $this->_ob = $imap
299: ? new Horde_Imap_Client_Socket($config)
300: : new Horde_Imap_Client_Socket_Pop3($config);
301: return $this->_ob;
302: } catch (Horde_Imap_Client_Exception $e) {
303: Horde::log($e->raw_msg);
304: throw new IMP_Imap_Exception($e);
305: }
306: }
307:
308: 309: 310:
311: public function doPostLoginTasks()
312: {
313: global $prefs;
314:
315: switch ($this->_config->protocol) {
316: case 'imap':
317:
318: foreach ($this->_config->special_mboxes as $key => $val) {
319: if ($key != IMP_Mailbox::MBOX_USERSPECIAL) {
320: $prefs->setValue($key, $val, array(
321: 'force' => true,
322: 'nosave' => true
323: ));
324: }
325: }
326: break;
327:
328: case 'pop':
329:
330: foreach (array('newmail_notify', 'save_sent_mail') as $val) {
331: $prefs->setValue($val, false, array(
332: 'force' => true,
333: 'nosave' => true
334: ));
335: $prefs->setLocked($val, true);
336: }
337: $prefs->setLocked(IMP_Mailbox::MBOX_DRAFTS, true);
338: $prefs->setLocked(IMP_Mailbox::MBOX_SENT, true);
339: $prefs->setLocked(IMP_Mailbox::MBOX_SPAM, true);
340: $prefs->setLocked(IMP_Mailbox::MBOX_TEMPLATES, true);
341: $prefs->setLocked(IMP_Mailbox::MBOX_TRASH, true);
342: break;
343: }
344:
345: $this->updateFetchIgnore();
346: }
347:
348: 349: 350: 351:
352: public function updateFetchIgnore()
353: {
354: if ($this->isImap()) {
355: $special = IMP_Mailbox::getSpecialMailboxes();
356: $cache = $this->_ob->getParam('cache');
357: $cache['fetch_ignore'] = array_filter(array(
358: strval($special[IMP_Mailbox::SPECIAL_SPAM]),
359: strval($special[IMP_Mailbox::SPECIAL_TRASH])
360: ));
361: $this->_ob->setParam('cache', $cache);
362: }
363: }
364:
365: 366: 367: 368: 369: 370: 371:
372: public function access($right)
373: {
374: global $injector;
375:
376: if (!$this->init) {
377: return false;
378: }
379:
380: switch ($right) {
381: case self::ACCESS_ACL:
382: return ($this->config->acl && $this->queryCapability('ACL'));
383:
384: case self::ACCESS_CREATEMBOX:
385: return ($this->isImap() &&
386: $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('create_mboxes')));
387:
388: case self::ACCESS_CREATEMBOX_MAX:
389: return ($this->isImap() &&
390: $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('max_create_mboxes')));
391:
392: case self::ACCESS_DRAFTS:
393: case self::ACCESS_FLAGS:
394: case self::ACCESS_IMPORT:
395: case self::ACCESS_SEARCH:
396: case self::ACCESS_UNSEEN:
397: return $this->isImap();
398:
399: case self::ACCESS_FOLDERS:
400: case self::ACCESS_TRASH:
401: return ($this->isImap() &&
402: $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('allow_folders')));
403:
404: case self::ACCESS_REMOTE:
405: return $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('allow_remote'));
406:
407: case self::ACCESS_SORT:
408: return ($this->isImap() &&
409: ($this->config->sort_force || $this->_ob->queryCapability('SORT')));
410: }
411:
412: return false;
413: }
414:
415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428:
429: public function accessCompose($right, $data)
430: {
431: switch ($right) {
432: case self::ACCESS_COMPOSE_BODYSIZE:
433: $perm_name = 'max_bodysize';
434: break;
435:
436: case self::ACCESS_COMPOSE_RECIPIENTS:
437: $perm_name = 'max_recipients';
438: break;
439:
440: case self::ACCESS_COMPOSE_TIMELIMIT:
441: $perm_name = 'max_timelimit';
442: break;
443:
444: default:
445: return false;
446: }
447:
448: return $GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission(
449: $perm_name,
450: array(
451: 'opts' => array(
452: 'value' => $data
453: )
454: )
455: );
456: }
457:
458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468:
469: public function getNamespace($mailbox, $personal = false)
470: {
471: if ($this->isImap()) {
472: $ns = $this->getNamespaces();
473: if ($mailbox !== self::NS_DEFAULT) {
474: return $ns->getNamespace($mailbox, $personal);
475: }
476:
477: foreach ($ns as $val) {
478: if ($val->type === $val::NS_PERSONAL) {
479: return $val;
480: }
481: }
482: }
483:
484: return null;
485: }
486:
487: 488: 489: 490: 491: 492: 493: 494:
495: public function getCacheId($mailbox, array $addl = array())
496: {
497: return $this->getSyncToken($mailbox) .
498: (empty($addl) ? '' : ('|' . implode('|', $addl)));
499: }
500:
501: 502: 503: 504: 505: 506: 507: 508: 509: 510:
511: public function parseCacheId($id)
512: {
513: $out = array('date' => null);
514:
515: if ((($pos = strrpos($id, '|')) !== false) &&
516: (substr($id, $pos + 1, 1) == 'D')) {
517: $out['date'] = substr($id, $pos + 2);
518: }
519:
520: $out['token'] = (($pos = strpos($id, '|')) === false)
521: ? $id
522: : substr($id, 0, $pos);
523:
524: return $out;
525: }
526:
527: 528: 529: 530: 531: 532: 533: 534: 535: 536:
537: public function getSlices(
538: $mbox, Horde_Imap_Client_Ids $ids, $size = 5242880
539: )
540: {
541: $imp_imap = IMP_Mailbox::get($mbox)->imp_imap;
542:
543: $query = new Horde_Imap_Client_Fetch_Query();
544: $query->size();
545:
546: try {
547: $res = $imp_imap->fetch($mbox, $query, array(
548: 'ids' => $ids,
549: 'nocache' => true
550: ));
551: } catch (IMP_Imap_Exception $e) {
552: return array();
553: }
554:
555: $curr = $slices = array();
556: $curr_size = 0;
557:
558: foreach ($res as $key => $val) {
559: $curr_size += $val->getSize();
560: if ($curr_size > $size) {
561: $slices[] = $imp_imap->getIdsOb($curr, $ids->sequence);
562: $curr = array();
563: }
564: $curr[] = $key;
565: }
566:
567: $slices[] = $imp_imap->getIdsOb($curr, $ids->sequence);
568:
569: return $slices;
570: }
571:
572: 573: 574: 575: 576:
577: protected function _status($args)
578: {
579: global $injector;
580:
581: $accounts = $mboxes = $out = array();
582: $imap_factory = $injector->getInstance('IMP_Factory_Imap');
583:
584: foreach (IMP_Mailbox::get($args[0]) as $val) {
585: if ($raccount = $val->remote_account) {
586: $accounts[strval($raccount)] = $raccount;
587: }
588: $mboxes[strval($raccount)][] = $val;
589: }
590:
591: foreach ($mboxes as $key => $val) {
592: $imap = $imap_factory->create($key);
593: if ($imap->init) {
594: foreach (call_user_func_array(array($imap, 'impStatus'), array($val) + $args) as $key2 => $val2) {
595: $out[isset($accounts[$key]) ? $accounts[$key]->mailbox($key2) : $key2] = $val2;
596: }
597: }
598: }
599:
600: return $out;
601: }
602:
603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613:
614: public function __call($method, $params)
615: {
616: global $injector;
617:
618: if (!$this->init) {
619:
620: switch ($method) {
621: case 'getIdsOb':
622: $ob = new Horde_Imap_Client_Ids();
623: call_user_func_array(array($ob, 'add'), $params);
624: return $ob;
625: }
626:
627: throw new Horde_Exception_AuthenticationFailure(
628: 'IMP is marked as authenticated, but no credentials can be found in the session.',
629: Horde_Auth::REASON_SESSION
630: );
631: }
632:
633: switch ($method) {
634: case 'append':
635: case 'createMailbox':
636: case 'deleteMailbox':
637: case 'expunge':
638: case 'fetch':
639: case 'getACL':
640: case 'getMetadata':
641: case 'getMyACLRights':
642: case 'getQuota':
643: case 'getQuotaRoot':
644: case 'getSyncToken':
645: case 'setMetadata':
646: case 'setQuota':
647: case 'store':
648: case 'subscribeMailbox':
649: case 'sync':
650: case 'thread':
651:
652:
653: $params[0] = IMP_Mailbox::getImapMboxOb($params[0]);
654: break;
655:
656: case 'copy':
657: case 'renameMailbox':
658:
659: $source = IMP_Mailbox::get($params[0]);
660: $dest = IMP_Mailbox::get($params[1]);
661: if ($source->remote_account != $dest->remote_account) {
662: return call_user_func_array(array($this, '_' . $method), $params);
663: }
664:
665:
666:
667: $params[0] = $source->imap_mbox_ob;
668: $params[1] = $dest->imap_mbox_ob;
669: break;
670:
671: case 'getNamespaces':
672: if (isset($this->_temp['ns'])) {
673: return $this->_temp['ns'];
674: }
675: $nsconfig = $this->config->namespace;
676: $params[0] = is_null($nsconfig) ? array() : $nsconfig;
677: $params[1] = array('ob_return' => true);
678: break;
679:
680: case 'impStatus':
681: 682:
683: $params[0] = IMP_Mailbox::getImapMboxOb($params[0]);
684: $method = 'status';
685: break;
686:
687: case 'openMailbox':
688: $mbox = IMP_Mailbox::get($params[0]);
689: if ($mbox->search) {
690:
691: return;
692: }
693: $params[0] = $mbox->imap_mbox_ob;
694: break;
695:
696: case 'search':
697: $params = call_user_func_array(array($this, '_search'), $params);
698: break;
699:
700: case 'status':
701: if (is_array($params[0])) {
702: return $this->_status($params);
703: }
704: $params[0] = IMP_Mailbox::getImapMboxOb($params[0]);
705: break;
706:
707: default:
708: if (!method_exists($this->_ob, $method)) {
709: throw new BadMethodCallException(
710: sprintf('%s: Invalid method call "%s".', __CLASS__, $method)
711: );
712: }
713: break;
714: }
715:
716: try {
717: $result = call_user_func_array(array($this->_ob, $method), $params);
718: } catch (Horde_Imap_Client_Exception $e) {
719: switch ($method) {
720: case 'getNamespaces':
721: return new Horde_Imap_Client_Namespace_List();
722: }
723:
724: Horde::log(
725: new Exception(
726: sprintf('[%s] %s', $method, $e->raw_msg),
727: $e->getCode(),
728: $e
729: ),
730: 'WARN'
731: );
732: $error = new IMP_Imap_Exception($e);
733: throw ($auth_e = $error->authException(false))
734: ? $auth_e
735: : $error;
736: }
737:
738:
739: switch ($method) {
740: case 'createMailbox':
741: case 'deleteMailbox':
742: case 'renameMailbox':
743: $injector->getInstance('IMP_Mailbox_SessionCache')->expire(
744: null,
745:
746: IMP_Mailbox::get($params[0])
747: );
748: break;
749:
750: case 'getNamespaces':
751: $this->_temp['ns'] = $result;
752: break;
753:
754: case 'login':
755: if (!$this->_ob->getParam('imp:login')) {
756:
757: if ($this->isPop3() && !$this->queryCapability('UIDL')) {
758: Horde::log(
759: sprintf(
760: 'The POP3 server does not support the REQUIRED UIDL capability. [server key: %s]',
761: $this->server_key
762: ),
763: 'CRIT'
764: );
765: throw new Horde_Exception_AuthenticationFailure(
766: _("The mail server is not currently avaliable."),
767: Horde_Auth::REASON_MESSAGE
768: );
769: }
770:
771: $this->_ob->setParam('imp:login', true);
772: $this->_changed = true;
773: }
774: break;
775:
776: case 'setACL':
777: $injector->getInstance('IMP_Mailbox_SessionCache')->expire(
778: IMP_Mailbox_SessionCache::CACHE_ACL,
779: IMP_Mailbox::get($params[0])
780: );
781: break;
782: }
783:
784: return $result;
785: }
786:
787: 788: 789: 790: 791: 792: 793: 794: 795: 796: 797:
798: protected function _search($mailbox, $query = null, array $opts = array())
799: {
800: $mailbox = IMP_Mailbox::get($mailbox);
801:
802: if (!empty($opts['sort']) && $mailbox->access_sort) {
803: 804: 805: 806:
807: foreach ($opts['sort'] as $key => $val) {
808: switch ($val) {
809: case Horde_Imap_Client::SORT_FROM:
810: $opts['sort'][$key] = Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK;
811: break;
812:
813: case Horde_Imap_Client::SORT_TO:
814: $opts['sort'][$key] = Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK;
815: break;
816: }
817: }
818: }
819:
820: if (!is_null($query)) {
821: $query->charset('UTF-8', false);
822: }
823:
824: return array($mailbox->imap_mbox_ob, $query, $opts);
825: }
826:
827: 828: 829: 830: 831:
832: protected function _copy()
833: {
834: global $injector;
835:
836: $args = func_get_args();
837: $imap_factory = $injector->getInstance('IMP_Factory_Imap');
838: $source_imap = $imap_factory->create($args[0]);
839: $dest_imap = $imap_factory->create($args[1]);
840:
841: $create = !empty($args[2]['create']);
842: $ids = isset($args[2]['ids'])
843: ? $args[2]['ids']
844: : $source_imap->getIdsOb(Horde_Imap_Client_Ids::ALL);
845: $move = !empty($args[2]['move']);
846: $retval = true;
847:
848: $query = new Horde_Imap_Client_Fetch_Query();
849: $query->fullText(array(
850: 'peek' => true
851: ));
852:
853: foreach ($this->getSlices($args[0], $ids) as $val) {
854: try {
855: $res = $source_imap->fetch($args[0], $query, array(
856: 'ids' => $val,
857: 'nocache' => true
858: ));
859:
860: $append = array();
861: foreach ($res as $msg) {
862: $append[] = array(
863: 'data' => $msg->getFullMsg(true)
864: );
865: }
866:
867: $dest_imap->append($args[1], $append, array(
868: 'create' => $create
869: ));
870:
871: if ($move) {
872: $source_imap->expunge($args[0], array(
873: 'delete' => true,
874: 'ids' => $val
875: ));
876: }
877: } catch (IMP_Imap_Exception $e) {
878: $retval = false;
879: }
880: }
881:
882: return $retval;
883: }
884:
885: 886: 887: 888: 889: 890:
891: protected function _renameMailbox()
892: {
893: $args = func_get_args();
894: $source = IMP_Mailbox::get($args[0]);
895:
896: if ($source->create() && $this->copy($source, $args[1])) {
897: $source->delete();
898: } else {
899: throw new IMP_Imap_Exception(_("Could not move all messages between mailboxes, so the original mailbox was not removed."));
900: }
901: }
902:
903:
904:
905: 906: 907: 908: 909: 910: 911: 912:
913: public static function loadServerConfig($server = null)
914: {
915: global $registry;
916:
917: if (empty(self::$_backends)) {
918: try {
919: $s = $registry->loadConfigFile('backends.php', 'servers', 'imp')->config['servers'];
920: } catch (Horde_Exception $e) {
921: Horde::log($e, 'ERR');
922: return false;
923: }
924:
925: foreach ($s as $key => $val) {
926: if (empty($val['disabled'])) {
927: self::$_backends[$key] = new IMP_Imap_Config($val);
928: }
929: }
930: }
931:
932: return is_null($server)
933: ? self::$_backends
934: : (isset(self::$_backends[$server]) ? self::$_backends[$server] : false);
935: }
936:
937:
938:
939: 940:
941: public function serialize()
942: {
943: return $GLOBALS['injector']->getInstance('Horde_Pack')->pack(
944: array(
945: $this->_ob,
946: $this->_id,
947: $this->_config
948: ),
949: array(
950: 'compression' => false,
951: 'phpob' => true
952: )
953: );
954: }
955:
956: 957:
958: public function unserialize($data)
959: {
960: list(
961: $this->_ob,
962: $this->_id,
963: $this->_config
964: ) = $GLOBALS['injector']->getInstance('Horde_Pack')->unpack($data);
965: }
966:
967: }
968: