thomascube
2005-11-18 fbf77b4493f1b77c99751d8a86365c712ae3fb1b
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     {
4b0f65 991     return $this->decode_mime_string($input);
T 992     }
993     
994     
995   function decode_mime_string($input)
996     {
4e17e6 997     $out = '';
T 998
999     $pos = strpos($input, '=?');
1000     if ($pos !== false)
1001       {
1002       $out = substr($input, 0, $pos);
1003   
1004       $end_cs_pos = strpos($input, "?", $pos+2);
1005       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1006       $end_pos = strpos($input, "?=", $end_en_pos+1);
1007   
1008       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1009       $rest = substr($input, $end_pos+2);
1010
4b0f65 1011       $out .= rcube_imap::_decode_mime_string_part($encstr);
T 1012       $out .= rcube_imap::decode_mime_string($rest);
4e17e6 1013
T 1014       return $out;
1015       }
1016     else
1017       return $input;
1018     }
1019
1020
4b0f65 1021   function _decode_mime_string_part($str)
4e17e6 1022     {
T 1023     $a = explode('?', $str);
1024     $count = count($a);
1025
1026     // should be in format "charset?encoding?base64_string"
1027     if ($count >= 3)
1028       {
1029       for ($i=2; $i<$count; $i++)
1030         $rest.=$a[$i];
1031
1032       if (($a[1]=="B")||($a[1]=="b"))
1033         $rest = base64_decode($rest);
1034       else if (($a[1]=="Q")||($a[1]=="q"))
1035         {
1036         $rest = str_replace("_", " ", $rest);
1037         $rest = quoted_printable_decode($rest);
1038         }
1039
1040       return decode_specialchars($rest, $a[0]);
1041       }
1042     else
1043       return $str;    //we dont' know what to do with this  
1044     }
1045
1046
1047   function mime_decode($input, $encoding='7bit')
1048     {
1049     switch (strtolower($encoding))
1050       {
1051       case '7bit':
1052         return $input;
1053         break;
1054       
1055       case 'quoted-printable':
1056         return quoted_printable_decode($input);
1057         break;
1058       
1059       case 'base64':
1060         return base64_decode($input);
1061         break;
1062       
1063       default:
1064         return $input;
1065       }
1066     }
1067
1068
1069   function mime_encode($input, $encoding='7bit')
1070     {
1071     switch ($encoding)
1072       {
1073       case 'quoted-printable':
1074         return quoted_printable_encode($input);
1075         break;
1076
1077       case 'base64':
1078         return base64_encode($input);
1079         break;
1080
1081       default:
1082         return $input;
1083       }
1084     }
1085
1086
1087   // convert body chars according to the ctype_parameters
1088   function charset_decode($body, $ctype_param)
1089     {
a95e0e 1090     if (is_array($ctype_param) && !empty($ctype_param['charset']))
4e17e6 1091       return decode_specialchars($body, $ctype_param['charset']);
T 1092
1093     return $body;
1094     }
1095
1096
1097   /* --------------------------------
1098    *         private methods
1099    * --------------------------------*/
1100
1101
1102   function _mod_mailbox($mbox, $mode='in')
1103     {
f619de 1104     if ((!empty($this->root_ns) && $this->root_ns == $mbox) || ($mbox == 'INBOX' && $mode == 'in'))
7902df 1105       return $mbox;
T 1106
f619de 1107     if (!empty($this->root_dir) && $mode=='in') 
520c36 1108       $mbox = $this->root_dir.$this->delimiter.$mbox;
7902df 1109     else if (strlen($this->root_dir) && $mode=='out') 
4e17e6 1110       $mbox = substr($mbox, strlen($this->root_dir)+1);
T 1111
1112     return $mbox;
1113     }
1114
1115
1116   // sort mailboxes first by default folders and then in alphabethical order
1117   function _sort_mailbox_list($a_folders)
1118     {
1119     $a_out = $a_defaults = array();
1120
1121     // find default folders and skip folders starting with '.'
1122     foreach($a_folders as $i => $folder)
1123       {
1124       if ($folder{0}=='.')
1125           continue;
1126           
1127       if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1128           $a_defaults[$p] = $folder;
1129       else
1130         $a_out[] = $folder;
1131       }
1132
1133     sort($a_out);
1134     ksort($a_defaults);
1135     
1136     return array_merge($a_defaults, $a_out);
1137     }
1138
1139
1140   function _uid2id($uid, $mbox=NULL)
1141     {
1142     if (!$mbox)
1143       $mbox = $this->mailbox;
1144       
1145     if (!isset($this->uid_id_map[$mbox][$uid]))
1146       $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid);
1147
1148     return $this->uid_id_map[$mbox][$uid];
1149     }
1150
1151
1152   // subscribe/unsubscribe a list of mailboxes and update local cache
1153   function _change_subscription($a_mboxes, $mode)
1154     {
1155     $updated = FALSE;
1156     
1157     if (is_array($a_mboxes))
1158       foreach ($a_mboxes as $i => $mbox)
1159         {
1160         $mailbox = $this->_mod_mailbox($mbox);
1161         $a_mboxes[$i] = $mailbox;
1162
1163         if ($mode=='subscribe')
1164           $result = iil_C_Subscribe($this->conn, $mailbox);
1165         else if ($mode=='unsubscribe')
1166           $result = iil_C_UnSubscribe($this->conn, $mailbox);
1167
1168         if ($result>=0)
1169           $updated = TRUE;
1170         }
1171         
1172     // get cached mailbox list    
1173     if ($updated)
1174       {
1175       $a_mailbox_cache = $this->get_cache('mailboxes');
1176       if (!is_array($a_mailbox_cache))
1177         return $updated;
1178
1179       // modify cached list
1180       if ($mode=='subscribe')
1181         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1182       else if ($mode=='unsubscribe')
1183         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1184         
1185       // write mailboxlist to cache
1186       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1187       }
1188
1189     return $updated;
1190     }
1191
1192
1193   // increde/decrese messagecount for a specific mailbox
1194   function _set_messagecount($mbox, $mode, $increment)
1195     {
1196     $a_mailbox_cache = FALSE;
1197     $mailbox = $mbox ? $mbox : $this->mailbox;
1198     $mode = strtoupper($mode);
1199
1200     $a_mailbox_cache = $this->get_cache('messagecount');
1201     
1202     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1203       return FALSE;
1204     
1205     // add incremental value to messagecount
1206     $a_mailbox_cache[$mailbox][$mode] += $increment;
1207
1208     // write back to cache
1209     $this->update_cache('messagecount', $a_mailbox_cache);
1210     
1211     return TRUE;
1212     }
1213
1214
1215   // remove messagecount of a specific mailbox from cache
1216   function _clear_messagecount($mbox='')
1217     {
1218     $a_mailbox_cache = FALSE;
1219     $mailbox = $mbox ? $mbox : $this->mailbox;
1220
1221     $a_mailbox_cache = $this->get_cache('messagecount');
1222
1223     if (is_array($a_mailbox_cache[$mailbox]))
1224       {
1225       unset($a_mailbox_cache[$mailbox]);
1226       $this->update_cache('messagecount', $a_mailbox_cache);
1227       }
1228     }
1229
1230
1231   function _parse_address_list($str)
1232     {
1233     $a = $this->_explode_quoted_string(',', $str);
1234     $result = array();
1235
1236     foreach ($a as $key => $val)
1237       {
1238       $val = str_replace("\"<", "\" <", $val);
1239       $sub_a = $this->_explode_quoted_string(' ', $val);
1240       
1241       foreach ($sub_a as $k => $v)
1242         {
1243         if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
1244           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
1245         else
1246           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
1247         }
1248         
1249       if (empty($result[$key]['name']))
1250         $result[$key]['name'] = $result[$key]['address'];
1251         
1252       $result[$key]['name'] = $this->decode_header($result[$key]['name']);
1253       }
1254     
1255     return $result;
1256     }
1257
1258
1259   function _explode_quoted_string($delimiter, $string)
1260     {
1261     $quotes = explode("\"", $string);
1262     foreach ($quotes as $key => $val)
1263       if (($key % 2) == 1)
1264         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
1265         
1266     $string = implode("\"", $quotes);
1267
1268     $result = explode($delimiter, $string);
1269     foreach ($result as $key => $val) 
1270       $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
1271     
1272     return $result;
1273     }
1274   }
1275
1276
1277
1278
1279
1280 function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
1281   {
1282   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1283   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
1284   $eol = "\r\n";
1285   $escape = "=";
1286   $output = "";
1287
1288   while( list(, $line) = each($lines))
1289     {
1290     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
1291     $linlen = strlen($line);
1292     $newline = "";
1293     for($i = 0; $i < $linlen; $i++)
1294       {
1295       $c = substr( $line, $i, 1 );
1296       $dec = ord( $c );
1297       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
1298         {
1299         $c = "=2E";
1300         }
1301       if ( $dec == 32 )
1302         {
1303         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
1304           {
1305           $c = "=20";
1306           }
1307         else if ( $space_conv )
1308           {
1309           $c = "=20";
1310           }
1311         }
1312       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
1313         {
1314         $h2 = floor($dec/16);
1315         $h1 = floor($dec%16);
1316         $c = $escape.$hex["$h2"].$hex["$h1"];
1317         }
1318          
1319       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
1320         {
1321         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1322         $newline = "";
1323         // check if newline first character will be point or not
1324         if ( $dec == 46 )
1325           {
1326           $c = "=2E";
1327           }
1328         }
1329       $newline .= $c;
1330       } // end of for
1331     $output .= $newline.$eol;
1332     } // end of while
1333
1334   return trim($output);
1335   }
1336
1337 ?>