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: class IMP_Mailbox_List
25: implements ArrayAccess, Countable, Iterator, Serializable
26: {
27: 28:
29: const SERIALIZE_LIMIT = 500;
30:
31: 32: 33: 34: 35:
36: public $changed = false;
37:
38: 39: 40: 41: 42:
43: protected $_buidmax = 0;
44:
45: 46: 47: 48: 49:
50: protected $_buids = array();
51:
52: 53: 54: 55: 56:
57: protected $_cacheid = null;
58:
59: 60: 61: 62: 63:
64: protected $_index = null;
65:
66: 67: 68: 69: 70:
71: protected $_mailbox;
72:
73: 74: 75: 76: 77:
78: protected $_sorted = null;
79:
80: 81: 82: 83: 84:
85: protected $_thread = array();
86:
87: 88: 89: 90: 91:
92: protected $_threadui = array();
93:
94: 95: 96: 97: 98:
99: public function __construct($mbox)
100: {
101: $this->_mailbox = IMP_Mailbox::get($mbox);
102: }
103:
104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136:
137: public function getMailboxArray($msgnum, $options = array())
138: {
139: $this->_buildMailbox();
140:
141: $headers = $overview = $to_process = $uids = array();
142:
143:
144: foreach ($msgnum as $i) {
145: 146: 147:
148: if (isset($this->_sorted[$i - 1])) {
149: $to_process[strval($this->_getMbox($i - 1))][$i] = $this->_sorted[$i - 1];
150: }
151: }
152:
153: $fetch_query = new Horde_Imap_Client_Fetch_Query();
154: $fetch_query->envelope();
155: $fetch_query->flags();
156: $fetch_query->size();
157: $fetch_query->uid();
158:
159: if (!empty($options['headers'])) {
160: $headers = array_merge($headers, array(
161: 'importance',
162: 'list-post',
163: 'x-priority'
164: ));
165: }
166:
167: if (!empty($options['type'])) {
168: $headers[] = 'content-type';
169: }
170:
171: if (!empty($headers)) {
172: $fetch_query->headers('imp', $headers, array(
173: 'cache' => true,
174: 'peek' => true
175: ));
176: }
177:
178: if (empty($options['preview'])) {
179: $cache = null;
180: $options['preview'] = 0;
181: } else {
182: $cache = $this->_mailbox->imp_imap->getCache();
183: }
184:
185:
186: foreach ($to_process as $mbox => $ids) {
187: try {
188: $imp_imap = IMP_Mailbox::get($mbox)->imp_imap;
189: $fetch_res = $imp_imap->fetch($mbox, $fetch_query, array(
190: 'ids' => $imp_imap->getIdsOb($ids)
191: ));
192:
193: if ($options['preview']) {
194: $preview_info = $tostore = array();
195: if ($cache) {
196: try {
197: $preview_info = $cache->get($mbox, $ids, array('IMPpreview', 'IMPpreviewc'));
198: } catch (IMP_Imap_Exception $e) {}
199: }
200: }
201:
202: $mbox_ids = array();
203:
204: foreach ($ids as $k => $v) {
205: if (!isset($fetch_res[$v])) {
206: continue;
207: }
208:
209: $f = $fetch_res[$v];
210: $uid = $f->getUid();
211: $v = array(
212: 'envelope' => $f->getEnvelope(),
213: 'flags' => $f->getFlags(),
214: 'headers' => $f->getHeaders('imp', Horde_Imap_Client_Data_Fetch::HEADER_PARSE),
215: 'idx' => $k,
216: 'mailbox' => $mbox,
217: 'size' => $f->getSize(),
218: 'uid' => $uid
219: );
220:
221: if (($options['preview'] === 2) ||
222: (($options['preview'] === 1) &&
223: (!$GLOBALS['prefs']->getValue('preview_show_unread') ||
224: !in_array(Horde_Imap_Client::FLAG_SEEN, $v['flags'])))) {
225: if (empty($preview_info[$uid])) {
226: try {
227: $imp_contents = $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create(new IMP_Indices($mbox, $uid));
228: $prev = $imp_contents->generatePreview();
229: $preview_info[$uid] = array(
230: 'IMPpreview' => $prev['text'],
231: 'IMPpreviewc' => $prev['cut']
232: );
233: if (!is_null($cache)) {
234: $tostore[$uid] = $preview_info[$uid];
235: }
236: } catch (Exception $e) {
237: $preview_info[$uid] = array(
238: 'IMPpreview' => '',
239: 'IMPpreviewc' => false
240: );
241: }
242: }
243:
244: $v['preview'] = $preview_info[$uid]['IMPpreview'];
245: $v['previewcut'] = $preview_info[$uid]['IMPpreviewc'];
246: }
247:
248: $overview[] = $v;
249: $mbox_ids[] = $uid;
250: }
251:
252: $uids[$mbox] = $mbox_ids;
253:
254: if (!is_null($cache) && !empty($tostore)) {
255: $status = $imp_imap->status($mbox, Horde_Imap_Client::STATUS_UIDVALIDITY);
256: $cache->set($mbox, $tostore, $status['uidvalidity']);
257: }
258: } catch (IMP_Imap_Exception $e) {}
259: }
260:
261: return array(
262: 'overview' => $overview,
263: 'uids' => new IMP_Indices($uids)
264: );
265: }
266:
267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283:
284: public function buildMailboxPage($page = 0, $start = 0)
285: {
286: global $prefs, $session;
287:
288: $this->_buildMailbox();
289:
290: $ret = array('msgcount' => count($this->_sorted));
291:
292: $page_size = max($prefs->getValue('max_msgs'), 1);
293:
294: if ($ret['msgcount'] > $page_size) {
295: $ret['pagecount'] = ceil($ret['msgcount'] / $page_size);
296:
297:
298: if (empty($page) || strcspn($page, '0123456789')) {
299: if (!empty($start)) {
300:
301: $page = ceil($start / $page_size);
302: } else {
303:
304: $page = $session->exists('imp', 'mbox_page/' . $this->_mailbox)
305: ? $session->get('imp', 'mbox_page/' . $this->_mailbox)
306: : ceil($this->mailboxStart($ret['msgcount']) / $page_size);
307: }
308: }
309:
310: 311:
312: $ret['page'] = intval($page);
313: if ($ret['page'] > $ret['pagecount']) {
314: $ret['page'] = $ret['pagecount'];
315: } elseif ($ret['page'] < 1) {
316: $ret['page'] = 1;
317: }
318:
319: $ret['begin'] = (($ret['page'] - 1) * $page_size) + 1;
320: $ret['end'] = $ret['begin'] + $page_size - 1;
321: if ($ret['end'] > $ret['msgcount']) {
322: $ret['end'] = $ret['msgcount'];
323: }
324: } else {
325: $ret['begin'] = 1;
326: $ret['end'] = $ret['msgcount'];
327: $ret['page'] = 1;
328: $ret['pagecount'] = 1;
329: }
330:
331: 332:
333: $ret['anymsg'] = true;
334: if (!$ret['msgcount'] && !$this->_mailbox->search) {
335: try {
336: $status = $this->_mailbox->imp_imap->status($this->_mailbox, Horde_Imap_Client::STATUS_MESSAGES);
337: $ret['anymsg'] = (bool)$status['messages'];
338: } catch (IMP_Imap_Exception $e) {
339: $ret['anymsg'] = false;
340: }
341: }
342:
343:
344: $session->set('imp', 'mbox_page/' . $this->_mailbox, $ret['page']);
345:
346: return $ret;
347: }
348:
349: 350: 351: 352: 353:
354: public function isBuilt()
355: {
356: return !is_null($this->_sorted);
357: }
358:
359: 360: 361:
362: protected function _buildMailbox()
363: {
364: $cacheid = $this->_mailbox->cacheid;
365:
366: if ($this->isBuilt() && ($this->_cacheid == $cacheid)) {
367: return;
368: }
369:
370: $this->changed = true;
371: $this->_cacheid = $cacheid;
372: $this->_sorted = array();
373:
374: $query_ob = $this->_buildMailboxQuery();
375: $sortpref = $this->_mailbox->getSort(true);
376: $thread_sort = ($sortpref->sortby == Horde_Imap_Client::SORT_THREAD);
377:
378: if ($this->_mailbox->access_search &&
379: $this->_mailbox->hideDeletedMsgs()) {
380: $delete_query = new Horde_Imap_Client_Search_Query();
381: $delete_query->flag(Horde_Imap_Client::FLAG_DELETED, false);
382:
383: if (is_null($query_ob)) {
384: $query_ob = array(strval($this->_mailbox) => $delete_query);
385: } else {
386: foreach ($query_ob as $val) {
387: $val->andSearch($delete_query);
388: }
389: }
390: }
391:
392: if (is_null($query_ob)) {
393: $query_ob = array(strval($this->_mailbox) => null);
394: }
395:
396: if ($thread_sort) {
397: $this->_thread = $this->_threadui = array();
398: }
399:
400: foreach ($query_ob as $mbox => $val) {
401: if ($thread_sort) {
402: $this->_getThread($mbox, $val ? array('search' => $val) : array());
403: $sorted = $this->_thread[$mbox]->messageList()->ids;
404: if ($sortpref->sortdir) {
405: $sorted = array_reverse($sorted);
406: }
407: } else {
408: $res = IMP_Mailbox::get($mbox)->imp_imap->search($mbox, $val, array(
409: 'sort' => array($sortpref->sortby)
410: ));
411: if ($sortpref->sortdir) {
412: $res['match']->reverse();
413: }
414: $sorted = $res['match']->ids;
415: }
416:
417: $this->_sorted = array_merge($this->_sorted, $sorted);
418: $this->_buildMailboxProcess($mbox, $sorted);
419: }
420: }
421:
422: 423:
424: protected function _buildMailboxQuery()
425: {
426: return null;
427: }
428:
429: 430:
431: protected function _buildMailboxProcess($mbox, $sorted)
432: {
433: }
434:
435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448:
449: public function unseenMessages($results, array $opts = array())
450: {
451: $count = ($results == Horde_Imap_Client::SEARCH_RESULTS_COUNT);
452:
453: if (empty($this->_sorted)) {
454: return $count ? 0 : array();
455: }
456:
457: $criteria = new Horde_Imap_Client_Search_Query();
458: $imp_imap = $this->_mailbox->imp_imap;
459:
460: if ($this->_mailbox->hideDeletedMsgs()) {
461: $criteria->flag(Horde_Imap_Client::FLAG_DELETED, false);
462: } elseif ($count) {
463: try {
464: $status_res = $imp_imap->status($this->_mailbox, Horde_Imap_Client::STATUS_UNSEEN);
465: return $status_res['unseen'];
466: } catch (IMP_Imap_Exception $e) {
467: return 0;
468: }
469: }
470:
471: $criteria->flag(Horde_Imap_Client::FLAG_SEEN, false);
472:
473: try {
474: $res = $imp_imap->search($this->_mailbox, $criteria, array(
475: 'results' => array($results),
476: 'sequence' => empty($opts['uids']),
477: 'sort' => empty($opts['sort']) ? null : $opts['sort']
478: ));
479: return $count ? $res['count'] : $res;
480: } catch (IMP_Imap_Exception $e) {
481: return $count ? 0 : array();
482: }
483: }
484:
485: 486: 487: 488: 489: 490: 491: 492:
493: public function mailboxStart($total)
494: {
495: switch ($GLOBALS['prefs']->getValue('mailbox_start')) {
496: case IMP::MAILBOX_START_FIRSTPAGE:
497: return 1;
498:
499: case IMP::MAILBOX_START_LASTPAGE:
500: return $total;
501:
502: case IMP::MAILBOX_START_FIRSTUNSEEN:
503: if (!$this->_mailbox->access_sort) {
504: return 1;
505: }
506:
507: $sortpref = $this->_mailbox->getSort();
508:
509: 510:
511: if ($sortpref->sortby == Horde_Imap_Client::SORT_SEQUENCE) {
512: try {
513: $res = $this->_mailbox->imp_imap->status($this->_mailbox, Horde_Imap_Client::STATUS_FIRSTUNSEEN | Horde_Imap_Client::STATUS_MESSAGES);
514: if (!is_null($res['firstunseen'])) {
515: return $sortpref->sortdir
516: ? ($res['messages'] - $res['firstunseen'] + 1)
517: : $res['firstunseen'];
518: }
519: } catch (IMP_Imap_Exception $e) {}
520:
521: return 1;
522: }
523:
524: $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SEARCH_RESULTS_MIN, array(
525: 'sort' => array(Horde_Imap_Client::SORT_DATE),
526: 'uids' => true
527: ));
528: return empty($unseen_msgs['min'])
529: ? 1
530: : ($this->getArrayIndex($unseen_msgs['min']) + 1);
531:
532: case IMP::MAILBOX_START_LASTUNSEEN:
533: if (!$this->_mailbox->access_sort) {
534: return 1;
535: }
536:
537: $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SEARCH_RESULTS_MAX, array(
538: 'sort' => array(Horde_Imap_Client::SORT_DATE),
539: 'uids' => true
540: ));
541: return empty($unseen_msgs['max'])
542: ? 1
543: : ($this->getArrayIndex($unseen_msgs['max']) + 1);
544: }
545: }
546:
547: 548: 549: 550: 551:
552: public function rebuild($reset = false)
553: {
554: $this->_cacheid = $this->_sorted = null;
555:
556: if ($reset) {
557: $this->_buidmax = 0;
558: $this->_buids = array();
559: $this->changed = true;
560: } else {
561: $this->_buildMailbox();
562: }
563: }
564:
565: 566: 567: 568: 569: 570: 571: 572: 573: 574:
575: public function getArrayIndex($uid, $mbox = null)
576: {
577: $this->_buildMailbox();
578:
579: 580:
581: return (($aindex = array_search($uid, $this->_sorted)) === false)
582: ? null
583: : $aindex;
584: }
585:
586: 587: 588: 589: 590:
591: public function getIndicesOb()
592: {
593: $this->_buildMailbox();
594:
595: return new IMP_Indices($this->_mailbox, $this->_sorted);
596: }
597:
598: 599: 600: 601: 602: 603: 604: 605:
606: public function removeMsgs($indices)
607: {
608: if ($indices === true) {
609: $this->rebuild();
610: return false;
611: }
612:
613: if (!count($indices)) {
614: return false;
615: }
616:
617:
618: foreach ($indices as $ob) {
619: foreach ($ob->uids as $uid) {
620: unset($this->_sorted[$this->getArrayIndex($uid, $ob->mbox)]);
621: }
622: }
623:
624: $this->changed = true;
625: $this->_sorted = array_values($this->_sorted);
626:
627: if (isset($this->_thread[strval($ob->mbox)])) {
628: unset($this->_thread[strval($ob->mbox)], $this->_threadui[strval($ob->mbox)]);
629: }
630:
631: if (!is_null($this->_index)) {
632: $this->setIndex(0);
633: }
634:
635: return true;
636: }
637:
638: 639: 640: 641: 642: 643: 644: 645: 646: 647:
648: public function getFullThread($uid, $mbox = null)
649: {
650: if (is_null($mbox)) {
651: $mbox = $this->_mailbox;
652: }
653:
654: return new IMP_Indices($mbox, array_keys($this->_getThread($mbox)->getThread($uid)));
655: }
656:
657: 658: 659: 660: 661: 662: 663:
664: public function getThreadOb($offset)
665: {
666: $entry = $this[$offset];
667: $mbox = strval($entry['m']);
668: $uid = $entry['u'];
669:
670: if (!isset($this->_threadui[$mbox][$uid])) {
671: $thread_level = array();
672: $t_ob = $this->_getThread($mbox);
673:
674: foreach ($t_ob->getThread($uid) as $key => $val) {
675: if (is_null($val->base) ||
676: ($val->last && ($val->base == $key))) {
677: $this->_threadui[$mbox][$key] = '';
678: continue;
679: }
680:
681: if ($val->last) {
682: $join = IMP_Mailbox_List_Thread::JOINBOTTOM;
683: } else {
684: $join = (!$val->level && ($val->base == $key))
685: ? IMP_Mailbox_List_Thread::JOINBOTTOM_DOWN
686: : IMP_Mailbox_List_Thread::JOIN;
687: }
688:
689: $thread_level[$val->level] = $val->last;
690: $line = '';
691:
692: for ($i = 0; $i < $val->level; ++$i) {
693: if (isset($thread_level[$i])) {
694: $line .= (isset($thread_level[$i]) && !$thread_level[$i])
695: ? IMP_Mailbox_List_Thread::LINE
696: : IMP_Mailbox_List_Thread::BLANK;
697: }
698: }
699:
700: $this->_threadui[$mbox][$key] = $line . $join;
701: }
702: }
703:
704: return new IMP_Mailbox_List_Thread($this->_threadui[$mbox][$uid]);
705: }
706:
707: 708: 709: 710: 711: 712: 713: 714:
715: protected function _getThread($mbox, array $extra = array())
716: {
717: if (!isset($this->_thread[strval($mbox)])) {
718: $imp_imap = IMP_Mailbox::get($mbox)->imp_imap;
719:
720: try {
721: $thread = $imp_imap->thread($mbox, array_merge($extra, array(
722: 'criteria' => $imp_imap->thread_algo
723: )));
724: } catch (Horde_Imap_Client_Exception $e) {
725: $thread = new Horde_Imap_Client_Data_Thread(array(), 'uid');
726: }
727:
728: $this->_thread[strval($mbox)] = $thread;
729: }
730:
731: return $this->_thread[strval($mbox)];
732: }
733:
734: 735: 736: 737: 738: 739: 740:
741: protected function _getMbox($id)
742: {
743: return $this->_mailbox;
744: }
745:
746:
747:
748: 749: 750: 751: 752: 753: 754: 755:
756: public function getBuid($mbox, $uid)
757: {
758: return $uid;
759: }
760:
761: 762: 763: 764: 765: 766: 767: 768: 769:
770: public function resolveBuid($buid)
771: {
772: return array(
773: 'm' => $this->_mailbox,
774: 'u' => intval($buid)
775: );
776: }
777:
778:
779:
780: 781: 782: 783: 784: 785:
786: public function getIndex()
787: {
788: return $this->isValidIndex()
789: ? ($this->_index + 1)
790: : 1;
791: }
792:
793: 794: 795: 796: 797:
798: public function isValidIndex()
799: {
800: return !is_null($this->_index);
801: }
802:
803: 804: 805: 806: 807: 808: 809:
810: public function setIndex($data)
811: {
812: if ($data instanceof IMP_Indices) {
813: list($mailbox, $uid) = $data->getSingle();
814: $this->_index = $this->getArrayIndex($uid, $mailbox);
815: if (is_null($this->_index)) {
816: $this->rebuild();
817: $this->_index = $this->getArrayIndex($uid, $mailbox);
818: }
819: } else {
820: $index = $this->_index += $data;
821: if (isset($this->_sorted[$this->_index])) {
822: if (!isset($this->_sorted[$this->_index + 1])) {
823: $this->rebuild();
824: }
825: } else {
826: $this->rebuild();
827: $this->_index = isset($this->_sorted[$index])
828: ? $index
829: : null;
830: }
831: }
832: }
833:
834:
835:
836: 837: 838:
839: public function offsetExists($offset)
840: {
841: return isset($this->_sorted[$offset - 1]);
842: }
843:
844: 845: 846: 847: 848: 849: 850:
851: public function offsetGet($offset)
852: {
853: if (!isset($this->_sorted[$offset - 1])) {
854: return null;
855: }
856:
857: $ret = array(
858: 'm' => $this->_getMbox($offset - 1),
859: 'u' => $this->_sorted[$offset - 1]
860: );
861:
862: return $ret;
863: }
864:
865: 866: 867:
868: public function offsetSet($offset, $value)
869: {
870: throw new BadMethodCallException('Not supported');
871: }
872:
873: 874: 875:
876: public function offsetUnset($offset)
877: {
878: throw new BadMethodCallException('Not supported');
879: }
880:
881:
882:
883: 884: 885: 886: 887:
888: public function count()
889: {
890: $this->_buildMailbox();
891: return count($this->_sorted);
892: }
893:
894:
895:
896: 897: 898: 899: 900:
901: public function current()
902: {
903: return $this[key($this->_sorted) + 1];
904: }
905:
906: 907: 908:
909: public function key()
910: {
911: return (key($this->_sorted) + 1);
912: }
913:
914: 915:
916: public function next()
917: {
918: next($this->_sorted);
919: }
920:
921: 922:
923: public function rewind()
924: {
925: reset($this->_sorted);
926: }
927:
928: 929:
930: public function valid()
931: {
932: return (key($this->_sorted) !== null);
933: }
934:
935:
936:
937: 938: 939: 940: 941:
942: public function serialize()
943: {
944: return $GLOBALS['injector']->getInstance('Horde_Pack')->pack(
945: $this->_serialize(),
946: array(
947: 'compression' => false,
948: 'phpob' => true
949: )
950: );
951: }
952:
953: 954:
955: protected function _serialize()
956: {
957: $data = array(
958: 'm' => $this->_mailbox
959: );
960:
961: if ($this->_buidmax) {
962: $data['bm'] = $this->_buidmax;
963: if (!empty($this->_buids)) {
964: $data['b'] = $this->_buids;
965: }
966: }
967:
968: if (!is_null($this->_cacheid)) {
969: $data['c'] = $this->_cacheid;
970: }
971:
972: if (!is_null($this->_sorted)) {
973:
974: if (count($this->_sorted) > self::SERIALIZE_LIMIT) {
975: $ids = $this->_mailbox->imp_imap->getIdsOb();
976:
977: $ids->duplicates = true;
978: $ids->add($this->_sorted);
979: $data['so'] = $ids;
980: } else {
981: $data['so'] = $this->_sorted;
982: }
983: }
984:
985: return $data;
986: }
987:
988: 989: 990: 991: 992: 993: 994:
995: public function unserialize($data)
996: {
997: $this->_unserialize(
998: $GLOBALS['injector']->getInstance('Horde_Pack')->unpack($data)
999: );
1000: }
1001:
1002: 1003:
1004: protected function _unserialize($data)
1005: {
1006: $this->_mailbox = $data['m'];
1007:
1008: if (isset($data['bm'])) {
1009: $this->_buidmax = $data['bm'];
1010: if (isset($data['b'])) {
1011: $this->_buids = $data['b'];
1012: }
1013: }
1014:
1015: if (isset($data['c'])) {
1016: $this->_cacheid = $data['c'];
1017: }
1018:
1019: if (isset($data['so'])) {
1020: $this->_sorted = is_object($data['so'])
1021: ? $data['so']->ids
1022: : $data['so'];
1023: }
1024: }
1025:
1026: }
1027: