thomascube
2005-11-08 583f1c8d80c42195d0ee41f30a885e13d777b79f
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);
b595c9 467     $headers_count = count($a_headers);
8c0328 468
17fc71 469     // free memory
S 470     unset($a_msg_headers);
471     
4e17e6 472     // write headers list to cache
T 473     if (!$headers_cached)
17fc71 474       $this->update_cache($mailbox.'.msg', $a_headers);
b595c9 475       
T 476     // update message count cache
477     $a_mailbox_cache = $this->get_cache('messagecount');
478     if (isset($a_mailbox_cache[$mailbox]['ALL']) && $a_mailbox_cache[$mailbox]['ALL'] != $headers_count)
479       {
480       $a_mailbox_cache[$mailbox]['ALL'] = (int)$headers_count;
481       $this->update_cache('messagecount', $a_mailbox_cache);
482       }
4e17e6 483
8c0328 484     if (empty($a_headers))
S 485         return array();
486         
4e17e6 487     // return complete list of messages
T 488     if (strtolower($page)=='all')
17fc71 489       return $a_headers;
b076a4 490
4e17e6 491     $start_msg = ($this->list_page-1) * $this->page_size;
17fc71 492     return array_slice($a_headers, $start_msg, $this->page_size);
4e17e6 493     }
7902df 494   
4e17e6 495
T 496   // return sorted array of message UIDs
497   function message_index($mbox='', $sort_field='date', $sort_order='DESC')
498     {
499     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
500     $a_out = array();
501
502     // get array of message headers
503     $a_headers = $this->_list_headers($mailbox, 'all', $sort_field, $sort_order);
504
505     if (is_array($a_headers))
506       foreach ($a_headers as $header)
507         $a_out[] = $header->uid;
508
509     return $a_out;
510     }
511
512
513   function sync_header_index($mbox=NULL)
514     {
515     
516     }
517
518
519   function search($mbox='', $criteria='ALL')
520     {
521     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
522     $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
523     return $a_messages;
524     }
525
526
527   function get_headers($uid, $mbox=NULL)
528     {
529     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
530     
531     // get cached headers
532     $a_msg_headers = $this->get_cache($mailbox.'.msg');
533     
534     // return cached header
535     if ($a_msg_headers[$uid])
536       return $a_msg_headers[$uid];
520c36 537
4e17e6 538     $msg_id = $this->_uid2id($uid);
T 539     $header = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
520c36 540
4e17e6 541     // write headers cache
T 542     $a_msg_headers[$uid] = $header;
543     $this->update_cache($mailbox.'.msg', $a_msg_headers);
544
545     return $header;
546     }
547
548
549   function get_body($uid, $part=1)
550     {
551     if (!($msg_id = $this->_uid2id($uid)))
552       return FALSE;
553
554     $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
555     $structure = iml_GetRawStructureArray($structure_str);
556     $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
557
558     $encoding = iml_GetPartEncodingCode($structure, $part);
559     
560     if ($encoding==3) $body = $this->mime_decode($body, 'base64');
561     else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
562
563     return $body;
564     }
565
566
567   function get_raw_body($uid)
568     {
569     if (!($msg_id = $this->_uid2id($uid)))
570       return FALSE;
571
572     $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
573     $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
574
575     return $body;    
576     }
577
578
579   // set message flag to one or several messages
580   // possible flgs are: SEEN, DELETED, RECENT, ANSWERED, DRAFT
581   function set_flag($uids, $flag)
582     {
583     $flag = strtoupper($flag);
584     $msg_ids = array();
585     if (!is_array($uids))
586       $uids = array($uids);
587       
588     foreach ($uids as $uid)
589       $msg_ids[] = $this->_uid2id($uid);
590       
591     if ($flag=='UNSEEN')
592       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', $msg_ids));
593     else
594       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', $msg_ids), $flag);
595
596     // reload message headers if cached
597     $cache_key = $this->mailbox.'.msg';
520c36 598     if ($this->caching_enabled && $result && ($a_cached_headers = $this->get_cache($cache_key)))
4e17e6 599       {
9fee0e 600       // close and re-open connection
520c36 601       $this->reconnect();
T 602
4e17e6 603       foreach ($uids as $uid)
T 604         {
605         if (isset($a_cached_headers[$uid]))
606           {
607           unset($this->cache[$cache_key][$uid]);
608           $this->get_headers($uid);
609           }
610         }
611       }
612
613     // set nr of messages that were flaged
614     $count = sizeof($msg_ids);
615
616     // clear message count cache
617     if ($result && $flag=='SEEN')
618       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
619     else if ($result && $flag=='UNSEEN')
620       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
621     else if ($result && $flag=='DELETED')
622       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
623
624     return $result;
625     }
626
627
628   // append a mail message (source) to a specific mailbox
629   function save_message($mbox, $message)
630     {
631     $mailbox = $this->_mod_mailbox($mbox);
632
633     // make shure mailbox exists
634     if (in_array($mailbox, $this->_list_mailboxes()))
635       $saved = iil_C_Append($this->conn, $mailbox, $message);
636     
637     if ($saved)
638       {
639       // increase messagecount of the target mailbox
640       $this->_set_messagecount($mailbox, 'ALL', 1);
641       }
642           
643     return $saved;
644     }
645
646
647   // move a message from one mailbox to another
648   function move_message($uids, $to_mbox, $from_mbox='')
649     {
650     $to_mbox = $this->_mod_mailbox($to_mbox);
651     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
652
653     // make shure mailbox exists
654     if (!in_array($to_mbox, $this->_list_mailboxes()))
655       return FALSE;
656     
657     // convert the list of uids to array
658     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
659     
660     // exit if no message uids are specified
661     if (!is_array($a_uids))
662       return false;
520c36 663
4e17e6 664     // convert uids to message ids
T 665     $a_mids = array();
666     foreach ($a_uids as $uid)
667       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 668
4e17e6 669     $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 670     
671     // send expunge command in order to have the moved message
672     // really deleted from the source mailbox
673     if ($moved)
674       {
675       $this->expunge($from_mbox, FALSE);
676       $this->clear_cache($to_mbox.'.msg');
677       $this->_clear_messagecount($from_mbox);
678       $this->_clear_messagecount($to_mbox);
679       }
680
681     // update cached message headers
682     $cache_key = $from_mbox.'.msg';
683     if ($moved && ($a_cached_headers = $this->get_cache($cache_key)))
684       {
685       foreach ($a_uids as $uid)
686         unset($a_cached_headers[$uid]);
687
688       $this->update_cache($cache_key, $a_cached_headers);
689       }
690
691     return $moved;
692     }
693
694
695   // mark messages as deleted and expunge mailbox
696   function delete_message($uids, $mbox='')
697     {
698     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
699
700     // convert the list of uids to array
701     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
702     
703     // exit if no message uids are specified
704     if (!is_array($a_uids))
705       return false;
706
707
708     // convert uids to message ids
709     $a_mids = array();
710     foreach ($a_uids as $uid)
711       $a_mids[] = $this->_uid2id($uid, $mailbox);
712         
713     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
714     
715     // send expunge command in order to have the deleted message
716     // really deleted from the mailbox
717     if ($deleted)
718       {
719       $this->expunge($mailbox, FALSE);
720       $this->_clear_messagecount($mailbox);
721       }
722
723     // remove deleted messages from cache
724     if ($deleted && ($a_cached_headers = $this->get_cache($mailbox.'.msg')))
725       {
726       foreach ($a_uids as $uid)
727         unset($a_cached_headers[$uid]);
728
729       $this->update_cache($mailbox.'.msg', $a_cached_headers);
730       }
731
732     return $deleted;
733     }
734
735
a95e0e 736   // clear all messages in a specific mailbox
T 737   function clear_mailbox($mbox)
738     {
739     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
740     $msg_count = $this->_messagecount($mailbox, 'ALL');
741     
742     if ($msg_count>0)
743       return iil_C_ClearFolder($this->conn, $mailbox);
744     else
745       return 0;
746     }
747
748
4e17e6 749   // send IMAP expunge command and clear cache
T 750   function expunge($mbox='', $clear_cache=TRUE)
751     {
752     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
753     
754     $result = iil_C_Expunge($this->conn, $mailbox);
755
756     if ($result>=0 && $clear_cache)
757       {
758       $this->clear_cache($mailbox.'.msg');
759       $this->_clear_messagecount($mailbox);
760       }
761       
762     return $result;
763     }
764
765
766
767   /* --------------------------------
768    *        folder managment
769    * --------------------------------*/
770
771
772   // return an array with all folders available in IMAP server
773   function list_unsubscribed($root='')
774     {
775     static $sa_unsubscribed;
776     
777     if (is_array($sa_unsubscribed))
778       return $sa_unsubscribed;
779       
780     // retrieve list of folders from IMAP server
781     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
782
783     // modify names with root dir
784     foreach ($a_mboxes as $mbox)
785       {
786       $name = $this->_mod_mailbox($mbox, 'out');
787       if (strlen($name))
788         $a_folders[] = $name;
789       }
790
791     // filter folders and sort them
792     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
793     return $sa_unsubscribed;
794     }
795
796
797   // subscribe to a specific mailbox(es)
798   function subscribe($mbox, $mode='subscribe')
799     {
800     if (is_array($mbox))
801       $a_mboxes = $mbox;
802     else if (is_string($mbox) && strlen($mbox))
803       $a_mboxes = explode(',', $mbox);
804     
805     // let this common function do the main work
806     return $this->_change_subscription($a_mboxes, 'subscribe');
807     }
808
809
810   // unsubscribe mailboxes
811   function unsubscribe($mbox)
812     {
813     if (is_array($mbox))
814       $a_mboxes = $mbox;
815     else if (is_string($mbox) && strlen($mbox))
816       $a_mboxes = explode(',', $mbox);
817
818     // let this common function do the main work
819     return $this->_change_subscription($a_mboxes, 'unsubscribe');
820     }
821
822
823   // create a new mailbox on the server and register it in local cache
824   function create_mailbox($name, $subscribe=FALSE)
825     {
826     $result = FALSE;
a95e0e 827     $name_enc = UTF7EncodeString($name);
T 828     $abs_name = $this->_mod_mailbox($name_enc);
4e17e6 829     $a_mailbox_cache = $this->get_cache('mailboxes');
520c36 830     
7902df 831     //if (strlen($this->root_ns))
T 832     //  $abs_name = $this->root_ns.$abs_name;
4e17e6 833
T 834     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 835       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 836
T 837     // update mailboxlist cache
838     if ($result && $subscribe)
a95e0e 839       $this->subscribe($name_enc);
4e17e6 840
7902df 841     return $result ? $name : FALSE;
4e17e6 842     }
T 843
844
845   // set a new name to an existing mailbox
846   function rename_mailbox($mbox, $new_name)
847     {
848     // not implemented yet
849     }
850
851
852   // remove mailboxes from server
853   function delete_mailbox($mbox)
854     {
855     $deleted = FALSE;
856
857     if (is_array($mbox))
858       $a_mboxes = $mbox;
859     else if (is_string($mbox) && strlen($mbox))
860       $a_mboxes = explode(',', $mbox);
861
862     if (is_array($a_mboxes))
863       foreach ($a_mboxes as $mbox)
864         {
865         $mailbox = $this->_mod_mailbox($mbox);
866
867         // unsubscribe mailbox before deleting
868         iil_C_UnSubscribe($this->conn, $mailbox);
869         
870         // send delete command to server
871         $result = iil_C_DeleteFolder($this->conn, $mailbox);
872         if ($result>=0)
873           $deleted = TRUE;
874         }
875
876     // clear mailboxlist cache
877     if ($deleted)
878       $this->clear_cache('mailboxes');
879
880     return $updated;
881     }
882
883
884
885
886   /* --------------------------------
6dc026 887    *   internal caching functions
4e17e6 888    * --------------------------------*/
6dc026 889
T 890
891   function set_caching($set)
892     {
893     if ($set && function_exists('rcube_read_cache'))
894       $this->caching_enabled = TRUE;
895     else
896       $this->caching_enabled = FALSE;
897     }
4e17e6 898
T 899   function get_cache($key)
900     {
901     // read cache
6dc026 902     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 903       {
T 904       $cache_data = rcube_read_cache('IMAP.'.$key);
905       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
906       }
907     
908     return $this->cache[$key];         
909     }
910
911
912   function update_cache($key, $data)
913     {
914     $this->cache[$key] = $data;
915     $this->cache_changed = TRUE;
916     $this->cache_changes[$key] = TRUE;
917     }
918
919
920   function write_cache()
921     {
6dc026 922     if ($this->caching_enabled && $this->cache_changed)
4e17e6 923       {
T 924       foreach ($this->cache as $key => $data)
925         {
926         if ($this->cache_changes[$key])
927           rcube_write_cache('IMAP.'.$key, serialize($data));
928         }
929       }    
930     }
931
932
933   function clear_cache($key=NULL)
934     {
935     if ($key===NULL)
936       {
937       foreach ($this->cache as $key => $data)
938         rcube_clear_cache('IMAP.'.$key);
939
940       $this->cache = array();
941       $this->cache_changed = FALSE;
942       $this->cache_changes = array();
943       }
944     else
945       {
946       rcube_clear_cache('IMAP.'.$key);
947       $this->cache_changes[$key] = FALSE;
948       unset($this->cache[$key]);
949       }
950     }
951
952
953
954   /* --------------------------------
955    *   encoding/decoding functions
956    * --------------------------------*/
957
958   
959   function decode_address_list($input, $max=NULL)
960     {
961     $a = $this->_parse_address_list($input);
962     $out = array();
963
964     if (!is_array($a))
965       return $out;
966
967     $c = count($a);
968     $j = 0;
969
970     foreach ($a as $val)
971       {
972       $j++;
973       $address = $val['address'];
974       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
975       $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
976       
977       $out[$j] = array('name' => $name,
978                        'mailto' => $address,
979                        'string' => $string);
980               
981       if ($max && $j==$max)
982         break;
983       }
984     
985     return $out;
986     }
987
988
989   function decode_header($input)
990     {
991     $out = '';
992
993     $pos = strpos($input, '=?');
994     if ($pos !== false)
995       {
996       $out = substr($input, 0, $pos);
997   
998       $end_cs_pos = strpos($input, "?", $pos+2);
999       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1000       $end_pos = strpos($input, "?=", $end_en_pos+1);
1001   
1002       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1003       $rest = substr($input, $end_pos+2);
1004
1005       $out .= $this->decode_mime_string($encstr);
1006       $out .= $this->decode_header($rest);
1007
1008       return $out;
1009       }
1010     else
1011       return $input;
1012     }
1013
1014
1015   function decode_mime_string($str)
1016     {
1017     $a = explode('?', $str);
1018     $count = count($a);
1019
1020     // should be in format "charset?encoding?base64_string"
1021     if ($count >= 3)
1022       {
1023       for ($i=2; $i<$count; $i++)
1024         $rest.=$a[$i];
1025
1026       if (($a[1]=="B")||($a[1]=="b"))
1027         $rest = base64_decode($rest);
1028       else if (($a[1]=="Q")||($a[1]=="q"))
1029         {
1030         $rest = str_replace("_", " ", $rest);
1031         $rest = quoted_printable_decode($rest);
1032         }
1033
1034       return decode_specialchars($rest, $a[0]);
1035       }
1036     else
1037       return $str;    //we dont' know what to do with this  
1038     }
1039
1040
1041   function mime_decode($input, $encoding='7bit')
1042     {
1043     switch (strtolower($encoding))
1044       {
1045       case '7bit':
1046         return $input;
1047         break;
1048       
1049       case 'quoted-printable':
1050         return quoted_printable_decode($input);
1051         break;
1052       
1053       case 'base64':
1054         return base64_decode($input);
1055         break;
1056       
1057       default:
1058         return $input;
1059       }
1060     }
1061
1062
1063   function mime_encode($input, $encoding='7bit')
1064     {
1065     switch ($encoding)
1066       {
1067       case 'quoted-printable':
1068         return quoted_printable_encode($input);
1069         break;
1070
1071       case 'base64':
1072         return base64_encode($input);
1073         break;
1074
1075       default:
1076         return $input;
1077       }
1078     }
1079
1080
1081   // convert body chars according to the ctype_parameters
1082   function charset_decode($body, $ctype_param)
1083     {
a95e0e 1084     if (is_array($ctype_param) && !empty($ctype_param['charset']))
4e17e6 1085       return decode_specialchars($body, $ctype_param['charset']);
T 1086
1087     return $body;
1088     }
1089
1090
1091   /* --------------------------------
1092    *         private methods
1093    * --------------------------------*/
1094
1095
1096   function _mod_mailbox($mbox, $mode='in')
1097     {
f619de 1098     if ((!empty($this->root_ns) && $this->root_ns == $mbox) || ($mbox == 'INBOX' && $mode == 'in'))
7902df 1099       return $mbox;
T 1100
f619de 1101     if (!empty($this->root_dir) && $mode=='in') 
520c36 1102       $mbox = $this->root_dir.$this->delimiter.$mbox;
7902df 1103     else if (strlen($this->root_dir) && $mode=='out') 
4e17e6 1104       $mbox = substr($mbox, strlen($this->root_dir)+1);
T 1105
1106     return $mbox;
1107     }
1108
1109
1110   // sort mailboxes first by default folders and then in alphabethical order
1111   function _sort_mailbox_list($a_folders)
1112     {
1113     $a_out = $a_defaults = array();
1114
1115     // find default folders and skip folders starting with '.'
1116     foreach($a_folders as $i => $folder)
1117       {
1118       if ($folder{0}=='.')
1119           continue;
1120           
1121       if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1122           $a_defaults[$p] = $folder;
1123       else
1124         $a_out[] = $folder;
1125       }
1126
1127     sort($a_out);
1128     ksort($a_defaults);
1129     
1130     return array_merge($a_defaults, $a_out);
1131     }
1132
1133
1134   function _uid2id($uid, $mbox=NULL)
1135     {
1136     if (!$mbox)
1137       $mbox = $this->mailbox;
1138       
1139     if (!isset($this->uid_id_map[$mbox][$uid]))
1140       $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid);
1141
1142     return $this->uid_id_map[$mbox][$uid];
1143     }
1144
1145
1146   // subscribe/unsubscribe a list of mailboxes and update local cache
1147   function _change_subscription($a_mboxes, $mode)
1148     {
1149     $updated = FALSE;
1150     
1151     if (is_array($a_mboxes))
1152       foreach ($a_mboxes as $i => $mbox)
1153         {
1154         $mailbox = $this->_mod_mailbox($mbox);
1155         $a_mboxes[$i] = $mailbox;
1156
1157         if ($mode=='subscribe')
1158           $result = iil_C_Subscribe($this->conn, $mailbox);
1159         else if ($mode=='unsubscribe')
1160           $result = iil_C_UnSubscribe($this->conn, $mailbox);
1161
1162         if ($result>=0)
1163           $updated = TRUE;
1164         }
1165         
1166     // get cached mailbox list    
1167     if ($updated)
1168       {
1169       $a_mailbox_cache = $this->get_cache('mailboxes');
1170       if (!is_array($a_mailbox_cache))
1171         return $updated;
1172
1173       // modify cached list
1174       if ($mode=='subscribe')
1175         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1176       else if ($mode=='unsubscribe')
1177         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1178         
1179       // write mailboxlist to cache
1180       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1181       }
1182
1183     return $updated;
1184     }
1185
1186
1187   // increde/decrese messagecount for a specific mailbox
1188   function _set_messagecount($mbox, $mode, $increment)
1189     {
1190     $a_mailbox_cache = FALSE;
1191     $mailbox = $mbox ? $mbox : $this->mailbox;
1192     $mode = strtoupper($mode);
1193
1194     $a_mailbox_cache = $this->get_cache('messagecount');
1195     
1196     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1197       return FALSE;
1198     
1199     // add incremental value to messagecount
1200     $a_mailbox_cache[$mailbox][$mode] += $increment;
1201
1202     // write back to cache
1203     $this->update_cache('messagecount', $a_mailbox_cache);
1204     
1205     return TRUE;
1206     }
1207
1208
1209   // remove messagecount of a specific mailbox from cache
1210   function _clear_messagecount($mbox='')
1211     {
1212     $a_mailbox_cache = FALSE;
1213     $mailbox = $mbox ? $mbox : $this->mailbox;
1214
1215     $a_mailbox_cache = $this->get_cache('messagecount');
1216
1217     if (is_array($a_mailbox_cache[$mailbox]))
1218       {
1219       unset($a_mailbox_cache[$mailbox]);
1220       $this->update_cache('messagecount', $a_mailbox_cache);
1221       }
1222     }
1223
1224
1225   function _parse_address_list($str)
1226     {
1227     $a = $this->_explode_quoted_string(',', $str);
1228     $result = array();
1229
1230     foreach ($a as $key => $val)
1231       {
1232       $val = str_replace("\"<", "\" <", $val);
1233       $sub_a = $this->_explode_quoted_string(' ', $val);
1234       
1235       foreach ($sub_a as $k => $v)
1236         {
1237         if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
1238           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
1239         else
1240           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
1241         }
1242         
1243       if (empty($result[$key]['name']))
1244         $result[$key]['name'] = $result[$key]['address'];
1245         
1246       $result[$key]['name'] = $this->decode_header($result[$key]['name']);
1247       }
1248     
1249     return $result;
1250     }
1251
1252
1253   function _explode_quoted_string($delimiter, $string)
1254     {
1255     $quotes = explode("\"", $string);
1256     foreach ($quotes as $key => $val)
1257       if (($key % 2) == 1)
1258         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
1259         
1260     $string = implode("\"", $quotes);
1261
1262     $result = explode($delimiter, $string);
1263     foreach ($result as $key => $val) 
1264       $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
1265     
1266     return $result;
1267     }
1268   }
1269
1270
1271
1272
1273
1274 function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
1275   {
1276   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1277   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
1278   $eol = "\r\n";
1279   $escape = "=";
1280   $output = "";
1281
1282   while( list(, $line) = each($lines))
1283     {
1284     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
1285     $linlen = strlen($line);
1286     $newline = "";
1287     for($i = 0; $i < $linlen; $i++)
1288       {
1289       $c = substr( $line, $i, 1 );
1290       $dec = ord( $c );
1291       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
1292         {
1293         $c = "=2E";
1294         }
1295       if ( $dec == 32 )
1296         {
1297         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
1298           {
1299           $c = "=20";
1300           }
1301         else if ( $space_conv )
1302           {
1303           $c = "=20";
1304           }
1305         }
1306       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
1307         {
1308         $h2 = floor($dec/16);
1309         $h1 = floor($dec%16);
1310         $c = $escape.$hex["$h2"].$hex["$h1"];
1311         }
1312          
1313       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
1314         {
1315         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1316         $newline = "";
1317         // check if newline first character will be point or not
1318         if ( $dec == 46 )
1319           {
1320           $c = "=2E";
1321           }
1322         }
1323       $newline .= $c;
1324       } // end of for
1325     $output .= $newline.$eol;
1326     } // end of while
1327
1328   return trim($output);
1329   }
1330
1331 ?>