svncommit
2005-10-27 66773789e392305bba4cdf7ed8e6ae3b8380de51
commit | author | age
4e17e6 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcube_imap.inc                                        |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
8  | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
30233b 9  | Licensed under the GNU GPL                                            |
4e17e6 10  |                                                                       |
T 11  | PURPOSE:                                                              |
12  |   IMAP wrapper that implements the Iloha IMAP Library (IIL)           |
13  |   See http://ilohamail.org/ for details                               |
14  |                                                                       |
15  +-----------------------------------------------------------------------+
16  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17  +-----------------------------------------------------------------------+
18
19  $Id$
20
21 */
22
23
24 require_once('lib/imap.inc');
25 require_once('lib/mime.inc');
a95e0e 26 require_once('lib/utf7.inc');
4e17e6 27
T 28
29 class rcube_imap
30   {
31   var $conn;
520c36 32   var $root_ns = '';
4e17e6 33   var $root_dir = '';
T 34   var $mailbox = 'INBOX';
35   var $list_page = 1;
36   var $page_size = 10;
597170 37   var $delimiter = NULL;
6dc026 38   var $caching_enabled = FALSE;
4e17e6 39   var $default_folders = array('inbox', 'drafts', 'sent', 'junk', 'trash');
T 40   var $cache = array();
41   var $cache_changes = array();  
42   var $uid_id_map = array();
43   var $msg_headers = array();
44
45
46   // PHP 5 constructor
47   function __construct()
48     {
6dc026 49     
4e17e6 50     }
T 51
52   // PHP 4 compatibility
53   function rcube_imap()
54     {
55     $this->__construct();
56     }
57
58
42b113 59   function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
4e17e6 60     {
7902df 61     global $ICL_SSL, $ICL_PORT, $CONFIG;
42b113 62     
T 63     // check for Open-SSL support in PHP build
64     if ($use_ssl && in_array('openssl', get_loaded_extensions()))
65       $ICL_SSL = TRUE;
520c36 66     else if ($use_ssl)
T 67       {
68       raise_error(array('code' => 403,
69                         'type' => 'imap',
7902df 70                         'file' => __FILE__,
520c36 71                         'message' => 'Open SSL not available;'), TRUE, FALSE);
T 72       $port = 143;
73       }
4e17e6 74
T 75     $ICL_PORT = $port;
42b113 76     $this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
4e17e6 77     $this->host = $host;
T 78     $this->user = $user;
79     $this->pass = $pass;
520c36 80     $this->port = $port;
T 81     $this->ssl = $use_ssl;
42b113 82     
520c36 83     // print trace mesages
42b113 84     if ($this->conn && ($CONFIG['debug_level'] & 8))
520c36 85       console($this->conn->message);
T 86     
87     // write error log
42b113 88     else if (!$this->conn && $GLOBALS['iil_error'])
T 89       {
90       raise_error(array('code' => 403,
91                        'type' => 'imap',
92                        'message' => $GLOBALS['iil_error']), TRUE, FALSE);
520c36 93       }
T 94
95     // get account namespace
96     if ($this->conn)
97       {
98       iil_C_NameSpace($this->conn);
99       
100       if (!empty($this->conn->delimiter))
101         $this->delimiter = $this->conn->delimiter;
102       if (!empty($this->conn->rootdir))
7902df 103         {
T 104         $this->set_rootdir($this->conn->rootdir);
105         $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
106         }
42b113 107       }
4e17e6 108
T 109     return $this->conn ? TRUE : FALSE;
110     }
111
112
113   function close()
114     {    
115     if ($this->conn)
116       iil_Close($this->conn);
117     }
118
119
520c36 120   function reconnect()
T 121     {
122     $this->close();
123     $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
124     }
125
126
4e17e6 127   function set_rootdir($root)
T 128     {
520c36 129     if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
4e17e6 130       $root = substr($root, 0, -1);
T 131
132     $this->root_dir = $root;
520c36 133     
T 134     if (empty($this->delimiter))
135       $this->get_hierarchy_delimiter();
4e17e6 136     }
T 137
138
139   function set_default_mailboxes($arr)
140     {
141     if (is_array($arr))
142       {
143       $this->default_folders = array();
144       
145       // add mailbox names lower case
146       foreach ($arr as $mbox)
147         $this->default_folders[] = strtolower($mbox);
148       
149       // add inbox if not included
150       if (!in_array('inbox', $this->default_folders))
151         array_unshift($arr, 'inbox');
152       }
153     }
154
155
156   function set_mailbox($mbox)
157     {
158     $mailbox = $this->_mod_mailbox($mbox);
159
160     if ($this->mailbox == $mailbox)
161       return;
162
163     $this->mailbox = $mailbox;
164
165     // clear messagecount cache for this mailbox
166     $this->_clear_messagecount($mailbox);
167     }
168
169
170   function set_page($page)
171     {
172     $this->list_page = (int)$page;
173     }
174
175
176   function set_pagesize($size)
177     {
178     $this->page_size = (int)$size;
179     }
180
181
182   function get_mailbox_name()
183     {
184     return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
185     }
186
187
597170 188   function get_hierarchy_delimiter()
T 189     {
190     if ($this->conn && empty($this->delimiter))
191       $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
192
7902df 193     if (empty($this->delimiter))
T 194       $this->delimiter = '/';
195
597170 196     return $this->delimiter;
T 197     }
198
4e17e6 199   // public method for mailbox listing
T 200   // convert mailbox name with root dir first
201   function list_mailboxes($root='', $filter='*')
202     {
203     $a_out = array();
204     $a_mboxes = $this->_list_mailboxes($root, $filter);
205
206     foreach ($a_mboxes as $mbox)
207       {
208       $name = $this->_mod_mailbox($mbox, 'out');
209       if (strlen($name))
210         $a_out[] = $name;
211       }
212
213     // sort mailboxes
214     $a_out = $this->_sort_mailbox_list($a_out);
215
216     return $a_out;
217     }
218
219   // private method for mailbox listing
220   function _list_mailboxes($root='', $filter='*')
221     {
222     $a_defaults = $a_out = array();
223     
224     // get cached folder list    
225     $a_mboxes = $this->get_cache('mailboxes');
226     if (is_array($a_mboxes))
227       return $a_mboxes;
228
229     // retrieve list of folders from IMAP server
230     $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
231     
232     if (!is_array($a_folders) || !sizeof($a_folders))
233       $a_folders = array();
234
235     // create INBOX if it does not exist
236     if (!in_array_nocase('INBOX', $a_folders))
237       {
238       $this->create_mailbox('INBOX', TRUE);
239       array_unshift($a_folders, 'INBOX');
240       }
241
242     $a_mailbox_cache = array();
243
244     // write mailboxlist to cache
245     $this->update_cache('mailboxes', $a_folders);
246     
247     return $a_folders;
248     }
249
250
251   // get message count for a specific mailbox; acceptes modes are: ALL, UNSEEN
252   function messagecount($mbox='', $mode='ALL', $force=FALSE)
253     {
254     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
255     return $this->_messagecount($mailbox, $mode, $force);
256     }
257
258   // private method for getting nr of mesages
259   function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
260     {
261     $a_mailbox_cache = FALSE;
262     $mode = strtoupper($mode);
263
264     if (!$mailbox)
265       $mailbox = $this->mailbox;
266
267     $a_mailbox_cache = $this->get_cache('messagecount');
268     
269     // return cached value
270     if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
271       return $a_mailbox_cache[$mailbox][$mode];      
272
273     // get message count and store in cache
274     if ($mode == 'UNSEEN')
275       $count = iil_C_CountUnseen($this->conn, $mailbox);
276     else
277       $count = iil_C_CountMessages($this->conn, $mailbox);
278
279     if (is_array($a_mailbox_cache[$mailbox]))
280       $a_mailbox_cache[$mailbox] = array();
281       
282     $a_mailbox_cache[$mailbox][$mode] = (int)$count;
283     
284     // write back to cache
285     $this->update_cache('messagecount', $a_mailbox_cache);
286
287     return (int)$count;
288     }
289
290
291   // public method for listing headers
292   // convert mailbox name with root dir first
293   function list_headers($mbox='', $page=NULL, $sort_field='date', $sort_order='DESC')
294     {
295     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
296     return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
297     }
298
299
300   // private method for listing message header
7902df 301   // by DrSlump <drslump@drslump.biz>
T 302   function __list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC')
303     {
304     $a_out = array();
305     $cached_count = 0;
306
307     if (!strlen($mailbox))
308       return $a_out;
309
310     $mbox_count = $this->_messagecount($mailbox /*, 'ALL', TRUE*/);
311
312     $revalidate = false;
313     if ($mbox_count)
314       {
315       // get cached headers
316       $a_out = $this->get_cache($mailbox.'.msg');
317       $a_out = is_array($a_out) ? $a_out : array(); // make sure we get an array
318
319       $cached_count = count($a_out);
320       $a_new = array();
321       $revalidate = true; // revalidate by default
322      
323       // if the cache count is greater then there have been changes for sure
324       if ($cached_count <= $mbox_count)
325         {
326         $from = $cached_count?$cached_count:1;
327        
328         //get new headers (at least one is returned)
329         $a_temp = iil_C_FetchHeaders($this->conn, $mailbox, $from . ':' . $mbox_count);
330         $duplicated = $cached_count?true:false;
331        
332         foreach ($a_temp as $hdr)
333           {
334           //skip the first one if duplicated
335           if ($duplicated)
336             {
337             //check for changes using the UID
338             $lastCacheHdr = end($a_out);
339             if ($hdr->uid === $lastCacheHdr->uid)
340               $revalidate = false;
341
342             $duplicated = false;
343             continue;
344             }
345            
346           //skip deleted ones
347           if (! $hdr->deleted)
348             $a_new[ $hdr->uid ] = $hdr;
349           }
350         }
351
352       //revalidate cache if needed
353       $to = $mbox_count - count($a_new);
354       if ($revalidate && $to !== 0)    //we'll need to reindex the array so we have to make a copy
355         {
356         $a_dirty = $a_out;
357         $a_out = array();
358         $a_buffers = array();
359
360         //fetch chunks of 20 headers
361         $step = 20;
362         $found = false;
363          
364         //fetch headers in blocks starting from new to old
365         do {
366           $from = $to-$step;
367           if ($from < 1) $from = 1;
368
369           //store the block in a temporal buffer
370           $a_buffers[$from] = iil_C_FetchHeaders($this->conn, $mailbox, $from . ':' . $to);
371
372           //compare the fetched headers with the ones in the cache
373           $idx = 0;
374           foreach ($a_buffers[$from] as $k=>$hdr)
375             {
376             //if it's different the comparison ends
377             if (!isset($a_dirty[$hdr->uid]) || $a_dirty[$hdr->uid]->id !== $hdr->id)
378               break;
379
380             //if we arrive here then we know that the older messages in cache are ok
381             $found = $hdr->id;
382             $idx++;
383             }
384
385           //remove from the buffer the headers which are already cached
386           if ($found)
387             $a_buffers[$from] = array_splice($a_buffers[$from], 0, $idx );
388              
389           $to = $from-1;
390           }
391         while ($found===false && $from > 1);
392
393         //just keep the headers we are certain that didn't change in the cache
394         if ($found !== false)
395           {
396           foreach ($a_dirty as $hdr)
397             {
398             if ($hdr->id > $found) break;
399             $a_out[$hdr->uid] = $hdr;
400             }
401           }
402            
403         //we builded the block buffers from new to older, we process them in reverse order
404         ksort($a_buffers, SORT_NUMERIC);
405         foreach ($a_buffers as $a_buff)
406           {
407           foreach ($a_buff as $hdr)
408             {
409             if (! $hdr->deleted)
410               $a_out[$hdr->uid] = $hdr;
411             }
412           }
413         }
414          
415       //array_merge() would reindex the keys, so we use this 'hack'
416       $a_out += $a_new;
417       }
418  
419     //write headers list to cache if needed
420     if ($revalidate || count($a_out)!=$cached_count) {
421       $this->update_cache($mailbox.'.msg', $a_out);
422      }
423
424     //sort headers by a specific col
425     $a_out = iil_SortHeaders( $a_out, $sort_field, $sort_order );
426    
427     // return complete list of messages
428     if (strtolower($page)=='all')
429       return $a_out;
430
431     $start_msg = ($this->list_page-1) * $this->page_size;
432     return array_slice($a_out, $start_msg, $this->page_size);
433     }
434
435
b076a4 436   // original function; replaced 2005/10/18
7902df 437   // private method for listing message header
4e17e6 438   function _list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC')
T 439     {
7902df 440     $max = $this->_messagecount($mailbox);
T 441
4e17e6 442     if (!strlen($mailbox))
17fc71 443       return array();
4e17e6 444
T 445     // get cached headers
446     $a_msg_headers = $this->get_cache($mailbox.'.msg');
447
448     // retrieve headers from IMAP
449     if (!is_array($a_msg_headers) || sizeof($a_msg_headers) != $max)
450       {
451       $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, "1:$max");
452       $a_msg_headers = array();
7902df 453
8c0328 454       if (!empty($a_header_index))
7902df 455         foreach ($a_header_index as $i => $headers)
T 456           if (!$headers->deleted)
457             $a_msg_headers[$headers->uid] = $headers;
4e17e6 458       }
T 459     else
460       $headers_cached = TRUE;
461
69f71b 462     if (!is_array($a_msg_headers))
e02e3d 463         return array();
S 464         
4e17e6 465     // sort headers by a specific col
T 466     $a_headers = iil_SortHeaders($a_msg_headers, $sort_field, $sort_order);
8c0328 467
17fc71 468     // free memory
S 469     unset($a_msg_headers);
470     
4e17e6 471     // write headers list to cache
T 472     if (!$headers_cached)
17fc71 473       $this->update_cache($mailbox.'.msg', $a_headers);
4e17e6 474
8c0328 475     if (empty($a_headers))
S 476         return array();
477         
4e17e6 478     // return complete list of messages
T 479     if (strtolower($page)=='all')
17fc71 480       return $a_headers;
b076a4 481
4e17e6 482     $start_msg = ($this->list_page-1) * $this->page_size;
17fc71 483     return array_slice($a_headers, $start_msg, $this->page_size);
4e17e6 484     }
7902df 485   
4e17e6 486
T 487   // return sorted array of message UIDs
488   function message_index($mbox='', $sort_field='date', $sort_order='DESC')
489     {
490     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
491     $a_out = array();
492
493     // get array of message headers
494     $a_headers = $this->_list_headers($mailbox, 'all', $sort_field, $sort_order);
495
496     if (is_array($a_headers))
497       foreach ($a_headers as $header)
498         $a_out[] = $header->uid;
499
500     return $a_out;
501     }
502
503
504   function sync_header_index($mbox=NULL)
505     {
506     
507     }
508
509
510   function search($mbox='', $criteria='ALL')
511     {
512     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
513     $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
514     return $a_messages;
515     }
516
517
518   function get_headers($uid, $mbox=NULL)
519     {
520     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
521     
522     // get cached headers
523     $a_msg_headers = $this->get_cache($mailbox.'.msg');
524     
525     // return cached header
526     if ($a_msg_headers[$uid])
527       return $a_msg_headers[$uid];
520c36 528
4e17e6 529     $msg_id = $this->_uid2id($uid);
T 530     $header = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
520c36 531
4e17e6 532     // write headers cache
T 533     $a_msg_headers[$uid] = $header;
534     $this->update_cache($mailbox.'.msg', $a_msg_headers);
535
536     return $header;
537     }
538
539
540   function get_body($uid, $part=1)
541     {
542     if (!($msg_id = $this->_uid2id($uid)))
543       return FALSE;
544
545     $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
546     $structure = iml_GetRawStructureArray($structure_str);
547     $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
548
549     $encoding = iml_GetPartEncodingCode($structure, $part);
550     
551     if ($encoding==3) $body = $this->mime_decode($body, 'base64');
552     else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
553
554     return $body;
555     }
556
557
558   function get_raw_body($uid)
559     {
560     if (!($msg_id = $this->_uid2id($uid)))
561       return FALSE;
562
563     $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
564     $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
565
566     return $body;    
567     }
568
569
570   // set message flag to one or several messages
571   // possible flgs are: SEEN, DELETED, RECENT, ANSWERED, DRAFT
572   function set_flag($uids, $flag)
573     {
574     $flag = strtoupper($flag);
575     $msg_ids = array();
576     if (!is_array($uids))
577       $uids = array($uids);
578       
579     foreach ($uids as $uid)
580       $msg_ids[] = $this->_uid2id($uid);
581       
582     if ($flag=='UNSEEN')
583       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', $msg_ids));
584     else
585       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', $msg_ids), $flag);
586
587     // reload message headers if cached
588     $cache_key = $this->mailbox.'.msg';
520c36 589     if ($this->caching_enabled && $result && ($a_cached_headers = $this->get_cache($cache_key)))
4e17e6 590       {
9fee0e 591       // close and re-open connection
520c36 592       $this->reconnect();
T 593
4e17e6 594       foreach ($uids as $uid)
T 595         {
596         if (isset($a_cached_headers[$uid]))
597           {
598           unset($this->cache[$cache_key][$uid]);
599           $this->get_headers($uid);
600           }
601         }
602       }
603
604     // set nr of messages that were flaged
605     $count = sizeof($msg_ids);
606
607     // clear message count cache
608     if ($result && $flag=='SEEN')
609       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
610     else if ($result && $flag=='UNSEEN')
611       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
612     else if ($result && $flag=='DELETED')
613       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
614
615     return $result;
616     }
617
618
619   // append a mail message (source) to a specific mailbox
620   function save_message($mbox, $message)
621     {
622     $mailbox = $this->_mod_mailbox($mbox);
623
624     // make shure mailbox exists
625     if (in_array($mailbox, $this->_list_mailboxes()))
626       $saved = iil_C_Append($this->conn, $mailbox, $message);
627     
628     if ($saved)
629       {
630       // increase messagecount of the target mailbox
631       $this->_set_messagecount($mailbox, 'ALL', 1);
632       }
633           
634     return $saved;
635     }
636
637
638   // move a message from one mailbox to another
639   function move_message($uids, $to_mbox, $from_mbox='')
640     {
641     $to_mbox = $this->_mod_mailbox($to_mbox);
642     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
643
644     // make shure mailbox exists
645     if (!in_array($to_mbox, $this->_list_mailboxes()))
646       return FALSE;
647     
648     // convert the list of uids to array
649     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
650     
651     // exit if no message uids are specified
652     if (!is_array($a_uids))
653       return false;
520c36 654
4e17e6 655     // convert uids to message ids
T 656     $a_mids = array();
657     foreach ($a_uids as $uid)
658       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 659
4e17e6 660     $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 661     
662     // send expunge command in order to have the moved message
663     // really deleted from the source mailbox
664     if ($moved)
665       {
666       $this->expunge($from_mbox, FALSE);
667       $this->clear_cache($to_mbox.'.msg');
668       $this->_clear_messagecount($from_mbox);
669       $this->_clear_messagecount($to_mbox);
670       }
671
672     // update cached message headers
673     $cache_key = $from_mbox.'.msg';
674     if ($moved && ($a_cached_headers = $this->get_cache($cache_key)))
675       {
676       foreach ($a_uids as $uid)
677         unset($a_cached_headers[$uid]);
678
679       $this->update_cache($cache_key, $a_cached_headers);
680       }
681
682     return $moved;
683     }
684
685
686   // mark messages as deleted and expunge mailbox
687   function delete_message($uids, $mbox='')
688     {
689     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
690
691     // convert the list of uids to array
692     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
693     
694     // exit if no message uids are specified
695     if (!is_array($a_uids))
696       return false;
697
698
699     // convert uids to message ids
700     $a_mids = array();
701     foreach ($a_uids as $uid)
702       $a_mids[] = $this->_uid2id($uid, $mailbox);
703         
704     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
705     
706     // send expunge command in order to have the deleted message
707     // really deleted from the mailbox
708     if ($deleted)
709       {
710       $this->expunge($mailbox, FALSE);
711       $this->_clear_messagecount($mailbox);
712       }
713
714     // remove deleted messages from cache
715     if ($deleted && ($a_cached_headers = $this->get_cache($mailbox.'.msg')))
716       {
717       foreach ($a_uids as $uid)
718         unset($a_cached_headers[$uid]);
719
720       $this->update_cache($mailbox.'.msg', $a_cached_headers);
721       }
722
723     return $deleted;
724     }
725
726
a95e0e 727   // clear all messages in a specific mailbox
T 728   function clear_mailbox($mbox)
729     {
730     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
731     $msg_count = $this->_messagecount($mailbox, 'ALL');
732     
733     if ($msg_count>0)
734       return iil_C_ClearFolder($this->conn, $mailbox);
735     else
736       return 0;
737     }
738
739
4e17e6 740   // send IMAP expunge command and clear cache
T 741   function expunge($mbox='', $clear_cache=TRUE)
742     {
743     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
744     
745     $result = iil_C_Expunge($this->conn, $mailbox);
746
747     if ($result>=0 && $clear_cache)
748       {
749       $this->clear_cache($mailbox.'.msg');
750       $this->_clear_messagecount($mailbox);
751       }
752       
753     return $result;
754     }
755
756
757
758   /* --------------------------------
759    *        folder managment
760    * --------------------------------*/
761
762
763   // return an array with all folders available in IMAP server
764   function list_unsubscribed($root='')
765     {
766     static $sa_unsubscribed;
767     
768     if (is_array($sa_unsubscribed))
769       return $sa_unsubscribed;
770       
771     // retrieve list of folders from IMAP server
772     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
773
774     // modify names with root dir
775     foreach ($a_mboxes as $mbox)
776       {
777       $name = $this->_mod_mailbox($mbox, 'out');
778       if (strlen($name))
779         $a_folders[] = $name;
780       }
781
782     // filter folders and sort them
783     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
784     return $sa_unsubscribed;
785     }
786
787
788   // subscribe to a specific mailbox(es)
789   function subscribe($mbox, $mode='subscribe')
790     {
791     if (is_array($mbox))
792       $a_mboxes = $mbox;
793     else if (is_string($mbox) && strlen($mbox))
794       $a_mboxes = explode(',', $mbox);
795     
796     // let this common function do the main work
797     return $this->_change_subscription($a_mboxes, 'subscribe');
798     }
799
800
801   // unsubscribe mailboxes
802   function unsubscribe($mbox)
803     {
804     if (is_array($mbox))
805       $a_mboxes = $mbox;
806     else if (is_string($mbox) && strlen($mbox))
807       $a_mboxes = explode(',', $mbox);
808
809     // let this common function do the main work
810     return $this->_change_subscription($a_mboxes, 'unsubscribe');
811     }
812
813
814   // create a new mailbox on the server and register it in local cache
815   function create_mailbox($name, $subscribe=FALSE)
816     {
817     $result = FALSE;
a95e0e 818     $name_enc = UTF7EncodeString($name);
T 819     $abs_name = $this->_mod_mailbox($name_enc);
4e17e6 820     $a_mailbox_cache = $this->get_cache('mailboxes');
520c36 821     
7902df 822     //if (strlen($this->root_ns))
T 823     //  $abs_name = $this->root_ns.$abs_name;
4e17e6 824
T 825     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 826       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 827
T 828     // update mailboxlist cache
829     if ($result && $subscribe)
a95e0e 830       $this->subscribe($name_enc);
4e17e6 831
7902df 832     return $result ? $name : FALSE;
4e17e6 833     }
T 834
835
836   // set a new name to an existing mailbox
837   function rename_mailbox($mbox, $new_name)
838     {
839     // not implemented yet
840     }
841
842
843   // remove mailboxes from server
844   function delete_mailbox($mbox)
845     {
846     $deleted = FALSE;
847
848     if (is_array($mbox))
849       $a_mboxes = $mbox;
850     else if (is_string($mbox) && strlen($mbox))
851       $a_mboxes = explode(',', $mbox);
852
853     if (is_array($a_mboxes))
854       foreach ($a_mboxes as $mbox)
855         {
856         $mailbox = $this->_mod_mailbox($mbox);
857
858         // unsubscribe mailbox before deleting
859         iil_C_UnSubscribe($this->conn, $mailbox);
860         
861         // send delete command to server
862         $result = iil_C_DeleteFolder($this->conn, $mailbox);
863         if ($result>=0)
864           $deleted = TRUE;
865         }
866
867     // clear mailboxlist cache
868     if ($deleted)
869       $this->clear_cache('mailboxes');
870
871     return $updated;
872     }
873
874
875
876
877   /* --------------------------------
6dc026 878    *   internal caching functions
4e17e6 879    * --------------------------------*/
6dc026 880
T 881
882   function set_caching($set)
883     {
884     if ($set && function_exists('rcube_read_cache'))
885       $this->caching_enabled = TRUE;
886     else
887       $this->caching_enabled = FALSE;
888     }
4e17e6 889
T 890   function get_cache($key)
891     {
892     // read cache
6dc026 893     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 894       {
T 895       $cache_data = rcube_read_cache('IMAP.'.$key);
896       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
897       }
898     
899     return $this->cache[$key];         
900     }
901
902
903   function update_cache($key, $data)
904     {
905     $this->cache[$key] = $data;
906     $this->cache_changed = TRUE;
907     $this->cache_changes[$key] = TRUE;
908     }
909
910
911   function write_cache()
912     {
6dc026 913     if ($this->caching_enabled && $this->cache_changed)
4e17e6 914       {
T 915       foreach ($this->cache as $key => $data)
916         {
917         if ($this->cache_changes[$key])
918           rcube_write_cache('IMAP.'.$key, serialize($data));
919         }
920       }    
921     }
922
923
924   function clear_cache($key=NULL)
925     {
926     if ($key===NULL)
927       {
928       foreach ($this->cache as $key => $data)
929         rcube_clear_cache('IMAP.'.$key);
930
931       $this->cache = array();
932       $this->cache_changed = FALSE;
933       $this->cache_changes = array();
934       }
935     else
936       {
937       rcube_clear_cache('IMAP.'.$key);
938       $this->cache_changes[$key] = FALSE;
939       unset($this->cache[$key]);
940       }
941     }
942
943
944
945   /* --------------------------------
946    *   encoding/decoding functions
947    * --------------------------------*/
948
949   
950   function decode_address_list($input, $max=NULL)
951     {
952     $a = $this->_parse_address_list($input);
953     $out = array();
954
955     if (!is_array($a))
956       return $out;
957
958     $c = count($a);
959     $j = 0;
960
961     foreach ($a as $val)
962       {
963       $j++;
964       $address = $val['address'];
965       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
966       $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
967       
968       $out[$j] = array('name' => $name,
969                        'mailto' => $address,
970                        'string' => $string);
971               
972       if ($max && $j==$max)
973         break;
974       }
975     
976     return $out;
977     }
978
979
980   function decode_header($input)
981     {
982     $out = '';
983
984     $pos = strpos($input, '=?');
985     if ($pos !== false)
986       {
987       $out = substr($input, 0, $pos);
988   
989       $end_cs_pos = strpos($input, "?", $pos+2);
990       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
991       $end_pos = strpos($input, "?=", $end_en_pos+1);
992   
993       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
994       $rest = substr($input, $end_pos+2);
995
996       $out .= $this->decode_mime_string($encstr);
997       $out .= $this->decode_header($rest);
998
999       return $out;
1000       }
1001     else
1002       return $input;
1003     }
1004
1005
1006   function decode_mime_string($str)
1007     {
1008     $a = explode('?', $str);
1009     $count = count($a);
1010
1011     // should be in format "charset?encoding?base64_string"
1012     if ($count >= 3)
1013       {
1014       for ($i=2; $i<$count; $i++)
1015         $rest.=$a[$i];
1016
1017       if (($a[1]=="B")||($a[1]=="b"))
1018         $rest = base64_decode($rest);
1019       else if (($a[1]=="Q")||($a[1]=="q"))
1020         {
1021         $rest = str_replace("_", " ", $rest);
1022         $rest = quoted_printable_decode($rest);
1023         }
1024
1025       return decode_specialchars($rest, $a[0]);
1026       }
1027     else
1028       return $str;    //we dont' know what to do with this  
1029     }
1030
1031
1032   function mime_decode($input, $encoding='7bit')
1033     {
1034     switch (strtolower($encoding))
1035       {
1036       case '7bit':
1037         return $input;
1038         break;
1039       
1040       case 'quoted-printable':
1041         return quoted_printable_decode($input);
1042         break;
1043       
1044       case 'base64':
1045         return base64_decode($input);
1046         break;
1047       
1048       default:
1049         return $input;
1050       }
1051     }
1052
1053
1054   function mime_encode($input, $encoding='7bit')
1055     {
1056     switch ($encoding)
1057       {
1058       case 'quoted-printable':
1059         return quoted_printable_encode($input);
1060         break;
1061
1062       case 'base64':
1063         return base64_encode($input);
1064         break;
1065
1066       default:
1067         return $input;
1068       }
1069     }
1070
1071
1072   // convert body chars according to the ctype_parameters
1073   function charset_decode($body, $ctype_param)
1074     {
a95e0e 1075     if (is_array($ctype_param) && !empty($ctype_param['charset']))
4e17e6 1076       return decode_specialchars($body, $ctype_param['charset']);
T 1077
1078     return $body;
1079     }
1080
1081
1082   /* --------------------------------
1083    *         private methods
1084    * --------------------------------*/
1085
1086
1087   function _mod_mailbox($mbox, $mode='in')
1088     {
f619de 1089     if ((!empty($this->root_ns) && $this->root_ns == $mbox) || ($mbox == 'INBOX' && $mode == 'in'))
7902df 1090       return $mbox;
T 1091
f619de 1092     if (!empty($this->root_dir) && $mode=='in') 
520c36 1093       $mbox = $this->root_dir.$this->delimiter.$mbox;
7902df 1094     else if (strlen($this->root_dir) && $mode=='out') 
4e17e6 1095       $mbox = substr($mbox, strlen($this->root_dir)+1);
T 1096
1097     return $mbox;
1098     }
1099
1100
1101   // sort mailboxes first by default folders and then in alphabethical order
1102   function _sort_mailbox_list($a_folders)
1103     {
1104     $a_out = $a_defaults = array();
1105
1106     // find default folders and skip folders starting with '.'
1107     foreach($a_folders as $i => $folder)
1108       {
1109       if ($folder{0}=='.')
1110           continue;
1111           
1112       if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1113           $a_defaults[$p] = $folder;
1114       else
1115         $a_out[] = $folder;
1116       }
1117
1118     sort($a_out);
1119     ksort($a_defaults);
1120     
1121     return array_merge($a_defaults, $a_out);
1122     }
1123
1124
1125   function _uid2id($uid, $mbox=NULL)
1126     {
1127     if (!$mbox)
1128       $mbox = $this->mailbox;
1129       
1130     if (!isset($this->uid_id_map[$mbox][$uid]))
1131       $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid);
1132
1133     return $this->uid_id_map[$mbox][$uid];
1134     }
1135
1136
1137   // subscribe/unsubscribe a list of mailboxes and update local cache
1138   function _change_subscription($a_mboxes, $mode)
1139     {
1140     $updated = FALSE;
1141     
1142     if (is_array($a_mboxes))
1143       foreach ($a_mboxes as $i => $mbox)
1144         {
1145         $mailbox = $this->_mod_mailbox($mbox);
1146         $a_mboxes[$i] = $mailbox;
1147
1148         if ($mode=='subscribe')
1149           $result = iil_C_Subscribe($this->conn, $mailbox);
1150         else if ($mode=='unsubscribe')
1151           $result = iil_C_UnSubscribe($this->conn, $mailbox);
1152
1153         if ($result>=0)
1154           $updated = TRUE;
1155         }
1156         
1157     // get cached mailbox list    
1158     if ($updated)
1159       {
1160       $a_mailbox_cache = $this->get_cache('mailboxes');
1161       if (!is_array($a_mailbox_cache))
1162         return $updated;
1163
1164       // modify cached list
1165       if ($mode=='subscribe')
1166         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1167       else if ($mode=='unsubscribe')
1168         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1169         
1170       // write mailboxlist to cache
1171       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1172       }
1173
1174     return $updated;
1175     }
1176
1177
1178   // increde/decrese messagecount for a specific mailbox
1179   function _set_messagecount($mbox, $mode, $increment)
1180     {
1181     $a_mailbox_cache = FALSE;
1182     $mailbox = $mbox ? $mbox : $this->mailbox;
1183     $mode = strtoupper($mode);
1184
1185     $a_mailbox_cache = $this->get_cache('messagecount');
1186     
1187     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1188       return FALSE;
1189     
1190     // add incremental value to messagecount
1191     $a_mailbox_cache[$mailbox][$mode] += $increment;
1192
1193     // write back to cache
1194     $this->update_cache('messagecount', $a_mailbox_cache);
1195     
1196     return TRUE;
1197     }
1198
1199
1200   // remove messagecount of a specific mailbox from cache
1201   function _clear_messagecount($mbox='')
1202     {
1203     $a_mailbox_cache = FALSE;
1204     $mailbox = $mbox ? $mbox : $this->mailbox;
1205
1206     $a_mailbox_cache = $this->get_cache('messagecount');
1207
1208     if (is_array($a_mailbox_cache[$mailbox]))
1209       {
1210       unset($a_mailbox_cache[$mailbox]);
1211       $this->update_cache('messagecount', $a_mailbox_cache);
1212       }
1213     }
1214
1215
1216   function _parse_address_list($str)
1217     {
1218     $a = $this->_explode_quoted_string(',', $str);
1219     $result = array();
1220
1221     foreach ($a as $key => $val)
1222       {
1223       $val = str_replace("\"<", "\" <", $val);
1224       $sub_a = $this->_explode_quoted_string(' ', $val);
1225       
1226       foreach ($sub_a as $k => $v)
1227         {
1228         if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
1229           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
1230         else
1231           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
1232         }
1233         
1234       if (empty($result[$key]['name']))
1235         $result[$key]['name'] = $result[$key]['address'];
1236         
1237       $result[$key]['name'] = $this->decode_header($result[$key]['name']);
1238       }
1239     
1240     return $result;
1241     }
1242
1243
1244   function _explode_quoted_string($delimiter, $string)
1245     {
1246     $quotes = explode("\"", $string);
1247     foreach ($quotes as $key => $val)
1248       if (($key % 2) == 1)
1249         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
1250         
1251     $string = implode("\"", $quotes);
1252
1253     $result = explode($delimiter, $string);
1254     foreach ($result as $key => $val) 
1255       $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
1256     
1257     return $result;
1258     }
1259   }
1260
1261
1262
1263
1264
1265 function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
1266   {
1267   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1268   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
1269   $eol = "\r\n";
1270   $escape = "=";
1271   $output = "";
1272
1273   while( list(, $line) = each($lines))
1274     {
1275     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
1276     $linlen = strlen($line);
1277     $newline = "";
1278     for($i = 0; $i < $linlen; $i++)
1279       {
1280       $c = substr( $line, $i, 1 );
1281       $dec = ord( $c );
1282       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
1283         {
1284         $c = "=2E";
1285         }
1286       if ( $dec == 32 )
1287         {
1288         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
1289           {
1290           $c = "=20";
1291           }
1292         else if ( $space_conv )
1293           {
1294           $c = "=20";
1295           }
1296         }
1297       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
1298         {
1299         $h2 = floor($dec/16);
1300         $h1 = floor($dec%16);
1301         $c = $escape.$hex["$h2"].$hex["$h1"];
1302         }
1303          
1304       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
1305         {
1306         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1307         $newline = "";
1308         // check if newline first character will be point or not
1309         if ( $dec == 46 )
1310           {
1311           $c = "=2E";
1312           }
1313         }
1314       $newline .= $c;
1315       } // end of for
1316     $output .= $newline.$eol;
1317     } // end of while
1318
1319   return trim($output);
1320   }
1321
1322 ?>