thomascube
2005-12-14 749b07c78a29b03c63812c3ec3630b16db4baa8f
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   {
1cded8 31   var $db;
4e17e6 32   var $conn;
520c36 33   var $root_ns = '';
4e17e6 34   var $root_dir = '';
T 35   var $mailbox = 'INBOX';
36   var $list_page = 1;
37   var $page_size = 10;
31b2ce 38   var $sort_field = 'date';
T 39   var $sort_order = 'DESC';
597170 40   var $delimiter = NULL;
6dc026 41   var $caching_enabled = FALSE;
4e17e6 42   var $default_folders = array('inbox', 'drafts', 'sent', 'junk', 'trash');
T 43   var $cache = array();
1cded8 44   var $cache_keys = array();  
4e17e6 45   var $cache_changes = array();  
T 46   var $uid_id_map = array();
47   var $msg_headers = array();
1cded8 48   var $capabilities = array();
4e17e6 49
T 50
51   // PHP 5 constructor
1cded8 52   function __construct($db_conn)
4e17e6 53     {
1cded8 54     $this->db = $db_conn;    
4e17e6 55     }
T 56
57   // PHP 4 compatibility
1cded8 58   function rcube_imap($db_conn)
4e17e6 59     {
1cded8 60     $this->__construct($db_conn);
4e17e6 61     }
T 62
63
42b113 64   function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
4e17e6 65     {
7902df 66     global $ICL_SSL, $ICL_PORT, $CONFIG;
42b113 67     
T 68     // check for Open-SSL support in PHP build
69     if ($use_ssl && in_array('openssl', get_loaded_extensions()))
70       $ICL_SSL = TRUE;
520c36 71     else if ($use_ssl)
T 72       {
73       raise_error(array('code' => 403,
74                         'type' => 'imap',
7902df 75                         'file' => __FILE__,
520c36 76                         'message' => 'Open SSL not available;'), TRUE, FALSE);
T 77       $port = 143;
78       }
4e17e6 79
T 80     $ICL_PORT = $port;
42b113 81     $this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
4e17e6 82     $this->host = $host;
T 83     $this->user = $user;
84     $this->pass = $pass;
520c36 85     $this->port = $port;
T 86     $this->ssl = $use_ssl;
42b113 87     
520c36 88     // print trace mesages
42b113 89     if ($this->conn && ($CONFIG['debug_level'] & 8))
520c36 90       console($this->conn->message);
T 91     
92     // write error log
42b113 93     else if (!$this->conn && $GLOBALS['iil_error'])
T 94       {
95       raise_error(array('code' => 403,
96                        'type' => 'imap',
97                        'message' => $GLOBALS['iil_error']), TRUE, FALSE);
520c36 98       }
T 99
100     // get account namespace
101     if ($this->conn)
102       {
1cded8 103       $this->_parse_capability($this->conn->capability);
520c36 104       iil_C_NameSpace($this->conn);
T 105       
106       if (!empty($this->conn->delimiter))
107         $this->delimiter = $this->conn->delimiter;
108       if (!empty($this->conn->rootdir))
7902df 109         {
T 110         $this->set_rootdir($this->conn->rootdir);
111         $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
112         }
42b113 113       }
4e17e6 114
T 115     return $this->conn ? TRUE : FALSE;
116     }
117
118
119   function close()
120     {    
121     if ($this->conn)
122       iil_Close($this->conn);
123     }
124
125
520c36 126   function reconnect()
T 127     {
128     $this->close();
129     $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
130     }
131
132
4e17e6 133   function set_rootdir($root)
T 134     {
520c36 135     if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
4e17e6 136       $root = substr($root, 0, -1);
T 137
138     $this->root_dir = $root;
520c36 139     
T 140     if (empty($this->delimiter))
141       $this->get_hierarchy_delimiter();
4e17e6 142     }
T 143
144
145   function set_default_mailboxes($arr)
146     {
147     if (is_array($arr))
148       {
149       $this->default_folders = array();
150       
151       // add mailbox names lower case
152       foreach ($arr as $mbox)
153         $this->default_folders[] = strtolower($mbox);
154       
155       // add inbox if not included
156       if (!in_array('inbox', $this->default_folders))
157         array_unshift($arr, 'inbox');
158       }
159     }
160
161
162   function set_mailbox($mbox)
163     {
164     $mailbox = $this->_mod_mailbox($mbox);
165
166     if ($this->mailbox == $mailbox)
167       return;
168
169     $this->mailbox = $mailbox;
170
171     // clear messagecount cache for this mailbox
172     $this->_clear_messagecount($mailbox);
173     }
174
175
176   function set_page($page)
177     {
178     $this->list_page = (int)$page;
179     }
180
181
182   function set_pagesize($size)
183     {
184     $this->page_size = (int)$size;
185     }
186
187
188   function get_mailbox_name()
189     {
190     return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
1cded8 191     }
T 192
193
194   function get_capability($cap)
195     {
196     $cap = strtoupper($cap);
197     return $this->capabilities[$cap];
4e17e6 198     }
T 199
200
597170 201   function get_hierarchy_delimiter()
T 202     {
203     if ($this->conn && empty($this->delimiter))
204       $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
205
7902df 206     if (empty($this->delimiter))
T 207       $this->delimiter = '/';
208
597170 209     return $this->delimiter;
T 210     }
211
4e17e6 212   // public method for mailbox listing
T 213   // convert mailbox name with root dir first
214   function list_mailboxes($root='', $filter='*')
215     {
216     $a_out = array();
217     $a_mboxes = $this->_list_mailboxes($root, $filter);
218
219     foreach ($a_mboxes as $mbox)
220       {
221       $name = $this->_mod_mailbox($mbox, 'out');
222       if (strlen($name))
223         $a_out[] = $name;
224       }
225
226     // sort mailboxes
227     $a_out = $this->_sort_mailbox_list($a_out);
228
229     return $a_out;
230     }
231
232   // private method for mailbox listing
233   function _list_mailboxes($root='', $filter='*')
234     {
235     $a_defaults = $a_out = array();
236     
237     // get cached folder list    
238     $a_mboxes = $this->get_cache('mailboxes');
239     if (is_array($a_mboxes))
240       return $a_mboxes;
241
242     // retrieve list of folders from IMAP server
243     $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
244     
245     if (!is_array($a_folders) || !sizeof($a_folders))
246       $a_folders = array();
247
248     // create INBOX if it does not exist
249     if (!in_array_nocase('INBOX', $a_folders))
250       {
251       $this->create_mailbox('INBOX', TRUE);
252       array_unshift($a_folders, 'INBOX');
253       }
254
255     $a_mailbox_cache = array();
256
257     // write mailboxlist to cache
258     $this->update_cache('mailboxes', $a_folders);
259     
260     return $a_folders;
261     }
262
263
264   // get message count for a specific mailbox; acceptes modes are: ALL, UNSEEN
265   function messagecount($mbox='', $mode='ALL', $force=FALSE)
266     {
267     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
268     return $this->_messagecount($mailbox, $mode, $force);
269     }
270
271   // private method for getting nr of mesages
272   function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
273     {
274     $a_mailbox_cache = FALSE;
275     $mode = strtoupper($mode);
276
277     if (!$mailbox)
278       $mailbox = $this->mailbox;
279
280     $a_mailbox_cache = $this->get_cache('messagecount');
281     
282     // return cached value
283     if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
31b2ce 284       return $a_mailbox_cache[$mailbox][$mode];
T 285       
286     $search_str = "ALL UNDELETED";
4e17e6 287
T 288     // get message count and store in cache
289     if ($mode == 'UNSEEN')
31b2ce 290       $search_str .= " UNSEEN";
T 291
292     // get message count using SEARCH
293     // not very performant but more precise (using UNDELETED)
294     $count = 0;
295     $index = $this->_search_index($mailbox, $search_str);
296     if (is_array($index))
297       {
298       $str = implode(",", $index);
299       if (!empty($str))
300         $count = count($index);
301       }
4e17e6 302
T 303     if (is_array($a_mailbox_cache[$mailbox]))
304       $a_mailbox_cache[$mailbox] = array();
305       
306     $a_mailbox_cache[$mailbox][$mode] = (int)$count;
31b2ce 307
4e17e6 308     // write back to cache
T 309     $this->update_cache('messagecount', $a_mailbox_cache);
310
311     return (int)$count;
312     }
313
314
315   // public method for listing headers
316   // convert mailbox name with root dir first
31b2ce 317   function list_headers($mbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
4e17e6 318     {
T 319     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
320     return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
321     }
322
323
324   // private method for listing message header
31b2ce 325   function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
4e17e6 326     {
T 327     if (!strlen($mailbox))
17fc71 328       return array();
31b2ce 329       
T 330     if ($sort_field!=NULL)
331       $this->sort_field = $sort_field;
332     if ($sort_order!=NULL)
333       $this->sort_order = strtoupper($sort_order);
4e17e6 334
1cded8 335     $max = $this->_messagecount($mailbox);
T 336     $start_msg = ($this->list_page-1) * $this->page_size;
337     
338     if ($page=='all')
4e17e6 339       {
1cded8 340       $begin = 0;
T 341       $end = $max;
342       }
31b2ce 343     else if ($this->sort_order=='DESC')
1cded8 344       {
T 345       $begin = $max - $this->page_size - $start_msg;
346       $end =   $max - $start_msg;
4e17e6 347       }
T 348     else
b595c9 349       {
1cded8 350       $begin = $start_msg;
T 351       $end =   $start_msg + $this->page_size;
b595c9 352       }
4e17e6 353
1cded8 354     if ($begin < 0) $begin = 0;
T 355     if ($end < 0) $end = $max;
356     if ($end > $max) $end = $max;
b076a4 357
1cded8 358 //console("fetch headers $start_msg to ".($start_msg+$this->page_size)." (msg $begin to $end)");
T 359
360     $headers_sorted = FALSE;
361     $cache_key = $mailbox.'.msg';
362     $cache_status = $this->check_cache_status($mailbox, $cache_key);
363
364 //console("Cache status = $cache_status");
365     
366     // cache is OK, we can get all messages from local cache
367     if ($cache_status>0)
368       {
31b2ce 369       $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
1cded8 370       $headers_sorted = TRUE;
T 371       }
372     else
373       {
374       // retrieve headers from IMAP
31b2ce 375       if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field)))
1cded8 376         {
T 377 //console("$mailbox: ".count($msg_index));
378         
379         $msgs = $msg_index[$begin];
380         for ($i=$begin; $i < $end; $i++)
381           {
31b2ce 382           if ($this->sort_order == 'DESC')
1cded8 383             $msgs = $msg_index[$i].','.$msgs;
T 384           else
385             $msgs = $msgs.','.$msg_index[$i];
386           }
387
388         $sorted = TRUE;
389         }
390       else
391         {
392         $msgs = sprintf("%d:%d", $begin+1, $end);
393         $sorted = FALSE;
394         }
395
396
397       // cache is dirty, sync it
31b2ce 398       if ($this->caching_enabled && $cache_status==-1 && !$recursive)
1cded8 399         {
T 400         $this->sync_header_index($mailbox);
31b2ce 401         return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
1cded8 402         }      
T 403
404
405       // cache is incomplete
406       $cache_index = $this->get_message_cache_index($cache_key);
407         
408       // fetch reuested headers from server
409       $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
410       $a_msg_headers = array();
31b2ce 411       $deleted_count = 0;
1cded8 412       
T 413       if (!empty($a_header_index))
414         {
415         foreach ($a_header_index as $i => $headers)
416           {
417           if ($headers->deleted)
418             {
419             // delete from cache
420             if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
421               $this->remove_message_cache($cache_key, $headers->id);
422
31b2ce 423             $deleted_count++;
1cded8 424             continue;
T 425             }
426
427           // add message to cache
428           if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
429             $this->add_message_cache($cache_key, $headers->id, $headers);
430
431           $a_msg_headers[$headers->uid] = $headers;
432           }
433         }
434
435       // delete cached messages with a higher index than $max
436       $this->clear_message_cache($cache_key, $max);
437
438
31b2ce 439       // fetch more headers of there were any deleted messages
T 440       // ...
441
1cded8 442       // kick child process to sync cache
31b2ce 443       // ...
1cded8 444       
T 445       }
446
447
448     // return empty array if no messages found
449     if (!is_array($a_msg_headers) || empty($a_msg_headers))
450         return array();
451
452
453     // if not already sorted
454     if (!$headers_sorted)
31b2ce 455       $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
1cded8 456
T 457     return array_values($a_msg_headers);
4e17e6 458     }
1cded8 459
4e17e6 460
T 461   // return sorted array of message UIDs
31b2ce 462   function message_index($mbox='', $sort_field=NULL, $sort_order=NULL)
4e17e6 463     {
31b2ce 464     if ($sort_field!=NULL)
T 465       $this->sort_field = $sort_field;
466     if ($sort_order!=NULL)
467       $this->sort_order = strtoupper($sort_order);
468
4e17e6 469     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
31b2ce 470     $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
4e17e6 471
31b2ce 472     // have stored it in RAM
T 473     if (isset($this->cache[$key]))
474       return $this->cache[$key];
4e17e6 475
31b2ce 476     // check local cache
T 477     $cache_key = $mailbox.'.msg';
478     $cache_status = $this->check_cache_status($mailbox, $cache_key);
4e17e6 479
31b2ce 480     // cache is OK
T 481     if ($cache_status>0)
482       {
749b07 483       $a_index = $this->get_message_cache_index($cache_key, FALSE, $this->sort_field);
31b2ce 484       return array_values($a_index);
T 485       }
486
487
488     // fetch complete message index
489     $msg_count = $this->_messagecount($mailbox);
490     if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field)))
491       {
492       $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
493
494       if ($this->sort_order == 'DESC')
495         $a_index = array_reverse($a_index);
496
497       $i = 0;
498       $this->cache[$key] = array();
499       foreach ($a_index as $index => $value)
500         $this->cache[$key][$i++] = $a_uids[$value];
501       }
502     else
503       {
504       $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
505       $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
506     
507       if ($this->sort_order=="ASC")
508         asort($a_index);
509       else if ($this->sort_order=="DESC")
510         arsort($a_index);
511         
512       $i = 0;
513       $this->cache[$key] = array();
514       foreach ($a_index as $index => $value)
515         $this->cache[$key][$i++] = $a_uids[$index];
516       }
517
518     return $this->cache[$key];
4e17e6 519     }
T 520
521
1cded8 522   function sync_header_index($mailbox)
4e17e6 523     {
1cded8 524     $cache_key = $mailbox.'.msg';
T 525     $cache_index = $this->get_message_cache_index($cache_key);
526     $msg_count = $this->_messagecount($mailbox);
527
528     // fetch complete message index
529     $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
530         
531     foreach ($a_message_index as $id => $uid)
532       {
533       // message in cache at correct position
534       if ($cache_index[$id] == $uid)
535         {
536 // console("$id / $uid: OK");
537         unset($cache_index[$id]);
538         continue;
539         }
540         
541       // message in cache but in wrong position
542       if (in_array((string)$uid, $cache_index, TRUE))
543         {
544 // console("$id / $uid: Moved");
545         unset($cache_index[$id]);        
546         }
547       
548       // other message at this position
549       if (isset($cache_index[$id]))
550         {
551 // console("$id / $uid: Delete");
552         $this->remove_message_cache($cache_key, $id);
553         unset($cache_index[$id]);
554         }
555         
556
557 // console("$id / $uid: Add");
558
559       // fetch complete headers and add to cache
560       $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
561       $this->add_message_cache($cache_key, $headers->id, $headers);
562       }
563
564     // those ids that are still in cache_index have been deleted      
565     if (!empty($cache_index))
566       {
567       foreach ($cache_index as $id => $uid)
568         $this->remove_message_cache($cache_key, $id);
569       }
4e17e6 570     }
T 571
572
573   function search($mbox='', $criteria='ALL')
574     {
575     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
31b2ce 576     return $this->_search_index($mailbox, $criteria);
T 577     }
578     
579     
580   function _search_index($mailbox, $criteria='ALL')
581     {
4e17e6 582     $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
T 583     return $a_messages;
584     }
585
586
587   function get_headers($uid, $mbox=NULL)
588     {
589     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1cded8 590
4e17e6 591     // get cached headers
1cded8 592     if ($headers = $this->get_cached_message($mailbox.'.msg', $uid))
T 593       return $headers;
520c36 594
4e17e6 595     $msg_id = $this->_uid2id($uid);
1cded8 596     $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
520c36 597
4e17e6 598     // write headers cache
1cded8 599     if ($headers)
T 600       $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
4e17e6 601
1cded8 602     return $headers;
4e17e6 603     }
T 604
605
606   function get_body($uid, $part=1)
607     {
608     if (!($msg_id = $this->_uid2id($uid)))
609       return FALSE;
610
611     $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
612     $structure = iml_GetRawStructureArray($structure_str);
613     $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
614
615     $encoding = iml_GetPartEncodingCode($structure, $part);
616     
617     if ($encoding==3) $body = $this->mime_decode($body, 'base64');
618     else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
619
620     return $body;
621     }
622
623
624   function get_raw_body($uid)
625     {
626     if (!($msg_id = $this->_uid2id($uid)))
627       return FALSE;
628
629     $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
630     $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
631
632     return $body;    
633     }
634
635
636   // set message flag to one or several messages
637   // possible flgs are: SEEN, DELETED, RECENT, ANSWERED, DRAFT
638   function set_flag($uids, $flag)
639     {
640     $flag = strtoupper($flag);
641     $msg_ids = array();
642     if (!is_array($uids))
643       $uids = array($uids);
644       
645     foreach ($uids as $uid)
31b2ce 646       $msg_ids[$uid] = $this->_uid2id($uid);
4e17e6 647       
T 648     if ($flag=='UNSEEN')
31b2ce 649       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
4e17e6 650     else
31b2ce 651       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
4e17e6 652
T 653     // reload message headers if cached
654     $cache_key = $this->mailbox.'.msg';
1cded8 655     if ($this->caching_enabled)
4e17e6 656       {
31b2ce 657       foreach ($msg_ids as $uid => $id)
4e17e6 658         {
31b2ce 659         if ($cached_headers = $this->get_cached_message($cache_key, $uid))
4e17e6 660           {
1cded8 661           $this->remove_message_cache($cache_key, $id);
T 662           //$this->get_headers($uid);
4e17e6 663           }
T 664         }
1cded8 665
T 666       // close and re-open connection
667       // this prevents connection problems with Courier 
668       $this->reconnect();
4e17e6 669       }
T 670
671     // set nr of messages that were flaged
31b2ce 672     $count = count($msg_ids);
4e17e6 673
T 674     // clear message count cache
675     if ($result && $flag=='SEEN')
676       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
677     else if ($result && $flag=='UNSEEN')
678       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
679     else if ($result && $flag=='DELETED')
680       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
681
682     return $result;
683     }
684
685
686   // append a mail message (source) to a specific mailbox
687   function save_message($mbox, $message)
688     {
689     $mailbox = $this->_mod_mailbox($mbox);
690
691     // make shure mailbox exists
692     if (in_array($mailbox, $this->_list_mailboxes()))
693       $saved = iil_C_Append($this->conn, $mailbox, $message);
1cded8 694
4e17e6 695     if ($saved)
T 696       {
697       // increase messagecount of the target mailbox
698       $this->_set_messagecount($mailbox, 'ALL', 1);
699       }
700           
701     return $saved;
702     }
703
704
705   // move a message from one mailbox to another
706   function move_message($uids, $to_mbox, $from_mbox='')
707     {
708     $to_mbox = $this->_mod_mailbox($to_mbox);
709     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
710
711     // make shure mailbox exists
712     if (!in_array($to_mbox, $this->_list_mailboxes()))
713       return FALSE;
714     
715     // convert the list of uids to array
716     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
717     
718     // exit if no message uids are specified
719     if (!is_array($a_uids))
720       return false;
520c36 721
4e17e6 722     // convert uids to message ids
T 723     $a_mids = array();
724     foreach ($a_uids as $uid)
725       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 726
4e17e6 727     $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 728     
729     // send expunge command in order to have the moved message
730     // really deleted from the source mailbox
731     if ($moved)
732       {
1cded8 733       $this->_expunge($from_mbox, FALSE);
4e17e6 734       $this->_clear_messagecount($from_mbox);
T 735       $this->_clear_messagecount($to_mbox);
736       }
737
738     // update cached message headers
739     $cache_key = $from_mbox.'.msg';
1cded8 740     if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 741       {
1cded8 742       $start_index = 100000;
4e17e6 743       foreach ($a_uids as $uid)
1cded8 744         {
T 745         $index = array_search($uid, $a_cache_index);
746         $start_index = min($index, $start_index);
747         }
4e17e6 748
1cded8 749       // clear cache from the lowest index on
T 750       $this->clear_message_cache($cache_key, $start_index);
4e17e6 751       }
T 752
753     return $moved;
754     }
755
756
757   // mark messages as deleted and expunge mailbox
758   function delete_message($uids, $mbox='')
759     {
760     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
761
762     // convert the list of uids to array
763     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
764     
765     // exit if no message uids are specified
766     if (!is_array($a_uids))
767       return false;
768
769
770     // convert uids to message ids
771     $a_mids = array();
772     foreach ($a_uids as $uid)
773       $a_mids[] = $this->_uid2id($uid, $mailbox);
774         
775     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
776     
777     // send expunge command in order to have the deleted message
778     // really deleted from the mailbox
779     if ($deleted)
780       {
1cded8 781       $this->_expunge($mailbox, FALSE);
4e17e6 782       $this->_clear_messagecount($mailbox);
T 783       }
784
785     // remove deleted messages from cache
1cded8 786     $cache_key = $mailbox.'.msg';
T 787     if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 788       {
1cded8 789       $start_index = 100000;
4e17e6 790       foreach ($a_uids as $uid)
1cded8 791         {
T 792         $index = array_search($uid, $a_cache_index);
793         $start_index = min($index, $start_index);
794         }
4e17e6 795
1cded8 796       // clear cache from the lowest index on
T 797       $this->clear_message_cache($cache_key, $start_index);
4e17e6 798       }
T 799
800     return $deleted;
801     }
802
803
a95e0e 804   // clear all messages in a specific mailbox
T 805   function clear_mailbox($mbox)
806     {
807     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
808     $msg_count = $this->_messagecount($mailbox, 'ALL');
809     
810     if ($msg_count>0)
1cded8 811       {
T 812       $this->clear_message_cache($mailbox.'.msg');
a95e0e 813       return iil_C_ClearFolder($this->conn, $mailbox);
1cded8 814       }
a95e0e 815     else
T 816       return 0;
817     }
818
819
4e17e6 820   // send IMAP expunge command and clear cache
T 821   function expunge($mbox='', $clear_cache=TRUE)
822     {
823     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1cded8 824     return $this->_expunge($mailbox, $clear_cache);
T 825     }
826
827
828   // send IMAP expunge command and clear cache
829   function _expunge($mailbox, $clear_cache=TRUE)
830     {
4e17e6 831     $result = iil_C_Expunge($this->conn, $mailbox);
T 832
833     if ($result>=0 && $clear_cache)
834       {
1cded8 835       //$this->clear_message_cache($mailbox.'.msg');
4e17e6 836       $this->_clear_messagecount($mailbox);
T 837       }
838       
839     return $result;
840     }
841
842
843   /* --------------------------------
844    *        folder managment
845    * --------------------------------*/
846
847
848   // return an array with all folders available in IMAP server
849   function list_unsubscribed($root='')
850     {
851     static $sa_unsubscribed;
852     
853     if (is_array($sa_unsubscribed))
854       return $sa_unsubscribed;
855       
856     // retrieve list of folders from IMAP server
857     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
858
859     // modify names with root dir
860     foreach ($a_mboxes as $mbox)
861       {
862       $name = $this->_mod_mailbox($mbox, 'out');
863       if (strlen($name))
864         $a_folders[] = $name;
865       }
866
867     // filter folders and sort them
868     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
869     return $sa_unsubscribed;
870     }
871
872
873   // subscribe to a specific mailbox(es)
874   function subscribe($mbox, $mode='subscribe')
875     {
876     if (is_array($mbox))
877       $a_mboxes = $mbox;
878     else if (is_string($mbox) && strlen($mbox))
879       $a_mboxes = explode(',', $mbox);
880     
881     // let this common function do the main work
882     return $this->_change_subscription($a_mboxes, 'subscribe');
883     }
884
885
886   // unsubscribe mailboxes
887   function unsubscribe($mbox)
888     {
889     if (is_array($mbox))
890       $a_mboxes = $mbox;
891     else if (is_string($mbox) && strlen($mbox))
892       $a_mboxes = explode(',', $mbox);
893
894     // let this common function do the main work
895     return $this->_change_subscription($a_mboxes, 'unsubscribe');
896     }
897
898
899   // create a new mailbox on the server and register it in local cache
900   function create_mailbox($name, $subscribe=FALSE)
901     {
902     $result = FALSE;
1cded8 903     
T 904     // replace backslashes
905     $name = preg_replace('/[\\\]+/', '-', $name);
906
a95e0e 907     $name_enc = UTF7EncodeString($name);
1cded8 908
T 909     // reduce mailbox name to 100 chars
910     $name_enc = substr($name_enc, 0, 100);
911
a95e0e 912     $abs_name = $this->_mod_mailbox($name_enc);
4e17e6 913     $a_mailbox_cache = $this->get_cache('mailboxes');
1cded8 914         
4e17e6 915     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 916       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 917
T 918     // update mailboxlist cache
919     if ($result && $subscribe)
a95e0e 920       $this->subscribe($name_enc);
4e17e6 921
7902df 922     return $result ? $name : FALSE;
4e17e6 923     }
T 924
925
926   // set a new name to an existing mailbox
927   function rename_mailbox($mbox, $new_name)
928     {
929     // not implemented yet
930     }
931
932
933   // remove mailboxes from server
934   function delete_mailbox($mbox)
935     {
936     $deleted = FALSE;
937
938     if (is_array($mbox))
939       $a_mboxes = $mbox;
940     else if (is_string($mbox) && strlen($mbox))
941       $a_mboxes = explode(',', $mbox);
942
943     if (is_array($a_mboxes))
944       foreach ($a_mboxes as $mbox)
945         {
946         $mailbox = $this->_mod_mailbox($mbox);
947
948         // unsubscribe mailbox before deleting
949         iil_C_UnSubscribe($this->conn, $mailbox);
950         
951         // send delete command to server
952         $result = iil_C_DeleteFolder($this->conn, $mailbox);
953         if ($result>=0)
954           $deleted = TRUE;
955         }
956
957     // clear mailboxlist cache
958     if ($deleted)
1cded8 959       {
T 960       $this->clear_message_cache($mailbox.'.msg');
4e17e6 961       $this->clear_cache('mailboxes');
1cded8 962       }
4e17e6 963
1cded8 964     return $deleted;
4e17e6 965     }
T 966
967
968
969
970   /* --------------------------------
1cded8 971    *   internal caching methods
4e17e6 972    * --------------------------------*/
6dc026 973
T 974
975   function set_caching($set)
976     {
1cded8 977     if ($set && is_object($this->db))
6dc026 978       $this->caching_enabled = TRUE;
T 979     else
980       $this->caching_enabled = FALSE;
981     }
1cded8 982
4e17e6 983
T 984   function get_cache($key)
985     {
986     // read cache
6dc026 987     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 988       {
1cded8 989       $cache_data = $this->_read_cache_record('IMAP.'.$key);
4e17e6 990       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
T 991       }
992     
1cded8 993     return $this->cache[$key];
4e17e6 994     }
T 995
996
997   function update_cache($key, $data)
998     {
999     $this->cache[$key] = $data;
1000     $this->cache_changed = TRUE;
1001     $this->cache_changes[$key] = TRUE;
1002     }
1003
1004
1005   function write_cache()
1006     {
6dc026 1007     if ($this->caching_enabled && $this->cache_changed)
4e17e6 1008       {
T 1009       foreach ($this->cache as $key => $data)
1010         {
1011         if ($this->cache_changes[$key])
1cded8 1012           $this->_write_cache_record('IMAP.'.$key, serialize($data));
4e17e6 1013         }
T 1014       }    
1015     }
1016
1017
1018   function clear_cache($key=NULL)
1019     {
1020     if ($key===NULL)
1021       {
1022       foreach ($this->cache as $key => $data)
1cded8 1023         $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1024
T 1025       $this->cache = array();
1026       $this->cache_changed = FALSE;
1027       $this->cache_changes = array();
1028       }
1029     else
1030       {
1cded8 1031       $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1032       $this->cache_changes[$key] = FALSE;
T 1033       unset($this->cache[$key]);
1034       }
1035     }
1036
1037
1038
1cded8 1039   function _read_cache_record($key)
T 1040     {
1041     $cache_data = FALSE;
1042     
1043     if ($this->db)
1044       {
1045       // get cached data from DB
1046       $sql_result = $this->db->query(
1047         "SELECT cache_id, data
1048          FROM ".get_table_name('cache')."
1049          WHERE  user_id=?
1050          AND    cache_key=?",
1051         $_SESSION['user_id'],
1052         $key);
1053
1054       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1055         {
1056         $cache_data = $sql_arr['data'];
1057         $this->cache_keys[$key] = $sql_arr['cache_id'];
1058         }
1059       }
1060
1061     return $cache_data;    
1062     }
1063     
1064
1065   function _write_cache_record($key, $data)
1066     {
1067     if (!$this->db)
1068       return FALSE;
1069
1070     // check if we already have a cache entry for this key
1071     if (!isset($this->cache_keys[$key]))
1072       {
1073       $sql_result = $this->db->query(
1074         "SELECT cache_id
1075          FROM ".get_table_name('cache')."
1076          WHERE  user_id=?
1077          AND    cache_key=?",
1078         $_SESSION['user_id'],
1079         $key);
1080                                      
1081       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1082         $this->cache_keys[$key] = $sql_arr['cache_id'];
1083       else
1084         $this->cache_keys[$key] = FALSE;
1085       }
1086
1087     // update existing cache record
1088     if ($this->cache_keys[$key])
1089       {
1090       $this->db->query(
1091         "UPDATE ".get_table_name('cache')."
1092          SET    created=now(),
1093                 data=?
1094          WHERE  user_id=?
1095          AND    cache_key=?",
1096         $data,
1097         $_SESSION['user_id'],
1098         $key);
1099       }
1100     // add new cache record
1101     else
1102       {
1103       $this->db->query(
1104         "INSERT INTO ".get_table_name('cache')."
1105          (created, user_id, cache_key, data)
1106          VALUES (now(), ?, ?, ?)",
1107         $_SESSION['user_id'],
1108         $key,
1109         $data);
1110       }
1111     }
1112
1113
1114   function _clear_cache_record($key)
1115     {
1116     $this->db->query(
1117       "DELETE FROM ".get_table_name('cache')."
1118        WHERE  user_id=?
1119        AND    cache_key=?",
1120       $_SESSION['user_id'],
1121       $key);
1122     }
1123
1124
1125
4e17e6 1126   /* --------------------------------
1cded8 1127    *   message caching methods
T 1128    * --------------------------------*/
1129    
1130
1131   // checks if the cache is up-to-date
1132   // return: -3 = off, -2 = incomplete, -1 = dirty
1133   function check_cache_status($mailbox, $cache_key)
1134     {
1135     if (!$this->caching_enabled)
1136       return -3;
1137
1138     $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1139     $msg_count = $this->_messagecount($mailbox);
1140     $cache_count = count($cache_index);
1141
1142     // console("Cache check: $msg_count !== ".count($cache_index));
1143
1144     if ($cache_count==$msg_count)
1145       {
1146       // get highest index
1147       $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1148       $cache_uid = array_pop($cache_index);
1149       
1150       // uids of highes message matches -> cache seems OK
1151       if ($cache_uid == $header->uid)
1152         return 1;
1153
1154       // cache is dirty
1155       return -1;
1156       }
1157     // if cache count differs less that 10% report as dirty
1158     else if (abs($msg_count - $cache_count) < $msg_count/10)
1159       return -1;
1160     else
1161       return -2;
1162     }
1163
1164
1165
1166   function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1167     {
1168     $cache_key = "$key:$from:$to:$sort_field:$sort_order";
1169     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
1170     
1171     if (!in_array($sort_field, $db_header_fields))
1172       $sort_field = 'idx';
1173     
1174     if ($this->caching_enabled && !isset($this->cache[$cache_key]))
1175       {
1176       $this->cache[$cache_key] = array();
1177       $sql_result = $this->db->limitquery(
1178         "SELECT idx, uid, headers
1179          FROM ".get_table_name('messages')."
1180          WHERE  user_id=?
1181          AND    cache_key=?
1182          ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
1183          strtoupper($sort_order),
1184         $from,
1185         $to-$from,
1186         $_SESSION['user_id'],
1187         $key);
1188
1189       while ($sql_arr = $this->db->fetch_assoc($sql_result))
1190         {
1191         $uid = $sql_arr['uid'];
1192         $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
1193         }
1194       }
1195       
1196     return $this->cache[$cache_key];
1197     }
1198
1199
1200   function get_cached_message($key, $uid, $body=FALSE)
1201     {
1202     if (!$this->caching_enabled)
1203       return FALSE;
1204
1205     $internal_key = '__single_msg';
1206     if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
1207       {
1208       $sql_select = "idx, uid, headers";
1209       if ($body)
1210         $sql_select .= ", body";
1211       
1212       $sql_result = $this->db->query(
1213         "SELECT $sql_select
1214          FROM ".get_table_name('messages')."
1215          WHERE  user_id=?
1216          AND    cache_key=?
1217          AND    uid=?",
1218         $_SESSION['user_id'],
1219         $key,
1220         $uid);
1221       
1222       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1223         {
1224         $headers = unserialize($sql_arr['headers']);
1225         if (is_object($headers) && !empty($sql_arr['body']))
1226           $headers->body = $sql_arr['body'];
1227
1228         $this->cache[$internal_key][$uid] = $headers;
1229         }
1230       }
1231
1232     return $this->cache[$internal_key][$uid];
1233     }
1234
1235    
31b2ce 1236   function get_message_cache_index($key, $force=FALSE, $sort_col='idx')
1cded8 1237     {
T 1238     static $sa_message_index = array();
1239     
1240     if (!empty($sa_message_index[$key]) && !$force)
1241       return $sa_message_index[$key];
1242     
1243     $sa_message_index[$key] = array();
1244     $sql_result = $this->db->query(
1245       "SELECT idx, uid
1246        FROM ".get_table_name('messages')."
1247        WHERE  user_id=?
1248        AND    cache_key=?
31b2ce 1249        ORDER BY ".$sort_col." ASC",
1cded8 1250       $_SESSION['user_id'],
T 1251       $key);
1252
1253     while ($sql_arr = $this->db->fetch_assoc($sql_result))
1254       $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
1255       
1256     return $sa_message_index[$key];
1257     }
1258
1259
1260   function add_message_cache($key, $index, $headers)
1261     {
31b2ce 1262     if (!is_object($headers) || empty($headers->uid))
T 1263       return;
1264
1cded8 1265     $this->db->query(
T 1266       "INSERT INTO ".get_table_name('messages')."
1267        (user_id, del, cache_key, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
1268        VALUES (?, 0, ?, ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
1269       $_SESSION['user_id'],
1270       $key,
1271       $index,
1272       $headers->uid,
1273       $this->decode_header($headers->subject, TRUE),
1274       $this->decode_header($headers->from, TRUE),
1275       $this->decode_header($headers->to, TRUE),
1276       $this->decode_header($headers->cc, TRUE),
31b2ce 1277       (int)$headers->size,
1cded8 1278       serialize($headers));
T 1279     }
1280     
1281     
1282   function remove_message_cache($key, $index)
1283     {
1284     $this->db->query(
1285       "DELETE FROM ".get_table_name('messages')."
1286        WHERE  user_id=?
1287        AND    cache_key=?
1288        AND    idx=?",
1289       $_SESSION['user_id'],
1290       $key,
1291       $index);
1292     }
1293
1294
1295   function clear_message_cache($key, $start_index=1)
1296     {
1297     $this->db->query(
1298       "DELETE FROM ".get_table_name('messages')."
1299        WHERE  user_id=?
1300        AND    cache_key=?
1301        AND    idx>=?",
1302       $_SESSION['user_id'],
1303       $key,
1304       $start_index);
1305     }
1306
1307
1308
1309
1310   /* --------------------------------
1311    *   encoding/decoding methods
4e17e6 1312    * --------------------------------*/
T 1313
1314   
1315   function decode_address_list($input, $max=NULL)
1316     {
1317     $a = $this->_parse_address_list($input);
1318     $out = array();
1319
1320     if (!is_array($a))
1321       return $out;
1322
1323     $c = count($a);
1324     $j = 0;
1325
1326     foreach ($a as $val)
1327       {
1328       $j++;
1329       $address = $val['address'];
1330       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
1331       $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
1332       
1333       $out[$j] = array('name' => $name,
1334                        'mailto' => $address,
1335                        'string' => $string);
1336               
1337       if ($max && $j==$max)
1338         break;
1339       }
1340     
1341     return $out;
1342     }
1343
1344
1cded8 1345   function decode_header($input, $remove_quotes=FALSE)
4e17e6 1346     {
31b2ce 1347     $str = $this->decode_mime_string((string)$input);
1cded8 1348     if ($str{0}=='"' && $remove_quotes)
T 1349       {
1350       $str = str_replace('"', '', $str);
1351       }
1352     
1353     return $str;
4b0f65 1354     }
T 1355     
1356     
1357   function decode_mime_string($input)
1358     {
4e17e6 1359     $out = '';
T 1360
1361     $pos = strpos($input, '=?');
1362     if ($pos !== false)
1363       {
1364       $out = substr($input, 0, $pos);
1365   
1366       $end_cs_pos = strpos($input, "?", $pos+2);
1367       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1368       $end_pos = strpos($input, "?=", $end_en_pos+1);
1369   
1370       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1371       $rest = substr($input, $end_pos+2);
1372
4b0f65 1373       $out .= rcube_imap::_decode_mime_string_part($encstr);
T 1374       $out .= rcube_imap::decode_mime_string($rest);
4e17e6 1375
T 1376       return $out;
1377       }
1378     else
1379       return $input;
1380     }
1381
1382
4b0f65 1383   function _decode_mime_string_part($str)
4e17e6 1384     {
T 1385     $a = explode('?', $str);
1386     $count = count($a);
1387
1388     // should be in format "charset?encoding?base64_string"
1389     if ($count >= 3)
1390       {
1391       for ($i=2; $i<$count; $i++)
1392         $rest.=$a[$i];
1393
1394       if (($a[1]=="B")||($a[1]=="b"))
1395         $rest = base64_decode($rest);
1396       else if (($a[1]=="Q")||($a[1]=="q"))
1397         {
1398         $rest = str_replace("_", " ", $rest);
1399         $rest = quoted_printable_decode($rest);
1400         }
1401
1402       return decode_specialchars($rest, $a[0]);
1403       }
1404     else
1405       return $str;    //we dont' know what to do with this  
1406     }
1407
1408
1409   function mime_decode($input, $encoding='7bit')
1410     {
1411     switch (strtolower($encoding))
1412       {
1413       case '7bit':
1414         return $input;
1415         break;
1416       
1417       case 'quoted-printable':
1418         return quoted_printable_decode($input);
1419         break;
1420       
1421       case 'base64':
1422         return base64_decode($input);
1423         break;
1424       
1425       default:
1426         return $input;
1427       }
1428     }
1429
1430
1431   function mime_encode($input, $encoding='7bit')
1432     {
1433     switch ($encoding)
1434       {
1435       case 'quoted-printable':
1436         return quoted_printable_encode($input);
1437         break;
1438
1439       case 'base64':
1440         return base64_encode($input);
1441         break;
1442
1443       default:
1444         return $input;
1445       }
1446     }
1447
1448
1449   // convert body chars according to the ctype_parameters
1450   function charset_decode($body, $ctype_param)
1451     {
a95e0e 1452     if (is_array($ctype_param) && !empty($ctype_param['charset']))
4e17e6 1453       return decode_specialchars($body, $ctype_param['charset']);
T 1454
1455     return $body;
1456     }
1457
1458
1cded8 1459
4e17e6 1460   /* --------------------------------
T 1461    *         private methods
1462    * --------------------------------*/
1463
1464
1465   function _mod_mailbox($mbox, $mode='in')
1466     {
f619de 1467     if ((!empty($this->root_ns) && $this->root_ns == $mbox) || ($mbox == 'INBOX' && $mode == 'in'))
7902df 1468       return $mbox;
T 1469
f619de 1470     if (!empty($this->root_dir) && $mode=='in') 
520c36 1471       $mbox = $this->root_dir.$this->delimiter.$mbox;
7902df 1472     else if (strlen($this->root_dir) && $mode=='out') 
4e17e6 1473       $mbox = substr($mbox, strlen($this->root_dir)+1);
T 1474
1475     return $mbox;
1476     }
1477
1478
1479   // sort mailboxes first by default folders and then in alphabethical order
1480   function _sort_mailbox_list($a_folders)
1481     {
1482     $a_out = $a_defaults = array();
1483
1484     // find default folders and skip folders starting with '.'
1485     foreach($a_folders as $i => $folder)
1486       {
1487       if ($folder{0}=='.')
1488           continue;
1489           
1490       if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1491           $a_defaults[$p] = $folder;
1492       else
1493         $a_out[] = $folder;
1494       }
1495
1496     sort($a_out);
1497     ksort($a_defaults);
1498     
1499     return array_merge($a_defaults, $a_out);
1500     }
1501
1502
1503   function _uid2id($uid, $mbox=NULL)
1504     {
1505     if (!$mbox)
1506       $mbox = $this->mailbox;
1507       
1508     if (!isset($this->uid_id_map[$mbox][$uid]))
1509       $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid);
1510
1511     return $this->uid_id_map[$mbox][$uid];
1512     }
1513
1514
1cded8 1515   // parse string or array of server capabilities and put them in internal array
T 1516   function _parse_capability($caps)
1517     {
1518     if (!is_array($caps))
1519       $cap_arr = explode(' ', $caps);
1520     else
1521       $cap_arr = $caps;
1522     
1523     foreach ($cap_arr as $cap)
1524       {
1525       if ($cap=='CAPABILITY')
1526         continue;
1527
1528       if (strpos($cap, '=')>0)
1529         {
1530         list($key, $value) = explode('=', $cap);
1531         if (!is_array($this->capabilities[$key]))
1532           $this->capabilities[$key] = array();
1533           
1534         $this->capabilities[$key][] = $value;
1535         }
1536       else
1537         $this->capabilities[$cap] = TRUE;
1538       }
1539     }
1540
1541
4e17e6 1542   // subscribe/unsubscribe a list of mailboxes and update local cache
T 1543   function _change_subscription($a_mboxes, $mode)
1544     {
1545     $updated = FALSE;
1546     
1547     if (is_array($a_mboxes))
1548       foreach ($a_mboxes as $i => $mbox)
1549         {
1550         $mailbox = $this->_mod_mailbox($mbox);
1551         $a_mboxes[$i] = $mailbox;
1552
1553         if ($mode=='subscribe')
1554           $result = iil_C_Subscribe($this->conn, $mailbox);
1555         else if ($mode=='unsubscribe')
1556           $result = iil_C_UnSubscribe($this->conn, $mailbox);
1557
1558         if ($result>=0)
1559           $updated = TRUE;
1560         }
1561         
1562     // get cached mailbox list    
1563     if ($updated)
1564       {
1565       $a_mailbox_cache = $this->get_cache('mailboxes');
1566       if (!is_array($a_mailbox_cache))
1567         return $updated;
1568
1569       // modify cached list
1570       if ($mode=='subscribe')
1571         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1572       else if ($mode=='unsubscribe')
1573         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1574         
1575       // write mailboxlist to cache
1576       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1577       }
1578
1579     return $updated;
1580     }
1581
1582
1583   // increde/decrese messagecount for a specific mailbox
1584   function _set_messagecount($mbox, $mode, $increment)
1585     {
1586     $a_mailbox_cache = FALSE;
1587     $mailbox = $mbox ? $mbox : $this->mailbox;
1588     $mode = strtoupper($mode);
1589
1590     $a_mailbox_cache = $this->get_cache('messagecount');
1591     
1592     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1593       return FALSE;
1594     
1595     // add incremental value to messagecount
1596     $a_mailbox_cache[$mailbox][$mode] += $increment;
31b2ce 1597     
T 1598     // there's something wrong, delete from cache
1599     if ($a_mailbox_cache[$mailbox][$mode] < 0)
1600       unset($a_mailbox_cache[$mailbox][$mode]);
4e17e6 1601
T 1602     // write back to cache
1603     $this->update_cache('messagecount', $a_mailbox_cache);
1604     
1605     return TRUE;
1606     }
1607
1608
1609   // remove messagecount of a specific mailbox from cache
1610   function _clear_messagecount($mbox='')
1611     {
1612     $a_mailbox_cache = FALSE;
1613     $mailbox = $mbox ? $mbox : $this->mailbox;
1614
1615     $a_mailbox_cache = $this->get_cache('messagecount');
1616
1617     if (is_array($a_mailbox_cache[$mailbox]))
1618       {
1619       unset($a_mailbox_cache[$mailbox]);
1620       $this->update_cache('messagecount', $a_mailbox_cache);
1621       }
1622     }
1623
1624
1625   function _parse_address_list($str)
1626     {
1627     $a = $this->_explode_quoted_string(',', $str);
1628     $result = array();
1629
1630     foreach ($a as $key => $val)
1631       {
1632       $val = str_replace("\"<", "\" <", $val);
1633       $sub_a = $this->_explode_quoted_string(' ', $val);
1634       
1635       foreach ($sub_a as $k => $v)
1636         {
1637         if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
1638           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
1639         else
1640           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
1641         }
1642         
1643       if (empty($result[$key]['name']))
1644         $result[$key]['name'] = $result[$key]['address'];
1645         
1646       $result[$key]['name'] = $this->decode_header($result[$key]['name']);
1647       }
1648     
1649     return $result;
1650     }
1651
1652
1653   function _explode_quoted_string($delimiter, $string)
1654     {
1655     $quotes = explode("\"", $string);
1656     foreach ($quotes as $key => $val)
1657       if (($key % 2) == 1)
1658         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
1659         
1660     $string = implode("\"", $quotes);
1661
1662     $result = explode($delimiter, $string);
1663     foreach ($result as $key => $val) 
1664       $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
1665     
1666     return $result;
1667     }
1668   }
1669
1670
1671
1672
1673
1674 function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
1675   {
1676   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1677   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
1678   $eol = "\r\n";
1679   $escape = "=";
1680   $output = "";
1681
1682   while( list(, $line) = each($lines))
1683     {
1684     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
1685     $linlen = strlen($line);
1686     $newline = "";
1687     for($i = 0; $i < $linlen; $i++)
1688       {
1689       $c = substr( $line, $i, 1 );
1690       $dec = ord( $c );
1691       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
1692         {
1693         $c = "=2E";
1694         }
1695       if ( $dec == 32 )
1696         {
1697         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
1698           {
1699           $c = "=20";
1700           }
1701         else if ( $space_conv )
1702           {
1703           $c = "=20";
1704           }
1705         }
1706       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
1707         {
1708         $h2 = floor($dec/16);
1709         $h1 = floor($dec%16);
1710         $c = $escape.$hex["$h2"].$hex["$h1"];
1711         }
1712          
1713       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
1714         {
1715         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1716         $newline = "";
1717         // check if newline first character will be point or not
1718         if ( $dec == 46 )
1719           {
1720           $c = "=2E";
1721           }
1722         }
1723       $newline .= $c;
1724       } // end of for
1725     $output .= $newline.$eol;
1726     } // end of while
1727
1728   return trim($output);
1729   }
1730
1731 ?>