alecpl
2008-11-07 e0c9741ef550f36706888dee124700bae4f7dbfc
commit | author | age
4e17e6 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
47124c 5  | program/include/rcube_imap.php                                        |
4e17e6 6  |                                                                       |
T 7  | This file is part of the RoundCube Webmail client                     |
47124c 8  | Copyright (C) 2005-2008, 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
6d969b 24 /*
15a9d1 25  * Obtain classes from the Iloha IMAP library
T 26  */
4e17e6 27 require_once('lib/imap.inc');
T 28 require_once('lib/mime.inc');
29
30
15a9d1 31 /**
T 32  * Interface class for accessing an IMAP server
33  *
34  * This is a wrapper that implements the Iloha IMAP Library (IIL)
35  *
6d969b 36  * @package    Mail
15a9d1 37  * @author     Thomas Bruederli <roundcube@gmail.com>
d5342a 38  * @version    1.40
15a9d1 39  * @link       http://ilohamail.org
T 40  */
4e17e6 41 class rcube_imap
6d969b 42 {
1cded8 43   var $db;
4e17e6 44   var $conn;
520c36 45   var $root_ns = '';
4e17e6 46   var $root_dir = '';
T 47   var $mailbox = 'INBOX';
48   var $list_page = 1;
49   var $page_size = 10;
31b2ce 50   var $sort_field = 'date';
T 51   var $sort_order = 'DESC';
9490b7 52   var $delimiter = NULL;
6dc026 53   var $caching_enabled = FALSE;
17b5fb 54   var $default_charset = 'ISO-8859-1';
fa4cd2 55   var $default_folders = array('INBOX');
T 56   var $default_folders_lc = array('inbox');
4e17e6 57   var $cache = array();
1cded8 58   var $cache_keys = array();  
326e87 59   var $cache_changes = array();
4e17e6 60   var $uid_id_map = array();
T 61   var $msg_headers = array();
15a9d1 62   var $skip_deleted = FALSE;
04c618 63   var $search_set = NULL;
T 64   var $search_subject = '';
65   var $search_string = '';
66   var $search_charset = '';
15a9d1 67   var $debug_level = 1;
fc6725 68   var $error_code = 0;
4e17e6 69
T 70
15a9d1 71   /**
T 72    * Object constructor
73    *
6d969b 74    * @param object DB Database connection
15a9d1 75    */
1cded8 76   function __construct($db_conn)
4e17e6 77     {
3f9edb 78     $this->db = $db_conn;
4e17e6 79     }
T 80
15a9d1 81
T 82   /**
83    * Connect to an IMAP server
84    *
85    * @param  string   Host to connect
86    * @param  string   Username for IMAP account
87    * @param  string   Password for IMAP account
88    * @param  number   Port to connect to
5bc0ab 89    * @param  string   SSL schema (either ssl or tls) or null if plain connection
15a9d1 90    * @return boolean  TRUE on success, FALSE on failure
T 91    * @access public
92    */
1fb78c 93   function connect($host, $user, $pass, $port=143, $use_ssl=null, $auth_type=null)
4e17e6 94     {
4647e1 95     global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
42b113 96     
T 97     // check for Open-SSL support in PHP build
98     if ($use_ssl && in_array('openssl', get_loaded_extensions()))
5bc0ab 99       $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
520c36 100     else if ($use_ssl)
T 101       {
15a9d1 102       raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
520c36 103                         'message' => 'Open SSL not available;'), TRUE, FALSE);
T 104       $port = 143;
105       }
4e17e6 106
T 107     $ICL_PORT = $port;
4647e1 108     $IMAP_USE_INTERNAL_DATE = false;
b026c3 109
1fb78c 110     $this->conn = iil_Connect($host, $user, $pass, array('imap' => $auth_type ? $auth_type : 'check'));
4e17e6 111     $this->host = $host;
T 112     $this->user = $user;
113     $this->pass = $pass;
520c36 114     $this->port = $port;
T 115     $this->ssl = $use_ssl;
42b113 116     
520c36 117     // print trace mesages
15a9d1 118     if ($this->conn && ($this->debug_level & 8))
520c36 119       console($this->conn->message);
T 120     
121     // write error log
42b113 122     else if (!$this->conn && $GLOBALS['iil_error'])
T 123       {
fc6725 124       $this->error_code = $GLOBALS['iil_errornum'];
42b113 125       raise_error(array('code' => 403,
T 126                        'type' => 'imap',
127                        'message' => $GLOBALS['iil_error']), TRUE, FALSE);
520c36 128       }
T 129
f7bfec 130     // get server properties
520c36 131     if ($this->conn)
T 132       {
133       if (!empty($this->conn->delimiter))
134         $this->delimiter = $this->conn->delimiter;
135       if (!empty($this->conn->rootdir))
7902df 136         {
T 137         $this->set_rootdir($this->conn->rootdir);
138         $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
139         }
42b113 140       }
4e17e6 141
T 142     return $this->conn ? TRUE : FALSE;
143     }
144
145
15a9d1 146   /**
T 147    * Close IMAP connection
148    * Usually done on script shutdown
149    *
150    * @access public
151    */
4e17e6 152   function close()
T 153     {    
154     if ($this->conn)
155       iil_Close($this->conn);
156     }
157
158
15a9d1 159   /**
T 160    * Close IMAP connection and re-connect
161    * This is used to avoid some strange socket errors when talking to Courier IMAP
162    *
163    * @access public
164    */
520c36 165   function reconnect()
T 166     {
167     $this->close();
168     $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
32efb0 169     
T 170     // issue SELECT command to restore connection status
171     if ($this->mailbox)
172       iil_C_Select($this->conn, $this->mailbox);
520c36 173     }
T 174
175
15a9d1 176   /**
T 177    * Set a root folder for the IMAP connection.
178    *
179    * Only folders within this root folder will be displayed
180    * and all folder paths will be translated using this folder name
181    *
182    * @param  string   Root folder
183    * @access public
184    */
4e17e6 185   function set_rootdir($root)
T 186     {
520c36 187     if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
4e17e6 188       $root = substr($root, 0, -1);
T 189
190     $this->root_dir = $root;
520c36 191     
T 192     if (empty($this->delimiter))
193       $this->get_hierarchy_delimiter();
17b5fb 194     }
T 195
196
197   /**
198    * Set default message charset
199    *
200    * This will be used for message decoding if a charset specification is not available
201    *
202    * @param  string   Charset string
203    * @access public
204    */
205   function set_charset($cs)
206     {
f94a80 207     $this->default_charset = $cs;
4e17e6 208     }
T 209
210
15a9d1 211   /**
T 212    * This list of folders will be listed above all other folders
213    *
214    * @param  array  Indexed list of folder names
215    * @access public
216    */
4e17e6 217   function set_default_mailboxes($arr)
T 218     {
219     if (is_array($arr))
220       {
fa4cd2 221       $this->default_folders = $arr;
T 222       $this->default_folders_lc = array();
223
4e17e6 224       // add inbox if not included
fa4cd2 225       if (!in_array_nocase('INBOX', $this->default_folders))
T 226         array_unshift($this->default_folders, 'INBOX');
227
228       // create a second list with lower cased names
229       foreach ($this->default_folders as $mbox)
230         $this->default_folders_lc[] = strtolower($mbox);
4e17e6 231       }
T 232     }
233
234
15a9d1 235   /**
T 236    * Set internal mailbox reference.
237    *
238    * All operations will be perfomed on this mailbox/folder
239    *
240    * @param  string  Mailbox/Folder name
241    * @access public
242    */
aadfa1 243   function set_mailbox($new_mbox)
4e17e6 244     {
aadfa1 245     $mailbox = $this->_mod_mailbox($new_mbox);
4e17e6 246
T 247     if ($this->mailbox == $mailbox)
248       return;
249
250     $this->mailbox = $mailbox;
251
252     // clear messagecount cache for this mailbox
253     $this->_clear_messagecount($mailbox);
254     }
255
256
15a9d1 257   /**
T 258    * Set internal list page
259    *
260    * @param  number  Page number to list
261    * @access public
262    */
4e17e6 263   function set_page($page)
T 264     {
265     $this->list_page = (int)$page;
266     }
267
268
15a9d1 269   /**
T 270    * Set internal page size
271    *
272    * @param  number  Number of messages to display on one page
273    * @access public
274    */
4e17e6 275   function set_pagesize($size)
T 276     {
277     $this->page_size = (int)$size;
278     }
04c618 279     
T 280
281   /**
282    * Save a set of message ids for future message listing methods
283    *
284    * @param  array  List of IMAP fields to search in
285    * @param  string Search string
286    * @param  array  List of message ids or NULL if empty
287    */
e0c974 288   function set_search_set($subject, $str=null, $msgs=null, $charset=null, $sorted=null)
04c618 289     {
T 290     if (is_array($subject) && $str == null && $msgs == null)
e0c974 291       list($subject, $str, $msgs, $charset, $sorted) = $subject;
04c618 292     if ($msgs != null && !is_array($msgs))
T 293       $msgs = split(',', $msgs);
294       
295     $this->search_subject = $subject;
296     $this->search_string = $str;
4845a1 297     $this->search_set = (array)$msgs;
04c618 298     $this->search_charset = $charset;
T 299     }
300
301
302   /**
303    * Return the saved search set as hash array
6d969b 304    * @return array Search set
04c618 305    */
T 306   function get_search_set()
307     {
308     return array($this->search_subject, $this->search_string, $this->search_set, $this->search_charset);
309     }
4e17e6 310
T 311
15a9d1 312   /**
T 313    * Returns the currently used mailbox name
314    *
315    * @return  string Name of the mailbox/folder
316    * @access  public
317    */
4e17e6 318   function get_mailbox_name()
T 319     {
320     return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
1cded8 321     }
T 322
323
15a9d1 324   /**
T 325    * Returns the IMAP server's capability
326    *
327    * @param   string  Capability name
328    * @return  mixed   Capability value or TRUE if supported, FALSE if not
329    * @access  public
330    */
1cded8 331   function get_capability($cap)
T 332     {
11ef97 333     return iil_C_GetCapability($this->conn, strtoupper($cap));
4e17e6 334     }
T 335
336
15a9d1 337   /**
5b3dd4 338    * Checks the PERMANENTFLAGS capability of the current mailbox
T 339    * and returns true if the given flag is supported by the IMAP server
340    *
341    * @param   string  Permanentflag name
342    * @return  mixed   True if this flag is supported
343    * @access  public
344    */
345   function check_permflag($flag)
346     {
347     $flagsmap = $GLOBALS['IMAP_FLAGS'];
348     return (($imap_flag = $flagsmap[strtoupper($flag)]) && in_array_nocase($imap_flag, $this->conn->permanentflags));
349     }
350
351
352   /**
15a9d1 353    * Returns the delimiter that is used by the IMAP server for folder separation
T 354    *
355    * @return  string  Delimiter string
356    * @access  public
357    */
597170 358   function get_hierarchy_delimiter()
T 359     {
360     if ($this->conn && empty($this->delimiter))
361       $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
362
7902df 363     if (empty($this->delimiter))
T 364       $this->delimiter = '/';
365
597170 366     return $this->delimiter;
T 367     }
368
15a9d1 369
T 370   /**
371    * Public method for mailbox listing.
372    *
373    * Converts mailbox name with root dir first
374    *
375    * @param   string  Optional root folder
376    * @param   string  Optional filter for mailbox listing
377    * @return  array   List of mailboxes/folders
378    * @access  public
379    */
4e17e6 380   function list_mailboxes($root='', $filter='*')
T 381     {
382     $a_out = array();
383     $a_mboxes = $this->_list_mailboxes($root, $filter);
384
aadfa1 385     foreach ($a_mboxes as $mbox_row)
4e17e6 386       {
aadfa1 387       $name = $this->_mod_mailbox($mbox_row, 'out');
4e17e6 388       if (strlen($name))
T 389         $a_out[] = $name;
390       }
391
fa4cd2 392     // INBOX should always be available
T 393     if (!in_array_nocase('INBOX', $a_out))
394       array_unshift($a_out, 'INBOX');
395
4e17e6 396     // sort mailboxes
T 397     $a_out = $this->_sort_mailbox_list($a_out);
398
399     return $a_out;
400     }
401
15a9d1 402
T 403   /**
404    * Private method for mailbox listing
405    *
406    * @return  array   List of mailboxes/folders
6d969b 407    * @see     rcube_imap::list_mailboxes()
15a9d1 408    * @access  private
T 409    */
4e17e6 410   function _list_mailboxes($root='', $filter='*')
T 411     {
412     $a_defaults = $a_out = array();
413     
414     // get cached folder list    
415     $a_mboxes = $this->get_cache('mailboxes');
416     if (is_array($a_mboxes))
417       return $a_mboxes;
418
419     // retrieve list of folders from IMAP server
420     $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
421     
422     if (!is_array($a_folders) || !sizeof($a_folders))
423       $a_folders = array();
424
425     // write mailboxlist to cache
426     $this->update_cache('mailboxes', $a_folders);
427     
428     return $a_folders;
429     }
430
431
3f9edb 432   /**
T 433    * Get message count for a specific mailbox
434    *
435    * @param   string   Mailbox/folder name
436    * @param   string   Mode for count [ALL|UNSEEN|RECENT]
437    * @param   boolean  Force reading from server and update cache
6d969b 438    * @return  int      Number of messages
T 439    * @access  public
3f9edb 440    */
aadfa1 441   function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
4e17e6 442     {
aadfa1 443     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4e17e6 444     return $this->_messagecount($mailbox, $mode, $force);
T 445     }
446
3f9edb 447
T 448   /**
449    * Private method for getting nr of messages
450    *
451    * @access  private
6d969b 452    * @see     rcube_imap::messagecount()
3f9edb 453    */
4e17e6 454   function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
T 455     {
456     $a_mailbox_cache = FALSE;
457     $mode = strtoupper($mode);
458
15a9d1 459     if (empty($mailbox))
4e17e6 460       $mailbox = $this->mailbox;
04c618 461       
T 462     // count search set
110748 463     if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force)
4845a1 464       return count((array)$this->search_set);
4e17e6 465
T 466     $a_mailbox_cache = $this->get_cache('messagecount');
467     
468     // return cached value
469     if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
31b2ce 470       return $a_mailbox_cache[$mailbox][$mode];
4e17e6 471
197601 472     // RECENT count is fetched a bit different
15a9d1 473     if ($mode == 'RECENT')
T 474        $count = iil_C_CheckForRecent($this->conn, $mailbox);
31b2ce 475
15a9d1 476     // use SEARCH for message counting
T 477     else if ($this->skip_deleted)
31b2ce 478       {
15a9d1 479       $search_str = "ALL UNDELETED";
T 480
481       // get message count and store in cache
482       if ($mode == 'UNSEEN')
483         $search_str .= " UNSEEN";
484
485       // get message count using SEARCH
486       // not very performant but more precise (using UNDELETED)
487       $count = 0;
488       $index = $this->_search_index($mailbox, $search_str);
489       if (is_array($index))
490         {
491         $str = implode(",", $index);
492         if (!empty($str))
493           $count = count($index);
494         }
495       }
496     else
497       {
498       if ($mode == 'UNSEEN')
499         $count = iil_C_CountUnseen($this->conn, $mailbox);
500       else
501         $count = iil_C_CountMessages($this->conn, $mailbox);
31b2ce 502       }
4e17e6 503
13c1af 504     if (!is_array($a_mailbox_cache[$mailbox]))
4e17e6 505       $a_mailbox_cache[$mailbox] = array();
T 506       
507     $a_mailbox_cache[$mailbox][$mode] = (int)$count;
31b2ce 508
4e17e6 509     // write back to cache
T 510     $this->update_cache('messagecount', $a_mailbox_cache);
511
512     return (int)$count;
513     }
514
515
3f9edb 516   /**
T 517    * Public method for listing headers
518    * convert mailbox name with root dir first
519    *
520    * @param   string   Mailbox/folder name
6d969b 521    * @param   int      Current page to list
3f9edb 522    * @param   string   Header field to sort by
T 523    * @param   string   Sort order [ASC|DESC]
524    * @return  array    Indexed array with message header objects
525    * @access  public   
526    */
aadfa1 527   function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
4e17e6 528     {
aadfa1 529     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4e17e6 530     return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
T 531     }
532
533
3f9edb 534   /**
4647e1 535    * Private method for listing message headers
3f9edb 536    *
T 537    * @access  private
538    * @see     rcube_imap::list_headers
539    */
31b2ce 540   function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
4e17e6 541     {
T 542     if (!strlen($mailbox))
17fc71 543       return array();
04c618 544
T 545     // use saved message set
4845a1 546     if ($this->search_string && $mailbox == $this->mailbox)
04c618 547       return $this->_list_header_set($mailbox, $this->search_set, $page, $sort_field, $sort_order);
T 548
d5342a 549     $this->_set_sort_order($sort_field, $sort_order);
4e17e6 550
1cded8 551     $max = $this->_messagecount($mailbox);
T 552     $start_msg = ($this->list_page-1) * $this->page_size;
06ec1f 553
4647e1 554     list($begin, $end) = $this->_get_message_range($max, $page);
06ec1f 555
04c618 556     // mailbox is empty
078adf 557     if ($begin >= $end)
T 558       return array();
04c618 559       
1cded8 560     $headers_sorted = FALSE;
T 561     $cache_key = $mailbox.'.msg';
562     $cache_status = $this->check_cache_status($mailbox, $cache_key);
563
564     // cache is OK, we can get all messages from local cache
565     if ($cache_status>0)
566       {
31b2ce 567       $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
1cded8 568       $headers_sorted = TRUE;
T 569       }
c4e7e4 570     // cache is dirty, sync it
T 571     else if ($this->caching_enabled && $cache_status==-1 && !$recursive)
572       {
573       $this->sync_header_index($mailbox);
574       return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
575       }
1cded8 576     else
T 577       {
578       // retrieve headers from IMAP
15a9d1 579       if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
05d180 580         {
T 581         $mymsgidx = array_slice ($msg_index, $begin, $end-$begin);
257782 582         $msgs = join(",", $mymsgidx);
1cded8 583         }
T 584       else
585         {
c4e7e4 586         $msgs = sprintf("%d:%d", $begin+1, $end);
257782 587         $msg_index = range($begin, $end);
1cded8 588         }
T 589
06ec1f 590
1cded8 591       // fetch reuested headers from server
T 592       $a_msg_headers = array();
15a9d1 593       $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
257782 594       if ($this->sort_order == 'DESC' && $headers_sorted) {  
A 595         //since the sort order is not used in the iil_c_sort function we have to do it here
596         $a_msg_headers = array_reverse($a_msg_headers);
597       }
f388a8 598       // delete cached messages with a higher index than $max+1
S 599       // Changed $max to $max+1 to fix this bug : #1484295
600       $this->clear_message_cache($cache_key, $max + 1);
1cded8 601
06ec1f 602
1cded8 603       // kick child process to sync cache
31b2ce 604       // ...
06ec1f 605
1cded8 606       }
T 607
608     // return empty array if no messages found
257782 609     if (!is_array($a_msg_headers) || empty($a_msg_headers)) {
A 610       return array();
611     }
1cded8 612
T 613     // if not already sorted
06ec1f 614     if (!$headers_sorted)
7e93ff 615       {
257782 616       // use this class for message sorting
A 617       $sorter = new rcube_header_sorter();
618       $sorter->set_sequence_numbers($msg_index);
7e93ff 619       $sorter->sort_headers($a_msg_headers);
e6f360 620
7e93ff 621       if ($this->sort_order == 'DESC')
T 622         $a_msg_headers = array_reverse($a_msg_headers);
623       }
1cded8 624
T 625     return array_values($a_msg_headers);
4e17e6 626     }
7e93ff 627
T 628
15a9d1 629
4647e1 630   /**
T 631    * Public method for listing a specific set of headers
632    * convert mailbox name with root dir first
633    *
634    * @param   string   Mailbox/folder name
635    * @param   array    List of message ids to list
6d969b 636    * @param   int      Current page to list
4647e1 637    * @param   string   Header field to sort by
T 638    * @param   string   Sort order [ASC|DESC]
639    * @return  array    Indexed array with message header objects
640    * @access  public   
641    */
aadfa1 642   function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
4647e1 643     {
aadfa1 644     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4647e1 645     return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);    
T 646     }
647     
648
649   /**
650    * Private method for listing a set of message headers
651    *
652    * @access  private
6d969b 653    * @see     rcube_imap::list_header_set()
4647e1 654    */
T 655   function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
656     {
657     if (!strlen($mailbox) || empty($msgs))
658       return array();
659
257782 660     // also accept a comma-separated list of message ids
A 661     if (is_array ($msgs)) {
662       $max = count ($msgs);
663       $msgs = join (',', $msgs);
664     } else {
665       $max = count(split(',', $msgs));
666     } 
667
d5342a 668     $this->_set_sort_order($sort_field, $sort_order);
4647e1 669
T 670     $start_msg = ($this->list_page-1) * $this->page_size;
671
672     // fetch reuested headers from server
673     $a_msg_headers = array();
c5cc38 674     $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, NULL);
4647e1 675
T 676     // return empty array if no messages found
04c618 677     if (!is_array($a_msg_headers) || empty($a_msg_headers))
T 678       return array();
4647e1 679
T 680     // if not already sorted
681     $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
682
04c618 683     // only return the requested part of the set
T 684     return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
4647e1 685     }
T 686
687
688   /**
689    * Helper function to get first and last index of the requested set
690    *
6d969b 691    * @param  int     message count
4647e1 692    * @param  mixed   page number to show, or string 'all'
T 693    * @return array   array with two values: first index, last index
694    * @access private
695    */
696   function _get_message_range($max, $page)
697     {
698     $start_msg = ($this->list_page-1) * $this->page_size;
699     
700     if ($page=='all')
701       {
702       $begin = 0;
703       $end = $max;
704       }
705     else if ($this->sort_order=='DESC')
706       {
707       $begin = $max - $this->page_size - $start_msg;
708       $end =   $max - $start_msg;
709       }
710     else
711       {
712       $begin = $start_msg;
713       $end   = $start_msg + $this->page_size;
714       }
715
716     if ($begin < 0) $begin = 0;
717     if ($end < 0) $end = $max;
718     if ($end > $max) $end = $max;
719     
720     return array($begin, $end);
721     }
722     
723     
15a9d1 724
T 725   /**
726    * Fetches message headers
727    * Used for loop
728    *
729    * @param  string  Mailbox name
4647e1 730    * @param  string  Message index to fetch
15a9d1 731    * @param  array   Reference to message headers array
T 732    * @param  array   Array with cache index
6d969b 733    * @return int     Number of deleted messages
15a9d1 734    * @access private
T 735    */
736   function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
737     {
738     // cache is incomplete
739     $cache_index = $this->get_message_cache_index($cache_key);
4647e1 740     
15a9d1 741     // fetch reuested headers from server
T 742     $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
743     $deleted_count = 0;
744     
745     if (!empty($a_header_index))
746       {
747       foreach ($a_header_index as $i => $headers)
748         {
749         if ($headers->deleted && $this->skip_deleted)
750           {
751           // delete from cache
752           if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
753             $this->remove_message_cache($cache_key, $headers->id);
754
755           $deleted_count++;
756           continue;
757           }
758
759         // add message to cache
760         if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
761           $this->add_message_cache($cache_key, $headers->id, $headers);
762
763         $a_msg_headers[$headers->uid] = $headers;
764         }
765       }
766         
767     return $deleted_count;
768     }
769     
e6f360 770   
8d4bcd 771   /**
T 772    * Return sorted array of message UIDs
773    *
774    * @param string Mailbox to get index from
775    * @param string Sort column
776    * @param string Sort order [ASC, DESC]
777    * @return array Indexed array with message ids
778    */
aadfa1 779   function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
4e17e6 780     {
d5342a 781     $this->_set_sort_order($sort_field, $sort_order);
31b2ce 782
aadfa1 783     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
df0da2 784     $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi";
T 785
786     // we have a saved search result. get index from there
787     if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox)
788     {
789       $this->cache[$key] = $a_msg_headers = array();
790       $this->_fetch_headers($mailbox, join(',', $this->search_set), $a_msg_headers, NULL);
791
792       foreach (iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order) as $i => $msg)
793         $this->cache[$key][] = $msg->uid;
794     }
4e17e6 795
31b2ce 796     // have stored it in RAM
T 797     if (isset($this->cache[$key]))
798       return $this->cache[$key];
4e17e6 799
31b2ce 800     // check local cache
T 801     $cache_key = $mailbox.'.msg';
802     $cache_status = $this->check_cache_status($mailbox, $cache_key);
4e17e6 803
31b2ce 804     // cache is OK
T 805     if ($cache_status>0)
806       {
0677ca 807       $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
31b2ce 808       return array_values($a_index);
T 809       }
810
811
812     // fetch complete message index
813     $msg_count = $this->_messagecount($mailbox);
e6f360 814     if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
31b2ce 815       {
T 816       if ($this->sort_order == 'DESC')
817         $a_index = array_reverse($a_index);
818
e6f360 819       $this->cache[$key] = $a_index;
T 820
31b2ce 821       }
T 822     else
823       {
824       $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
825       $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
826     
827       if ($this->sort_order=="ASC")
828         asort($a_index);
829       else if ($this->sort_order=="DESC")
830         arsort($a_index);
831         
832       $i = 0;
833       $this->cache[$key] = array();
834       foreach ($a_index as $index => $value)
835         $this->cache[$key][$i++] = $a_uids[$index];
836       }
837
838     return $this->cache[$key];
4e17e6 839     }
T 840
841
6d969b 842   /**
T 843    * @access private
844    */
1cded8 845   function sync_header_index($mailbox)
4e17e6 846     {
1cded8 847     $cache_key = $mailbox.'.msg';
T 848     $cache_index = $this->get_message_cache_index($cache_key);
849     $msg_count = $this->_messagecount($mailbox);
850
851     // fetch complete message index
852     $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
853         
854     foreach ($a_message_index as $id => $uid)
855       {
856       // message in cache at correct position
857       if ($cache_index[$id] == $uid)
858         {
859         unset($cache_index[$id]);
860         continue;
861         }
862         
863       // message in cache but in wrong position
864       if (in_array((string)$uid, $cache_index, TRUE))
865         {
866         unset($cache_index[$id]);        
867         }
868       
869       // other message at this position
870       if (isset($cache_index[$id]))
871         {
872         $this->remove_message_cache($cache_key, $id);
873         unset($cache_index[$id]);
874         }
875         
876
877       // fetch complete headers and add to cache
878       $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
879       $this->add_message_cache($cache_key, $headers->id, $headers);
880       }
881
882     // those ids that are still in cache_index have been deleted      
883     if (!empty($cache_index))
884       {
885       foreach ($cache_index as $id => $uid)
886         $this->remove_message_cache($cache_key, $id);
887       }
4e17e6 888     }
T 889
890
4647e1 891   /**
T 892    * Invoke search request to IMAP server
893    *
894    * @param  string  mailbox name to search in
895    * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
896    * @param  string  search string
897    * @return array   search results as list of message ids
898    * @access public
899    */
42000a 900   function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
4e17e6 901     {
aadfa1 902     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
e0c974 903     $search = '';
04c618 904
e0c974 905     // have an array of criterias => create search string
A 906     if (is_array($criteria))
907       $search .= 'OR';
42000a 908
e0c974 909     $criteria = (array) $criteria;
A 910     foreach($criteria as $idx => $crit)
911       if ($str)
912         $search .= sprintf(" (%s {%d}\r\n%s)", $crit, strlen($str), $str);
913       else
914         $search .= '('. $crit .')';
915
916     $results = $this->_search_index($mailbox, (!empty($charset) ? "CHARSET $charset " : '') . $search);
917
918     // try search with ISO charset (should be supported by server)
919     if (empty($results) && !empty($charset) && $charset!='ISO-8859-1')
920       $results = $this->search($mbox_name, $criteria, rcube_charset_convert($str, $charset, 'ISO-8859-1'), 'ISO-8859-1');
921
922     $this->set_search_set($criteria, $str, $results, $charset);
923     return $results;
4647e1 924     }    
T 925
926
927   /**
928    * Private search method
929    *
930    * @return array   search results as list of message ids
931    * @access private
932    * @see rcube_imap::search()
933    */
31b2ce 934   function _search_index($mailbox, $criteria='ALL')
T 935     {
4e17e6 936     $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
e0c974 937
4647e1 938     // clean message list (there might be some empty entries)
4f2d81 939     if (is_array($a_messages))
T 940       {
941       foreach ($a_messages as $i => $val)
942         if (empty($val))
943           unset($a_messages[$i]);
944       }
4647e1 945         
4e17e6 946     return $a_messages;
04c618 947     }
T 948     
949   
950   /**
951    * Refresh saved search set
6d969b 952    *
T 953    * @return array Current search set
04c618 954    */
T 955   function refresh_search()
956     {
957     if (!empty($this->search_subject) && !empty($this->search_string))
958       $this->search_set = $this->search('', $this->search_subject, $this->search_string, $this->search_charset);
959       
960     return $this->get_search_set();
4e17e6 961     }
110748 962   
T 963   
964   /**
965    * Check if the given message ID is part of the current search set
966    *
ed5407 967    * @return boolean True on match or if no search request is stored
110748 968    */
T 969   function in_searchset($msgid)
970   {
971     if (!empty($this->search_string))
972       return in_array("$msgid", (array)$this->search_set, true);
973     else
974       return true;
975   }
4e17e6 976
T 977
8d4bcd 978   /**
T 979    * Return message headers object of a specific message
980    *
981    * @param int     Message ID
982    * @param string  Mailbox to read from 
983    * @param boolean True if $id is the message UID
984    * @return object Message headers representation
985    */
aadfa1 986   function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
4e17e6 987     {
aadfa1 988     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
8d4bcd 989     $uid = $is_uid ? $id : $this->_id2uid($id);
1cded8 990
4e17e6 991     // get cached headers
f7bfec 992     if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
1cded8 993       return $headers;
520c36 994
f7bfec 995     $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid);
520c36 996
4e17e6 997     // write headers cache
1cded8 998     if ($headers)
f7bfec 999       {
017def 1000       if ($headers->uid && $headers->id)
T 1001         $this->uid_id_map[$mailbox][$headers->uid] = $headers->id;
f7bfec 1002
T 1003       $this->add_message_cache($mailbox.'.msg', $headers->id, $headers);
1004       }
4e17e6 1005
1cded8 1006     return $headers;
4e17e6 1007     }
T 1008
1009
8d4bcd 1010   /**
T 1011    * Fetch body structure from the IMAP server and build
1012    * an object structure similar to the one generated by PEAR::Mail_mimeDecode
1013    *
6d969b 1014    * @param int Message UID to fetch
e93e54 1015    * @return object rcube_message_part Message part tree or False on failure
8d4bcd 1016    */
T 1017   function &get_structure($uid)
4e17e6 1018     {
f7bfec 1019     $cache_key = $this->mailbox.'.msg';
T 1020     $headers = &$this->get_cached_message($cache_key, $uid, true);
1021
1022     // return cached message structure
c9ca6a 1023     if (is_object($headers) && is_object($headers->structure)) {
f7bfec 1024       return $headers->structure;
c9ca6a 1025     }
4e17e6 1026
c9ca6a 1027     // resolve message sequence number
S 1028     if (!($msg_id = $this->_uid2id($uid))) {
1029       return FALSE;
1030     }
1031
1032     $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
5cc4b1 1033     $structure = iml_GetRawStructureArray($structure_str);
T 1034     $struct = false;
1035
8d4bcd 1036     // parse structure and add headers
T 1037     if (!empty($structure))
1038       {
1039       $this->_msg_id = $msg_id;
017def 1040       $headers = $this->get_headers($uid);
8d4bcd 1041       
T 1042       $struct = &$this->_structure_part($structure);
1043       $struct->headers = get_object_vars($headers);
58afbe 1044
8d4bcd 1045       // don't trust given content-type
58afbe 1046       if (empty($struct->parts) && !empty($struct->headers['ctype']))
8d4bcd 1047         {
T 1048         $struct->mime_id = '1';
1049         $struct->mimetype = strtolower($struct->headers['ctype']);
1050         list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
1051         }
f7bfec 1052
T 1053       // write structure to cache
1054       if ($this->caching_enabled)
1055         $this->add_message_cache($cache_key, $msg_id, $headers, $struct);
8d4bcd 1056       }
94a99c 1057
5cc4b1 1058     return $struct;
T 1059     }
4e17e6 1060
8d4bcd 1061   
T 1062   /**
1063    * Build message part object
1064    *
1065    * @access private
1066    */
1067   function &_structure_part($part, $count=0, $parent='')
1068     {
1069     $struct = new rcube_message_part;
1070     $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
4e17e6 1071     
8d4bcd 1072     // multipart
T 1073     if (is_array($part[0]))
1074       {
1075       $struct->ctype_primary = 'multipart';
1076       
1077       // find first non-array entry
cfe4a6 1078       for ($i=1; $i<count($part); $i++)
8d4bcd 1079         if (!is_array($part[$i]))
T 1080           {
1081           $struct->ctype_secondary = strtolower($part[$i]);
1082           break;
1083           }
1084           
1085       $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
1086
1087       $struct->parts = array();
1088       for ($i=0, $count=0; $i<count($part); $i++)
cfe4a6 1089         if (is_array($part[$i]) && count($part[$i]) > 3)
8d4bcd 1090           $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id);
5cc4b1 1091           
T 1092       return $struct;
8d4bcd 1093       }
T 1094     
1095     
1096     // regular part
1097     $struct->ctype_primary = strtolower($part[0]);
1098     $struct->ctype_secondary = strtolower($part[1]);
1099     $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
5cc4b1 1100
8d4bcd 1101     // read content type parameters
5cc4b1 1102     if (is_array($part[2]))
T 1103       {
1104       $struct->ctype_parameters = array();
8d4bcd 1105       for ($i=0; $i<count($part[2]); $i+=2)
T 1106         $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
1107         
1108       if (isset($struct->ctype_parameters['charset']))
1109         $struct->charset = $struct->ctype_parameters['charset'];
5cc4b1 1110       }
T 1111     
1112     // read content encoding
1113     if (!empty($part[5]) && $part[5]!='NIL')
1114       {
1115       $struct->encoding = strtolower($part[5]);
1116       $struct->headers['content-transfer-encoding'] = $struct->encoding;
1117       }
1118     
1119     // get part size
1120     if (!empty($part[6]) && $part[6]!='NIL')
1121       $struct->size = intval($part[6]);
2f2f15 1122
5cc4b1 1123     // read part disposition
ea206d 1124     $di = count($part) - 2;
2f2f15 1125     if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
T 1126         (is_array($part[--$di]) && count($part[$di]) == 2))
8d4bcd 1127       {
T 1128       $struct->disposition = strtolower($part[$di][0]);
1129
1130       if (is_array($part[$di][1]))
1131         for ($n=0; $n<count($part[$di][1]); $n+=2)
1132           $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
1133       }
1134       
1135     // get child parts
1136     if (is_array($part[8]) && $di != 8)
1137       {
1138       $struct->parts = array();
1139       for ($i=0, $count=0; $i<count($part[8]); $i++)
1140         if (is_array($part[8][$i]) && count($part[8][$i]) > 5)
1141           $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
1142       }
5cc4b1 1143
T 1144     // get part ID
1145     if (!empty($part[3]) && $part[3]!='NIL')
1146       {
1147       $struct->content_id = $part[3];
1148       $struct->headers['content-id'] = $part[3];
1149     
1150       if (empty($struct->disposition))
1151         $struct->disposition = 'inline';
1152       }
c505e5 1153     
T 1154     // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
1155     if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
1156       $part_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id);
1157       $struct->headers = $this->_parse_headers($part_headers) + $struct->headers;
1158     }
8d4bcd 1159
c505e5 1160     if ($struct->ctype_primary=='message') {
5cc4b1 1161       if (is_array($part[8]) && empty($struct->parts))
T 1162         $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
c505e5 1163     }
b54121 1164
5cc4b1 1165     // normalize filename property
5df0ad 1166     $this->_set_part_filename($struct);
A 1167
5cc4b1 1168     return $struct;
8d4bcd 1169     }
T 1170     
5df0ad 1171
A 1172   /**
97e9d1 1173    * Set attachment filename from message part structure 
5df0ad 1174    *
A 1175    * @access private
1176    * @param  object rcube_message_part Part object
1177    */
1178   function _set_part_filename(&$part)
1179     {
1180     if (!empty($part->d_parameters['filename']))
1181       $filename_mime = $part->d_parameters['filename'];
1182     else if (!empty($part->d_parameters['filename*']))
1183       $filename_encoded = $part->d_parameters['filename*'];
1184     else if (!empty($part->ctype_parameters['name*']))
1185       $filename_encoded = $part->ctype_parameters['name*'];
1186     // RFC2231 value continuations
1187     // TODO: this should be rewrited to support RFC2231 4.1 combinations
1188     else if (!empty($part->d_parameters['filename*0'])) {
1189       $i = 0;
1190       while (isset($part->d_parameters['filename*'.$i])) {
1191         $filename_mime .= $part->d_parameters['filename*'.$i];
28db73 1192         $i++;
A 1193       }
5df0ad 1194       // some servers (eg. dovecot-1.x) have no support for parameter value continuations
A 1195       // we must fetch and parse headers "manually"
20a251 1196       //TODO: fetching headers for a second time is not effecient, this code should be moved somewhere earlier --tensor
5df0ad 1197       if ($i<2) {
A 1198         // TODO: fetch only Content-Type/Content-Disposition header
20a251 1199         $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
42e328 1200         $filename_mime = '';
T 1201         $i = 0;
1202         while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1203           $filename_mime .= $matches[1];
1204           $i++;
1205         }
5df0ad 1206       }
42e328 1207     }
5df0ad 1208     else if (!empty($part->d_parameters['filename*0*'])) {
A 1209       $i = 0;
1210       while (isset($part->d_parameters['filename*'.$i.'*'])) {
42e328 1211         $filename_encoded .= $part->d_parameters['filename*'.$i.'*'];
28db73 1212         $i++;
42e328 1213       }
5df0ad 1214       if ($i<2) {
20a251 1215         $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
42e328 1216         $filename_encoded = '';
T 1217         $i = 0; $matches = array();
1218         while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1219           $filename_encoded .= $matches[1];
1220           $i++;
1221         }
5df0ad 1222       }
42e328 1223     }
5df0ad 1224     else if (!empty($part->ctype_parameters['name*0'])) {
A 1225       $i = 0;
1226       while (isset($part->ctype_parameters['name*'.$i])) {
1227         $filename_mime .= $part->ctype_parameters['name*'.$i];
28db73 1228         $i++;
42e328 1229       }
5df0ad 1230       if ($i<2) {
20a251 1231         $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
42e328 1232         $filename_mime = '';
T 1233         $i = 0; $matches = array();
1234         while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1235           $filename_mime .= $matches[1];
1236           $i++;
1237         }
5df0ad 1238       }
42e328 1239     }
5df0ad 1240     else if (!empty($part->ctype_parameters['name*0*'])) {
A 1241       $i = 0;
1242       while (isset($part->ctype_parameters['name*'.$i.'*'])) {
1243         $filename_encoded .= $part->ctype_parameters['name*'.$i.'*'];
28db73 1244         $i++;
42e328 1245       }
5df0ad 1246       if ($i<2) {
20a251 1247         $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
42e328 1248         $filename_encoded = '';
T 1249         $i = 0; $matches = array();
1250         while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1251           $filename_encoded .= $matches[1];
1252           $i++;
1253         }
5df0ad 1254       }
42e328 1255     }
5b4562 1256     // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird)
A 1257     else if (!empty($part->ctype_parameters['name']))
1258       $filename_mime = $part->ctype_parameters['name'];
5df0ad 1259     // Content-Disposition
A 1260     else if (!empty($part->headers['content-description']))
1261       $filename_mime = $part->headers['content-description'];
1262     else
1263       return;
1264
1265     // decode filename
1266     if (!empty($filename_mime)) {
1267       $part->filename = rcube_imap::decode_mime_string($filename_mime, 
42e328 1268         $part->charset ? $part->charset : rc_detect_encoding($filename_mime, $this->default_charset));
5df0ad 1269       } 
A 1270     else if (!empty($filename_encoded)) {
1271       // decode filename according to RFC 2231, Section 4
94a99c 1272       if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) {
A 1273         $filename_charset = $fmatches[1];
1274         $filename_encoded = $fmatches[2];
1275         }
1276       $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset);
5df0ad 1277       }
A 1278     }
1279      
8d4bcd 1280   
T 1281   /**
1282    * Fetch message body of a specific message from the server
1283    *
1284    * @param  int    Message UID
1285    * @param  string Part number
6d969b 1286    * @param  object rcube_message_part Part object created by get_structure()
8d4bcd 1287    * @param  mixed  True to print part, ressource to write part contents in
81b573 1288    * @param  resource File pointer to save the message part
6d969b 1289    * @return string Message/part body if not printed
8d4bcd 1290    */
81b573 1291   function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL)
8d4bcd 1292     {
T 1293     if (!($msg_id = $this->_uid2id($uid)))
1294       return FALSE;
1295     
1296     // get part encoding if not provided
1297     if (!is_object($o_part))
1298       {
1299       $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
1300       $structure = iml_GetRawStructureArray($structure_str);
1301       $part_type = iml_GetPartTypeCode($structure, $part);
1302       $o_part = new rcube_message_part;
1303       $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other');
1304       $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part));
1305       $o_part->charset = iml_GetPartCharset($structure, $part);
1306       }
1307       
1308     // TODO: Add caching for message parts
1309
1310     if ($print)
1311       {
a9cc52 1312       $mode = $o_part->encoding == 'base64' ? 3 : ($o_part->encoding == 'quoted-printable' ? 1 : 2);
T 1313       $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, $mode);
1314       
1315       // we have to decode the part manually before printing
1316       if ($mode == 1)
1317         {
1318         echo $this->mime_decode($body, $o_part->encoding);
1319         $body = true;
1320         }
8d4bcd 1321       }
T 1322     else
1323       {
81b573 1324       if ($fp && $o_part->encoding == 'base64')
A 1325         return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 3, $fp);
1326       else
1327         $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1);
8d4bcd 1328
T 1329       // decode part body
a9cc52 1330       if ($o_part->encoding)
8d4bcd 1331         $body = $this->mime_decode($body, $o_part->encoding);
T 1332
1333       // convert charset (if text or message part)
f7bfec 1334       if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message')
T 1335         {
17b5fb 1336         // assume default if no charset specified
f7bfec 1337         if (empty($o_part->charset))
17b5fb 1338           $o_part->charset = $this->default_charset;
f7bfec 1339
8d4bcd 1340         $body = rcube_charset_convert($body, $o_part->charset);
f7bfec 1341         }
81b573 1342       
A 1343       if ($fp)
1344         {
1345         fwrite($fp, $body);
42e328 1346         return true;
81b573 1347         }
8d4bcd 1348       }
81b573 1349     
4e17e6 1350     return $body;
T 1351     }
1352
1353
8d4bcd 1354   /**
T 1355    * Fetch message body of a specific message from the server
1356    *
1357    * @param  int    Message UID
6d969b 1358    * @return string Message/part body
T 1359    * @see    rcube_imap::get_message_part()
8d4bcd 1360    */
T 1361   function &get_body($uid, $part=1)
1362     {
0a9989 1363     $headers = $this->get_headers($uid);
T 1364     return rcube_charset_convert(
1365       $this->mime_decode($this->get_message_part($uid, $part), 'quoted-printable'),
1366       $headers->charset ? $headers->charset : $this->default_charset);
8d4bcd 1367     }
T 1368
1369
1370   /**
1371    * Returns the whole message source as string
1372    *
1373    * @param int  Message UID
6d969b 1374    * @return string Message source string
8d4bcd 1375    */
T 1376   function &get_raw_body($uid)
4e17e6 1377     {
T 1378     if (!($msg_id = $this->_uid2id($uid)))
1379       return FALSE;
1380
6d969b 1381     $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
T 1382     $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
4e17e6 1383
T 1384     return $body;    
1385     }
e5686f 1386
A 1387
1388   /**
1389    * Returns the message headers as string
1390    *
1391    * @param int  Message UID
1392    * @return string Message headers string
1393    */
1394   function &get_raw_headers($uid)
1395     {
1396     if (!($msg_id = $this->_uid2id($uid)))
1397       return FALSE;
1398
1399     $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
1400
1401     return $headers;    
1402     }
8d4bcd 1403     
T 1404
1405   /**
1406    * Sends the whole message source to stdout
1407    *
1408    * @param int  Message UID
1409    */ 
1410   function print_raw_body($uid)
1411     {
1412     if (!($msg_id = $this->_uid2id($uid)))
1413       return FALSE;
1414
6d969b 1415     print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
T 1416     flush();
1417     iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
8d4bcd 1418     }
4e17e6 1419
T 1420
8d4bcd 1421   /**
T 1422    * Set message flag to one or several messages
1423    *
1424    * @param mixed  Message UIDs as array or as comma-separated string
ed5407 1425    * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
6d969b 1426    * @return boolean True on success, False on failure
8d4bcd 1427    */
4e17e6 1428   function set_flag($uids, $flag)
T 1429     {
1430     $flag = strtoupper($flag);
1431     $msg_ids = array();
1432     if (!is_array($uids))
8fae1e 1433       $uids = explode(',',$uids);
4e17e6 1434       
8fae1e 1435     foreach ($uids as $uid) {
31b2ce 1436       $msg_ids[$uid] = $this->_uid2id($uid);
8fae1e 1437     }
4e17e6 1438       
8fae1e 1439     if ($flag=='UNDELETED')
S 1440       $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
1441     else if ($flag=='UNSEEN')
31b2ce 1442       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
e189a6 1443     else if ($flag=='UNFLAGGED')
A 1444       $result = iil_C_UnFlag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), 'FLAGGED');
4e17e6 1445     else
31b2ce 1446       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
4e17e6 1447
T 1448     // reload message headers if cached
1449     $cache_key = $this->mailbox.'.msg';
1cded8 1450     if ($this->caching_enabled)
4e17e6 1451       {
31b2ce 1452       foreach ($msg_ids as $uid => $id)
4e17e6 1453         {
31b2ce 1454         if ($cached_headers = $this->get_cached_message($cache_key, $uid))
4e17e6 1455           {
1cded8 1456           $this->remove_message_cache($cache_key, $id);
T 1457           //$this->get_headers($uid);
4e17e6 1458           }
T 1459         }
1cded8 1460
T 1461       // close and re-open connection
1462       // this prevents connection problems with Courier 
1463       $this->reconnect();
4e17e6 1464       }
T 1465
1466     // set nr of messages that were flaged
31b2ce 1467     $count = count($msg_ids);
4e17e6 1468
T 1469     // clear message count cache
1470     if ($result && $flag=='SEEN')
1471       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
1472     else if ($result && $flag=='UNSEEN')
1473       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
1474     else if ($result && $flag=='DELETED')
1475       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
1476
1477     return $result;
1478     }
1479
1480
6d969b 1481   /**
T 1482    * Append a mail message (source) to a specific mailbox
1483    *
1484    * @param string Target mailbox
1485    * @param string Message source
1486    * @return boolean True on success, False on error
1487    */
b068a0 1488   function save_message($mbox_name, &$message)
4e17e6 1489     {
a894ba 1490     $mbox_name = stripslashes($mbox_name);
aadfa1 1491     $mailbox = $this->_mod_mailbox($mbox_name);
4e17e6 1492
f88d41 1493     // make sure mailbox exists
47c9cc 1494     if (($mailbox == 'INBOX') || in_array($mailbox, $this->_list_mailboxes()))
4e17e6 1495       $saved = iil_C_Append($this->conn, $mailbox, $message);
1cded8 1496
4e17e6 1497     if ($saved)
T 1498       {
1499       // increase messagecount of the target mailbox
1500       $this->_set_messagecount($mailbox, 'ALL', 1);
1501       }
1502           
1503     return $saved;
1504     }
1505
1506
6d969b 1507   /**
T 1508    * Move a message from one mailbox to another
1509    *
1510    * @param string List of UIDs to move, separated by comma
1511    * @param string Target mailbox
1512    * @param string Source mailbox
1513    * @return boolean True on success, False on error
1514    */
4e17e6 1515   function move_message($uids, $to_mbox, $from_mbox='')
T 1516     {
a05793 1517     $to_mbox_in = stripslashes($to_mbox);
a894ba 1518     $from_mbox = stripslashes($from_mbox);
a05793 1519     $to_mbox = $this->_mod_mailbox($to_mbox_in);
4e17e6 1520     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
T 1521
f88d41 1522     // make sure mailbox exists
4e17e6 1523     if (!in_array($to_mbox, $this->_list_mailboxes()))
f88d41 1524       {
a05793 1525       if (in_array($to_mbox_in, $this->default_folders))
T 1526         $this->create_mailbox($to_mbox_in, TRUE);
f88d41 1527       else
T 1528         return FALSE;
1529       }
1530
4e17e6 1531     // convert the list of uids to array
T 1532     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1533     
1534     // exit if no message uids are specified
1535     if (!is_array($a_uids))
1536       return false;
520c36 1537
4e17e6 1538     // convert uids to message ids
T 1539     $a_mids = array();
1540     foreach ($a_uids as $uid)
1541       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 1542
4379bb 1543     $iil_move = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 1544     $moved = !($iil_move === false || $iil_move < 0);
4e17e6 1545     
T 1546     // send expunge command in order to have the moved message
1547     // really deleted from the source mailbox
bf0cb9 1548     if ($moved) {
d87fc2 1549       // but only when flag_for_deletion is set to false
A 1550       if (!rcmail::get_instance()->config->get('flag_for_deletion', false))
1551         {
1552         $this->_expunge($from_mbox, FALSE);
1553         $this->_clear_messagecount($from_mbox);
1554         $this->_clear_messagecount($to_mbox);
1555         }
bf0cb9 1556     }
T 1557     // moving failed
1558     else if (rcmail::get_instance()->config->get('delete_always', false)) {
1559       return iil_C_Delete($this->conn, $from_mbox, join(',', $a_mids));
1560     }
04c618 1561       
T 1562     // remove message ids from search set
1563     if ($moved && $this->search_set && $from_mbox == $this->mailbox)
1564       $this->search_set = array_diff($this->search_set, $a_mids);
4e17e6 1565
T 1566     // update cached message headers
1567     $cache_key = $from_mbox.'.msg';
1cded8 1568     if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1569       {
1cded8 1570       $start_index = 100000;
4e17e6 1571       foreach ($a_uids as $uid)
1cded8 1572         {
04c618 1573         if (($index = array_search($uid, $a_cache_index)) !== FALSE)
T 1574           $start_index = min($index, $start_index);
1cded8 1575         }
4e17e6 1576
1cded8 1577       // clear cache from the lowest index on
T 1578       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1579       }
T 1580
1581     return $moved;
1582     }
1583
1584
6d969b 1585   /**
T 1586    * Mark messages as deleted and expunge mailbox
1587    *
1588    * @param string List of UIDs to move, separated by comma
1589    * @param string Source mailbox
1590    * @return boolean True on success, False on error
1591    */
aadfa1 1592   function delete_message($uids, $mbox_name='')
4e17e6 1593     {
a894ba 1594     $mbox_name = stripslashes($mbox_name);
aadfa1 1595     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4e17e6 1596
T 1597     // convert the list of uids to array
1598     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1599     
1600     // exit if no message uids are specified
1601     if (!is_array($a_uids))
1602       return false;
1603
1604     // convert uids to message ids
1605     $a_mids = array();
1606     foreach ($a_uids as $uid)
1607       $a_mids[] = $this->_uid2id($uid, $mailbox);
1608         
1609     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1610     
1611     // send expunge command in order to have the deleted message
1612     // really deleted from the mailbox
1613     if ($deleted)
1614       {
1cded8 1615       $this->_expunge($mailbox, FALSE);
4e17e6 1616       $this->_clear_messagecount($mailbox);
c719f3 1617       unset($this->uid_id_map[$mailbox]);
4e17e6 1618       }
T 1619
04c618 1620     // remove message ids from search set
077070 1621     if ($deleted && $this->search_set && $mailbox == $this->mailbox)
04c618 1622       $this->search_set = array_diff($this->search_set, $a_mids);
T 1623
4e17e6 1624     // remove deleted messages from cache
1cded8 1625     $cache_key = $mailbox.'.msg';
T 1626     if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1627       {
1cded8 1628       $start_index = 100000;
4e17e6 1629       foreach ($a_uids as $uid)
1cded8 1630         {
6ce04b 1631         if (($index = array_search($uid, $a_cache_index)) !== FALSE)
S 1632           $start_index = min($index, $start_index);
1cded8 1633         }
4e17e6 1634
1cded8 1635       // clear cache from the lowest index on
T 1636       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1637       }
T 1638
1639     return $deleted;
1640     }
1641
1642
6d969b 1643   /**
T 1644    * Clear all messages in a specific mailbox
1645    *
1646    * @param string Mailbox name
1647    * @return int Above 0 on success
1648    */
aadfa1 1649   function clear_mailbox($mbox_name=NULL)
a95e0e 1650     {
a894ba 1651     $mbox_name = stripslashes($mbox_name);
aadfa1 1652     $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
a95e0e 1653     $msg_count = $this->_messagecount($mailbox, 'ALL');
T 1654     
1655     if ($msg_count>0)
1cded8 1656       {
5e3512 1657       $cleared = iil_C_ClearFolder($this->conn, $mailbox);
T 1658       
1659       // make sure the message count cache is cleared as well
1660       if ($cleared)
1661         {
1662         $this->clear_message_cache($mailbox.'.msg');      
1663         $a_mailbox_cache = $this->get_cache('messagecount');
1664         unset($a_mailbox_cache[$mailbox]);
1665         $this->update_cache('messagecount', $a_mailbox_cache);
1666         }
1667         
1668       return $cleared;
1cded8 1669       }
a95e0e 1670     else
T 1671       return 0;
1672     }
1673
1674
6d969b 1675   /**
T 1676    * Send IMAP expunge command and clear cache
1677    *
1678    * @param string Mailbox name
1679    * @param boolean False if cache should not be cleared
1680    * @return boolean True on success
1681    */
aadfa1 1682   function expunge($mbox_name='', $clear_cache=TRUE)
4e17e6 1683     {
a894ba 1684     $mbox_name = stripslashes($mbox_name);
aadfa1 1685     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1cded8 1686     return $this->_expunge($mailbox, $clear_cache);
T 1687     }
1688
1689
6d969b 1690   /**
T 1691    * Send IMAP expunge command and clear cache
1692    *
1693    * @see rcube_imap::expunge()
1694    * @access private
1695    */
1cded8 1696   function _expunge($mailbox, $clear_cache=TRUE)
T 1697     {
4e17e6 1698     $result = iil_C_Expunge($this->conn, $mailbox);
T 1699
1700     if ($result>=0 && $clear_cache)
1701       {
5c69aa 1702       $this->clear_message_cache($mailbox.'.msg');
4e17e6 1703       $this->_clear_messagecount($mailbox);
T 1704       }
1705       
1706     return $result;
1707     }
1708
1709
1710   /* --------------------------------
1711    *        folder managment
1712    * --------------------------------*/
1713
1714
fa4cd2 1715   /**
T 1716    * Get a list of all folders available on the IMAP server
1717    * 
1718    * @param string IMAP root dir
6d969b 1719    * @return array Indexed array with folder names
fa4cd2 1720    */
4e17e6 1721   function list_unsubscribed($root='')
T 1722     {
1723     static $sa_unsubscribed;
1724     
1725     if (is_array($sa_unsubscribed))
1726       return $sa_unsubscribed;
1727       
1728     // retrieve list of folders from IMAP server
1729     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1730
1731     // modify names with root dir
aadfa1 1732     foreach ($a_mboxes as $mbox_name)
4e17e6 1733       {
aadfa1 1734       $name = $this->_mod_mailbox($mbox_name, 'out');
4e17e6 1735       if (strlen($name))
T 1736         $a_folders[] = $name;
1737       }
1738
1739     // filter folders and sort them
1740     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1741     return $sa_unsubscribed;
1742     }
1743
1744
58e360 1745   /**
6d969b 1746    * Get mailbox quota information
58e360 1747    * added by Nuny
6d969b 1748    * 
T 1749    * @return mixed Quota info or False if not supported
58e360 1750    */
T 1751   function get_quota()
1752     {
1753     if ($this->get_capability('QUOTA'))
fda695 1754       return iil_C_GetQuota($this->conn);
3ea0e3 1755     
4647e1 1756     return FALSE;
58e360 1757     }
T 1758
1759
fa4cd2 1760   /**
6d969b 1761    * Subscribe to a specific mailbox(es)
T 1762    *
c5097c 1763    * @param array Mailbox name(s)
6d969b 1764    * @return boolean True on success
fa4cd2 1765    */ 
c5097c 1766   function subscribe($a_mboxes)
4e17e6 1767     {
c5097c 1768     if (!is_array($a_mboxes))
A 1769       $a_mboxes = array($a_mboxes);
1770
4e17e6 1771     // let this common function do the main work
T 1772     return $this->_change_subscription($a_mboxes, 'subscribe');
1773     }
1774
1775
fa4cd2 1776   /**
6d969b 1777    * Unsubscribe mailboxes
T 1778    *
c5097c 1779    * @param array Mailbox name(s)
6d969b 1780    * @return boolean True on success
fa4cd2 1781    */
c5097c 1782   function unsubscribe($a_mboxes)
4e17e6 1783     {
c5097c 1784     if (!is_array($a_mboxes))
A 1785       $a_mboxes = array($a_mboxes);
4e17e6 1786
T 1787     // let this common function do the main work
1788     return $this->_change_subscription($a_mboxes, 'unsubscribe');
1789     }
1790
1791
fa4cd2 1792   /**
4d4264 1793    * Create a new mailbox on the server and register it in local cache
T 1794    *
1795    * @param string  New mailbox name (as utf-7 string)
1796    * @param boolean True if the new mailbox should be subscribed
1797    * @param string  Name of the created mailbox, false on error
fa4cd2 1798    */
4e17e6 1799   function create_mailbox($name, $subscribe=FALSE)
T 1800     {
1801     $result = FALSE;
1cded8 1802     
T 1803     // replace backslashes
1804     $name = preg_replace('/[\\\]+/', '-', $name);
1805
1806     // reduce mailbox name to 100 chars
4d4264 1807     $name = substr($name, 0, 100);
1cded8 1808
4d4264 1809     $abs_name = $this->_mod_mailbox($name);
4e17e6 1810     $a_mailbox_cache = $this->get_cache('mailboxes');
fa4cd2 1811
719a25 1812     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 1813       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 1814
fa4cd2 1815     // try to subscribe it
719a25 1816     if ($result && $subscribe)
4d4264 1817       $this->subscribe($name);
4e17e6 1818
7902df 1819     return $result ? $name : FALSE;
4e17e6 1820     }
T 1821
1822
fa4cd2 1823   /**
4d4264 1824    * Set a new name to an existing mailbox
T 1825    *
1826    * @param string Mailbox to rename (as utf-7 string)
1827    * @param string New mailbox name (as utf-7 string)
6d969b 1828    * @return string Name of the renames mailbox, False on error
fa4cd2 1829    */
f9c107 1830   function rename_mailbox($mbox_name, $new_name)
4e17e6 1831     {
c8c1e0 1832     $result = FALSE;
S 1833
1834     // replace backslashes
1835     $name = preg_replace('/[\\\]+/', '-', $new_name);
f9c107 1836         
T 1837     // encode mailbox name and reduce it to 100 chars
4d4264 1838     $name = substr($new_name, 0, 100);
c8c1e0 1839
f9c107 1840     // make absolute path
T 1841     $mailbox = $this->_mod_mailbox($mbox_name);
4d4264 1842     $abs_name = $this->_mod_mailbox($name);
f7bfec 1843     
T 1844     // check if mailbox is subscribed
1845     $a_subscribed = $this->_list_mailboxes();
1846     $subscribed = in_array($mailbox, $a_subscribed);
1847     
1848     // unsubscribe folder
1849     if ($subscribed)
1850       iil_C_UnSubscribe($this->conn, $mailbox);
4d4264 1851
f9c107 1852     if (strlen($abs_name))
T 1853       $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
4d4264 1854
f9c107 1855     if ($result)
T 1856       {
574564 1857       $delm = $this->get_hierarchy_delimiter();
T 1858       
1859       // check if mailbox children are subscribed
1860       foreach ($a_subscribed as $c_subscribed)
1861         if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed))
1862           {
1863           iil_C_UnSubscribe($this->conn, $c_subscribed);
1864           iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed));
1865           }
1866
1867       // clear cache
f9c107 1868       $this->clear_message_cache($mailbox.'.msg');
f7bfec 1869       $this->clear_cache('mailboxes');      
f9c107 1870       }
f7bfec 1871
4d4264 1872     // try to subscribe it
f7bfec 1873     if ($result && $subscribed)
T 1874       iil_C_Subscribe($this->conn, $abs_name);
c8c1e0 1875
S 1876     return $result ? $name : FALSE;
4e17e6 1877     }
T 1878
1879
fa4cd2 1880   /**
6d969b 1881    * Remove mailboxes from server
T 1882    *
1883    * @param string Mailbox name
1884    * @return boolean True on success
fa4cd2 1885    */
aadfa1 1886   function delete_mailbox($mbox_name)
4e17e6 1887     {
T 1888     $deleted = FALSE;
1889
aadfa1 1890     if (is_array($mbox_name))
S 1891       $a_mboxes = $mbox_name;
1892     else if (is_string($mbox_name) && strlen($mbox_name))
1893       $a_mboxes = explode(',', $mbox_name);
4e17e6 1894
97a656 1895     $all_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
S 1896
4e17e6 1897     if (is_array($a_mboxes))
aadfa1 1898       foreach ($a_mboxes as $mbox_name)
4e17e6 1899         {
aadfa1 1900         $mailbox = $this->_mod_mailbox($mbox_name);
4e17e6 1901
T 1902         // unsubscribe mailbox before deleting
1903         iil_C_UnSubscribe($this->conn, $mailbox);
fa4cd2 1904
4e17e6 1905         // send delete command to server
T 1906         $result = iil_C_DeleteFolder($this->conn, $mailbox);
1907         if ($result>=0)
1908           $deleted = TRUE;
97a656 1909
S 1910         foreach ($all_mboxes as $c_mbox)
fa0152 1911           {
T 1912           $regex = preg_quote($mailbox . $this->delimiter, '/');
1913           $regex = '/^' . $regex . '/';
1914           if (preg_match($regex, $c_mbox))
97a656 1915             {
S 1916             iil_C_UnSubscribe($this->conn, $c_mbox);
1917             $result = iil_C_DeleteFolder($this->conn, $c_mbox);
1918             if ($result>=0)
1919               $deleted = TRUE;
1920             }
fa0152 1921           }
4e17e6 1922         }
T 1923
1924     // clear mailboxlist cache
1925     if ($deleted)
1cded8 1926       {
T 1927       $this->clear_message_cache($mailbox.'.msg');
4e17e6 1928       $this->clear_cache('mailboxes');
1cded8 1929       }
4e17e6 1930
1cded8 1931     return $deleted;
4e17e6 1932     }
T 1933
fa4cd2 1934
T 1935   /**
1936    * Create all folders specified as default
1937    */
1938   function create_default_folders()
1939     {
1940     $a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
1941     $a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
1942     
1943     // create default folders if they do not exist
1944     foreach ($this->default_folders as $folder)
1945       {
1946       $abs_name = $this->_mod_mailbox($folder);
719a25 1947       if (!in_array_nocase($abs_name, $a_folders))
T 1948         $this->create_mailbox($folder, TRUE);
1949       else if (!in_array_nocase($abs_name, $a_subscribed))
1950         $this->subscribe($folder);
fa4cd2 1951       }
T 1952     }
4e17e6 1953
T 1954
1955
1956   /* --------------------------------
1cded8 1957    *   internal caching methods
4e17e6 1958    * --------------------------------*/
6dc026 1959
6d969b 1960   /**
T 1961    * @access private
1962    */
6dc026 1963   function set_caching($set)
T 1964     {
1cded8 1965     if ($set && is_object($this->db))
6dc026 1966       $this->caching_enabled = TRUE;
T 1967     else
1968       $this->caching_enabled = FALSE;
1969     }
1cded8 1970
6d969b 1971   /**
T 1972    * @access private
1973    */
4e17e6 1974   function get_cache($key)
T 1975     {
1976     // read cache
6dc026 1977     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 1978       {
1cded8 1979       $cache_data = $this->_read_cache_record('IMAP.'.$key);
4e17e6 1980       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
T 1981       }
1982     
1cded8 1983     return $this->cache[$key];
4e17e6 1984     }
T 1985
6d969b 1986   /**
T 1987    * @access private
1988    */
4e17e6 1989   function update_cache($key, $data)
T 1990     {
1991     $this->cache[$key] = $data;
1992     $this->cache_changed = TRUE;
1993     $this->cache_changes[$key] = TRUE;
1994     }
1995
6d969b 1996   /**
T 1997    * @access private
1998    */
4e17e6 1999   function write_cache()
T 2000     {
6dc026 2001     if ($this->caching_enabled && $this->cache_changed)
4e17e6 2002       {
T 2003       foreach ($this->cache as $key => $data)
2004         {
2005         if ($this->cache_changes[$key])
1cded8 2006           $this->_write_cache_record('IMAP.'.$key, serialize($data));
4e17e6 2007         }
T 2008       }    
2009     }
2010
6d969b 2011   /**
T 2012    * @access private
2013    */
4e17e6 2014   function clear_cache($key=NULL)
T 2015     {
fc2312 2016     if (!$this->caching_enabled)
A 2017       return;
2018     
4e17e6 2019     if ($key===NULL)
T 2020       {
2021       foreach ($this->cache as $key => $data)
1cded8 2022         $this->_clear_cache_record('IMAP.'.$key);
4e17e6 2023
T 2024       $this->cache = array();
2025       $this->cache_changed = FALSE;
2026       $this->cache_changes = array();
2027       }
2028     else
2029       {
1cded8 2030       $this->_clear_cache_record('IMAP.'.$key);
4e17e6 2031       $this->cache_changes[$key] = FALSE;
T 2032       unset($this->cache[$key]);
2033       }
2034     }
2035
6d969b 2036   /**
T 2037    * @access private
2038    */
1cded8 2039   function _read_cache_record($key)
T 2040     {
2041     $cache_data = FALSE;
2042     
2043     if ($this->db)
2044       {
2045       // get cached data from DB
2046       $sql_result = $this->db->query(
2047         "SELECT cache_id, data
2048          FROM ".get_table_name('cache')."
2049          WHERE  user_id=?
2050          AND    cache_key=?",
2051         $_SESSION['user_id'],
2052         $key);
2053
2054       if ($sql_arr = $this->db->fetch_assoc($sql_result))
2055         {
2056         $cache_data = $sql_arr['data'];
2057         $this->cache_keys[$key] = $sql_arr['cache_id'];
2058         }
2059       }
2060
6d969b 2061     return $cache_data;
1cded8 2062     }
T 2063
6d969b 2064   /**
T 2065    * @access private
2066    */
1cded8 2067   function _write_cache_record($key, $data)
T 2068     {
2069     if (!$this->db)
2070       return FALSE;
2071
2072     // check if we already have a cache entry for this key
2073     if (!isset($this->cache_keys[$key]))
2074       {
2075       $sql_result = $this->db->query(
2076         "SELECT cache_id
2077          FROM ".get_table_name('cache')."
2078          WHERE  user_id=?
2079          AND    cache_key=?",
2080         $_SESSION['user_id'],
2081         $key);
2082                                      
2083       if ($sql_arr = $this->db->fetch_assoc($sql_result))
2084         $this->cache_keys[$key] = $sql_arr['cache_id'];
2085       else
2086         $this->cache_keys[$key] = FALSE;
2087       }
2088
2089     // update existing cache record
2090     if ($this->cache_keys[$key])
2091       {
2092       $this->db->query(
2093         "UPDATE ".get_table_name('cache')."
107bde 2094          SET    created=".$this->db->now().",
1cded8 2095                 data=?
T 2096          WHERE  user_id=?
2097          AND    cache_key=?",
2098         $data,
2099         $_SESSION['user_id'],
2100         $key);
2101       }
2102     // add new cache record
2103     else
2104       {
2105       $this->db->query(
2106         "INSERT INTO ".get_table_name('cache')."
2107          (created, user_id, cache_key, data)
107bde 2108          VALUES (".$this->db->now().", ?, ?, ?)",
1cded8 2109         $_SESSION['user_id'],
T 2110         $key,
2111         $data);
2112       }
2113     }
2114
6d969b 2115   /**
T 2116    * @access private
2117    */
1cded8 2118   function _clear_cache_record($key)
T 2119     {
2120     $this->db->query(
2121       "DELETE FROM ".get_table_name('cache')."
2122        WHERE  user_id=?
2123        AND    cache_key=?",
2124       $_SESSION['user_id'],
2125       $key);
2126     }
2127
2128
2129
4e17e6 2130   /* --------------------------------
1cded8 2131    *   message caching methods
T 2132    * --------------------------------*/
2133    
2134
6d969b 2135   /**
T 2136    * Checks if the cache is up-to-date
2137    *
2138    * @param string Mailbox name
2139    * @param string Internal cache key
2140    * @return int -3 = off, -2 = incomplete, -1 = dirty
2141    */
1cded8 2142   function check_cache_status($mailbox, $cache_key)
T 2143     {
2144     if (!$this->caching_enabled)
2145       return -3;
2146
2147     $cache_index = $this->get_message_cache_index($cache_key, TRUE);
2148     $msg_count = $this->_messagecount($mailbox);
2149     $cache_count = count($cache_index);
2150
2151     // console("Cache check: $msg_count !== ".count($cache_index));
2152
2153     if ($cache_count==$msg_count)
2154       {
2155       // get highest index
2156       $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
2157       $cache_uid = array_pop($cache_index);
2158       
e6f360 2159       // uids of highest message matches -> cache seems OK
1cded8 2160       if ($cache_uid == $header->uid)
T 2161         return 1;
2162
2163       // cache is dirty
2164       return -1;
2165       }
e6f360 2166     // if cache count differs less than 10% report as dirty
1cded8 2167     else if (abs($msg_count - $cache_count) < $msg_count/10)
T 2168       return -1;
2169     else
2170       return -2;
2171     }
2172
6d969b 2173   /**
T 2174    * @access private
2175    */
1cded8 2176   function get_message_cache($key, $from, $to, $sort_field, $sort_order)
T 2177     {
2178     $cache_key = "$key:$from:$to:$sort_field:$sort_order";
2179     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
2180     
2181     if (!in_array($sort_field, $db_header_fields))
2182       $sort_field = 'idx';
2183     
2184     if ($this->caching_enabled && !isset($this->cache[$cache_key]))
2185       {
2186       $this->cache[$cache_key] = array();
2187       $sql_result = $this->db->limitquery(
2188         "SELECT idx, uid, headers
2189          FROM ".get_table_name('messages')."
2190          WHERE  user_id=?
2191          AND    cache_key=?
2192          ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
2193          strtoupper($sort_order),
2194         $from,
2195         $to-$from,
2196         $_SESSION['user_id'],
2197         $key);
2198
2199       while ($sql_arr = $this->db->fetch_assoc($sql_result))
2200         {
2201         $uid = $sql_arr['uid'];
2202         $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
ecd2e7 2203         
T 2204         // featch headers if unserialize failed
2205         if (empty($this->cache[$cache_key][$uid]))
2206           $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true);
1cded8 2207         }
T 2208       }
2209       
2210     return $this->cache[$cache_key];
2211     }
2212
6d969b 2213   /**
T 2214    * @access private
2215    */
f7bfec 2216   function &get_cached_message($key, $uid, $struct=false)
1cded8 2217     {
T 2218     $internal_key = '__single_msg';
017def 2219     
f7bfec 2220     if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) ||
T 2221         ($struct && empty($this->cache[$internal_key][$uid]->structure))))
1cded8 2222       {
f7bfec 2223       $sql_select = "idx, uid, headers" . ($struct ? ", structure" : '');
1cded8 2224       $sql_result = $this->db->query(
T 2225         "SELECT $sql_select
2226          FROM ".get_table_name('messages')."
2227          WHERE  user_id=?
2228          AND    cache_key=?
2229          AND    uid=?",
2230         $_SESSION['user_id'],
2231         $key,
2232         $uid);
f7bfec 2233
1cded8 2234       if ($sql_arr = $this->db->fetch_assoc($sql_result))
T 2235         {
f7bfec 2236         $this->cache[$internal_key][$uid] = unserialize($sql_arr['headers']);
T 2237         if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure']))
2238           $this->cache[$internal_key][$uid]->structure = unserialize($sql_arr['structure']);
1cded8 2239         }
T 2240       }
2241
2242     return $this->cache[$internal_key][$uid];
2243     }
2244
6d969b 2245   /**
T 2246    * @access private
2247    */  
0677ca 2248   function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
1cded8 2249     {
T 2250     static $sa_message_index = array();
2251     
4647e1 2252     // empty key -> empty array
ffb0b0 2253     if (!$this->caching_enabled || empty($key))
4647e1 2254       return array();
T 2255     
1cded8 2256     if (!empty($sa_message_index[$key]) && !$force)
T 2257       return $sa_message_index[$key];
2258     
2259     $sa_message_index[$key] = array();
2260     $sql_result = $this->db->query(
2261       "SELECT idx, uid
2262        FROM ".get_table_name('messages')."
2263        WHERE  user_id=?
2264        AND    cache_key=?
0677ca 2265        ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
1cded8 2266       $_SESSION['user_id'],
T 2267       $key);
2268
2269     while ($sql_arr = $this->db->fetch_assoc($sql_result))
2270       $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
2271       
2272     return $sa_message_index[$key];
2273     }
2274
6d969b 2275   /**
T 2276    * @access private
2277    */
f7bfec 2278   function add_message_cache($key, $index, $headers, $struct=null)
1cded8 2279     {
017def 2280     if (empty($key) || !is_object($headers) || empty($headers->uid))
T 2281         return;
2282     
2283     // add to internal (fast) cache
2284     $this->cache['__single_msg'][$headers->uid] = $headers;
2285     $this->cache['__single_msg'][$headers->uid]->structure = $struct;
2286     
2287     // no further caching
2288     if (!$this->caching_enabled)
31b2ce 2289       return;
017def 2290     
f7bfec 2291     // check for an existing record (probly headers are cached but structure not)
T 2292     $sql_result = $this->db->query(
2293         "SELECT message_id
2294          FROM ".get_table_name('messages')."
2295          WHERE  user_id=?
2296          AND    cache_key=?
2297          AND    uid=?
2298          AND    del<>1",
2299         $_SESSION['user_id'],
2300         $key,
2301         $headers->uid);
31b2ce 2302
f7bfec 2303     // update cache record
T 2304     if ($sql_arr = $this->db->fetch_assoc($sql_result))
2305       {
2306       $this->db->query(
2307         "UPDATE ".get_table_name('messages')."
2308          SET   idx=?, headers=?, structure=?
2309          WHERE message_id=?",
2310         $index,
2311         serialize($headers),
2312         is_object($struct) ? serialize($struct) : NULL,
2313         $sql_arr['message_id']
2314         );
2315       }
2316     else  // insert new record
2317       {
2318       $this->db->query(
2319         "INSERT INTO ".get_table_name('messages')."
2320          (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers, structure)
107bde 2321          VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?, ?)",
f7bfec 2322         $_SESSION['user_id'],
T 2323         $key,
2324         $index,
2325         $headers->uid,
2326         (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
2327         (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
2328         (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
2329         (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
2330         (int)$headers->size,
2331         serialize($headers),
2332         is_object($struct) ? serialize($struct) : NULL
2333         );
2334       }
1cded8 2335     }
T 2336     
6d969b 2337   /**
T 2338    * @access private
2339    */
1cded8 2340   function remove_message_cache($key, $index)
T 2341     {
780527 2342     if (!$this->caching_enabled)
T 2343       return;
2344     
1cded8 2345     $this->db->query(
T 2346       "DELETE FROM ".get_table_name('messages')."
2347        WHERE  user_id=?
2348        AND    cache_key=?
2349        AND    idx=?",
2350       $_SESSION['user_id'],
2351       $key,
2352       $index);
2353     }
2354
6d969b 2355   /**
T 2356    * @access private
2357    */
1cded8 2358   function clear_message_cache($key, $start_index=1)
T 2359     {
780527 2360     if (!$this->caching_enabled)
T 2361       return;
2362     
1cded8 2363     $this->db->query(
T 2364       "DELETE FROM ".get_table_name('messages')."
2365        WHERE  user_id=?
2366        AND    cache_key=?
2367        AND    idx>=?",
2368       $_SESSION['user_id'],
2369       $key,
2370       $start_index);
2371     }
2372
2373
2374
2375
2376   /* --------------------------------
2377    *   encoding/decoding methods
4e17e6 2378    * --------------------------------*/
T 2379
6d969b 2380   /**
T 2381    * Split an address list into a structured array list
2382    *
2383    * @param string  Input string
2384    * @param int     List only this number of addresses
2385    * @param boolean Decode address strings
2386    * @return array  Indexed list of addresses
2387    */
f11541 2388   function decode_address_list($input, $max=null, $decode=true)
4e17e6 2389     {
f11541 2390     $a = $this->_parse_address_list($input, $decode);
4e17e6 2391     $out = array();
0c6f4b 2392     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
T 2393     $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
41fa0b 2394     
4e17e6 2395     if (!is_array($a))
T 2396       return $out;
2397
2398     $c = count($a);
2399     $j = 0;
2400
2401     foreach ($a as $val)
2402       {
2403       $j++;
2404       $address = $val['address'];
2405       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
3cf664 2406       if ($name && $address && $name != $address)
0c6f4b 2407         $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
3cf664 2408       else if ($address)
T 2409         $string = $address;
2410       else if ($name)
2411         $string = $name;
4e17e6 2412       
T 2413       $out[$j] = array('name' => $name,
2414                        'mailto' => $address,
2415                        'string' => $string);
2416               
2417       if ($max && $j==$max)
2418         break;
2419       }
2420     
2421     return $out;
2422     }
2423
2424
6d969b 2425   /**
T 2426    * Decode a message header value
2427    *
2428    * @param string  Header value
2429    * @param boolean Remove quotes if necessary
2430    * @return string Decoded string
2431    */
1cded8 2432   function decode_header($input, $remove_quotes=FALSE)
4e17e6 2433     {
17b5fb 2434     $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset);
1cded8 2435     if ($str{0}=='"' && $remove_quotes)
T 2436       $str = str_replace('"', '', $str);
2437     
2438     return $str;
4b0f65 2439     }
ba8f44 2440
T 2441
2442   /**
2443    * Decode a mime-encoded string to internal charset
2444    *
6d969b 2445    * @param string  Header value
T 2446    * @param string  Fallback charset if none specified
2447    * @return string Decoded string
2448    * @static
ba8f44 2449    */
f11541 2450   function decode_mime_string($input, $fallback=null)
4b0f65 2451     {
2e6825 2452     // Initialize variable
4e17e6 2453     $out = '';
T 2454
2e6825 2455     // Iterate instead of recursing, this way if there are too many values we don't have stack overflows
T 2456     // rfc: all line breaks or other characters not found 
2457     // in the Base64 Alphabet must be ignored by decoding software
2458     // delete all blanks between MIME-lines, differently we can 
2459     // receive unnecessary blanks and broken utf-8 symbols
2460     $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
9e60d4 2461
2e6825 2462     // Check if there is stuff to decode
T 2463     if (strpos($input, '=?') !== false) {
2464       // Loop through the string to decode all occurences of =? ?= into the variable $out 
2465       while(($pos = strpos($input, '=?')) !== false) {
2466         // Append everything that is before the text to be decoded
2467         $out .= substr($input, 0, $pos);
4e17e6 2468
2e6825 2469         // Get the location of the text to decode
T 2470         $end_cs_pos = strpos($input, "?", $pos+2);
2471         $end_en_pos = strpos($input, "?", $end_cs_pos+1);
2472         $end_pos = strpos($input, "?=", $end_en_pos+1);
4e17e6 2473
2e6825 2474         // Extract the encoded string
T 2475         $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
2476         // Extract the remaining string
2477         $input = substr($input, $end_pos+2);
2478
2479         // Decode the string fragement
2480         $out .= rcube_imap::_decode_mime_string_part($encstr);
4e17e6 2481       }
399835 2482
2e6825 2483       // Deocde the rest (if any)
T 2484       if (strlen($input) != 0)
2485          $out .= rcube_imap::decode_mime_string($input, $fallback);
2486
2487        // return the results
2488       return $out;
2489     }
2490
f11541 2491     // no encoding information, use fallback
399835 2492     return rcube_charset_convert($input, 
b026c3 2493       !empty($fallback) ? $fallback : rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'));
4e17e6 2494     }
T 2495
2496
ba8f44 2497   /**
T 2498    * Decode a part of a mime-encoded string
2499    *
6d969b 2500    * @access private
ba8f44 2501    */
4b0f65 2502   function _decode_mime_string_part($str)
4e17e6 2503     {
T 2504     $a = explode('?', $str);
2505     $count = count($a);
2506
2507     // should be in format "charset?encoding?base64_string"
2508     if ($count >= 3)
2509       {
2510       for ($i=2; $i<$count; $i++)
2511         $rest.=$a[$i];
2512
2513       if (($a[1]=="B")||($a[1]=="b"))
2514         $rest = base64_decode($rest);
2515       else if (($a[1]=="Q")||($a[1]=="q"))
2516         {
2517         $rest = str_replace("_", " ", $rest);
2518         $rest = quoted_printable_decode($rest);
2519         }
2520
3f9edb 2521       return rcube_charset_convert($rest, $a[0]);
4e17e6 2522       }
T 2523     else
3f9edb 2524       return $str;    // we dont' know what to do with this  
4e17e6 2525     }
T 2526
2527
6d969b 2528   /**
T 2529    * Decode a mime part
2530    *
2531    * @param string Input string
2532    * @param string Part encoding
2533    * @return string Decoded string
2534    * @access private
2535    */
4e17e6 2536   function mime_decode($input, $encoding='7bit')
T 2537     {
2538     switch (strtolower($encoding))
2539       {
2540       case '7bit':
2541         return $input;
2542         break;
2543       
2544       case 'quoted-printable':
2545         return quoted_printable_decode($input);
2546         break;
2547       
2548       case 'base64':
2549         return base64_decode($input);
2550         break;
2551       
2552       default:
2553         return $input;
2554       }
2555     }
2556
2557
6d969b 2558   /**
T 2559    * Convert body charset to UTF-8 according to the ctype_parameters
2560    *
2561    * @param string Part body to decode
2562    * @param string Charset to convert from
2563    * @return string Content converted to internal charset
2564    */
4e17e6 2565   function charset_decode($body, $ctype_param)
T 2566     {
a95e0e 2567     if (is_array($ctype_param) && !empty($ctype_param['charset']))
3f9edb 2568       return rcube_charset_convert($body, $ctype_param['charset']);
4e17e6 2569
fb5f4f 2570     // defaults to what is specified in the class header
17b5fb 2571     return rcube_charset_convert($body,  $this->default_charset);
4e17e6 2572     }
T 2573
2574
6d969b 2575   /**
T 2576    * Translate UID to message ID
2577    *
2578    * @param int    Message UID
2579    * @param string Mailbox name
2580    * @return int   Message ID
2581    */
2582   function get_id($uid, $mbox_name=NULL) 
2583     {
2584       $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
2585       return $this->_uid2id($uid, $mailbox);
2586     }
2587
2588
2589   /**
2590    * Translate message number to UID
2591    *
2592    * @param int    Message ID
2593    * @param string Mailbox name
2594    * @return int   Message UID
2595    */
2596   function get_uid($id,$mbox_name=NULL)
2597     {
2598       $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
2599       return $this->_id2uid($id, $mailbox);
2600     }
2601
1cded8 2602
ba8f44 2603
4e17e6 2604   /* --------------------------------
T 2605    *         private methods
2606    * --------------------------------*/
2607
2608
6d969b 2609   /**
T 2610    * @access private
2611    */
aadfa1 2612   function _mod_mailbox($mbox_name, $mode='in')
4e17e6 2613     {
ae4d74 2614     if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
aadfa1 2615       return $mbox_name;
7902df 2616
f619de 2617     if (!empty($this->root_dir) && $mode=='in') 
aadfa1 2618       $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
7902df 2619     else if (strlen($this->root_dir) && $mode=='out') 
aadfa1 2620       $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
4e17e6 2621
aadfa1 2622     return $mbox_name;
4e17e6 2623     }
T 2624
d5342a 2625   /**
T 2626    * Validate the given input and save to local properties
2627    * @access private
2628    */
2629   function _set_sort_order($sort_field, $sort_order)
2630   {
2631     if ($sort_field != null)
2632       $this->sort_field = asciiwords($sort_field);
2633     if ($sort_order != null)
2634       $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
2635   }
4e17e6 2636
6d969b 2637   /**
T 2638    * Sort mailboxes first by default folders and then in alphabethical order
2639    * @access private
2640    */
4e17e6 2641   function _sort_mailbox_list($a_folders)
T 2642     {
ea400e 2643     $a_out = $a_defaults = $folders = array();
681a59 2644
A 2645     $delimiter = $this->get_hierarchy_delimiter();
4e17e6 2646
T 2647     // find default folders and skip folders starting with '.'
681a59 2648     foreach ($a_folders as $i => $folder)
4e17e6 2649       {
T 2650       if ($folder{0}=='.')
6d969b 2651         continue;
fa4cd2 2652
c007e6 2653       if (($p = array_search(strtolower($folder), $this->default_folders_lc)) !== false && !$a_defaults[$p])
6d969b 2654         $a_defaults[$p] = $folder;
4e17e6 2655       else
70aa90 2656         $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7'));
681a59 2657       }
A 2658
2659     asort($folders, SORT_LOCALE_STRING);
2660     ksort($a_defaults);
2661
2662     $folders = array_merge($a_defaults, array_keys($folders));
2663
2664     // finally we must rebuild the list to move 
f219a2 2665     // subfolders of default folders to their place...
A 2666     // ...also do this for the rest of folders because
8498dc 2667     // asort() is not properly sorting case sensitive names
A 2668
2669     // set the type of folder name variable (#1485527) 
681a59 2670     while (list($key, $folder) = each($folders)) {
8498dc 2671       $a_out[] = (string) $folder;
681a59 2672       unset($folders[$key]);
f219a2 2673       foreach ($folders as $idx => $f) {
A 2674     if (strpos($f, $folder.$delimiter) === 0) {
8498dc 2675           $a_out[] = (string) $f;
f219a2 2676       unset($folders[$idx]);
681a59 2677       }
f219a2 2678         }
A 2679       reset($folders);  
4e17e6 2680       }
T 2681
681a59 2682     return $a_out;
4e17e6 2683     }
T 2684
6d969b 2685   /**
T 2686    * @access private
2687    */
aadfa1 2688   function _uid2id($uid, $mbox_name=NULL)
4e17e6 2689     {
aadfa1 2690     if (!$mbox_name)
S 2691       $mbox_name = $this->mailbox;
4e17e6 2692       
aadfa1 2693     if (!isset($this->uid_id_map[$mbox_name][$uid]))
S 2694       $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
4e17e6 2695
aadfa1 2696     return $this->uid_id_map[$mbox_name][$uid];
4e17e6 2697     }
T 2698
6d969b 2699   /**
T 2700    * @access private
2701    */
aadfa1 2702   function _id2uid($id, $mbox_name=NULL)
e6f360 2703     {
aadfa1 2704     if (!$mbox_name)
S 2705       $mbox_name = $this->mailbox;
e6f360 2706       
1fb2c8 2707     $index = array_flip((array)$this->uid_id_map[$mbox_name]);
017def 2708     if (isset($index[$id]))
T 2709       $uid = $index[$id];
2710     else
2711       {
2712       $uid = iil_C_ID2UID($this->conn, $mbox_name, $id);
2713       $this->uid_id_map[$mbox_name][$uid] = $id;
2714       }
2715     
2716     return $uid;
1cded8 2717     }
T 2718
2719
6d969b 2720   /**
T 2721    * Subscribe/unsubscribe a list of mailboxes and update local cache
2722    * @access private
2723    */
4e17e6 2724   function _change_subscription($a_mboxes, $mode)
T 2725     {
2726     $updated = FALSE;
2727     
2728     if (is_array($a_mboxes))
aadfa1 2729       foreach ($a_mboxes as $i => $mbox_name)
4e17e6 2730         {
aadfa1 2731         $mailbox = $this->_mod_mailbox($mbox_name);
4e17e6 2732         $a_mboxes[$i] = $mailbox;
T 2733
2734         if ($mode=='subscribe')
2735           $result = iil_C_Subscribe($this->conn, $mailbox);
2736         else if ($mode=='unsubscribe')
2737           $result = iil_C_UnSubscribe($this->conn, $mailbox);
2738
2739         if ($result>=0)
2740           $updated = TRUE;
2741         }
2742         
2743     // get cached mailbox list    
2744     if ($updated)
2745       {
2746       $a_mailbox_cache = $this->get_cache('mailboxes');
2747       if (!is_array($a_mailbox_cache))
2748         return $updated;
2749
2750       // modify cached list
2751       if ($mode=='subscribe')
2752         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
2753       else if ($mode=='unsubscribe')
2754         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
2755         
2756       // write mailboxlist to cache
2757       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
2758       }
2759
2760     return $updated;
2761     }
2762
2763
6d969b 2764   /**
T 2765    * Increde/decrese messagecount for a specific mailbox
2766    * @access private
2767    */
aadfa1 2768   function _set_messagecount($mbox_name, $mode, $increment)
4e17e6 2769     {
T 2770     $a_mailbox_cache = FALSE;
aadfa1 2771     $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
4e17e6 2772     $mode = strtoupper($mode);
T 2773
2774     $a_mailbox_cache = $this->get_cache('messagecount');
2775     
2776     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
2777       return FALSE;
2778     
2779     // add incremental value to messagecount
2780     $a_mailbox_cache[$mailbox][$mode] += $increment;
31b2ce 2781     
T 2782     // there's something wrong, delete from cache
2783     if ($a_mailbox_cache[$mailbox][$mode] < 0)
2784       unset($a_mailbox_cache[$mailbox][$mode]);
4e17e6 2785
T 2786     // write back to cache
2787     $this->update_cache('messagecount', $a_mailbox_cache);
2788     
2789     return TRUE;
2790     }
2791
2792
6d969b 2793   /**
T 2794    * Remove messagecount of a specific mailbox from cache
2795    * @access private
2796    */
aadfa1 2797   function _clear_messagecount($mbox_name='')
4e17e6 2798     {
T 2799     $a_mailbox_cache = FALSE;
aadfa1 2800     $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
4e17e6 2801
T 2802     $a_mailbox_cache = $this->get_cache('messagecount');
2803
2804     if (is_array($a_mailbox_cache[$mailbox]))
2805       {
2806       unset($a_mailbox_cache[$mailbox]);
2807       $this->update_cache('messagecount', $a_mailbox_cache);
2808       }
2809     }
2810
2811
6d969b 2812   /**
T 2813    * Split RFC822 header string into an associative array
2814    * @access private
2815    */
8d4bcd 2816   function _parse_headers($headers)
T 2817     {
2818     $a_headers = array();
2819     $lines = explode("\n", $headers);
2820     $c = count($lines);
2821     for ($i=0; $i<$c; $i++)
2822       {
2823       if ($p = strpos($lines[$i], ': '))
2824         {
2825         $field = strtolower(substr($lines[$i], 0, $p));
2826         $value = trim(substr($lines[$i], $p+1));
2827         if (!empty($value))
2828           $a_headers[$field] = $value;
2829         }
2830       }
2831     
2832     return $a_headers;
2833     }
2834
2835
6d969b 2836   /**
T 2837    * @access private
2838    */
f11541 2839   function _parse_address_list($str, $decode=true)
4e17e6 2840     {
d04d20 2841     // remove any newlines and carriage returns before
5a6ad2 2842     $a = $this->_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
4e17e6 2843     $result = array();
41fa0b 2844     
4e17e6 2845     foreach ($a as $key => $val)
T 2846       {
568ba3 2847       $val = preg_replace("/([\"\w])</", "$1 <", $val);
f11541 2848       $sub_a = $this->_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val);
41fa0b 2849       $result[$key]['name'] = '';
T 2850
4e17e6 2851       foreach ($sub_a as $k => $v)
T 2852         {
3cf664 2853         if (strpos($v, '@') > 0)
4e17e6 2854           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
T 2855         else
2856           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
2857         }
2858         
2859       if (empty($result[$key]['name']))
41fa0b 2860         $result[$key]['name'] = $result[$key]['address'];        
4e17e6 2861       }
T 2862     
2863     return $result;
2864     }
2865
2866
6d969b 2867   /**
T 2868    * @access private
2869    */
4e17e6 2870   function _explode_quoted_string($delimiter, $string)
T 2871     {
5a6ad2 2872     $result = array();
T 2873     $strlen = strlen($string);
2874     for ($q=$p=$i=0; $i < $strlen; $i++)
2875     {
2876       if ($string{$i} == "\"" && $string{$i-1} != "\\")
2877         $q = $q ? false : true;
2878       else if (!$q && preg_match("/$delimiter/", $string{$i}))
2879       {
2880         $result[] = substr($string, $p, $i - $p);
2881         $p = $i + 1;
2882       }
2883     }
4e17e6 2884     
5a6ad2 2885     $result[] = substr($string, $p);
4e17e6 2886     return $result;
T 2887     }
6d969b 2888
T 2889 }  // end class rcube_imap
4e17e6 2890
8d4bcd 2891
T 2892 /**
2893  * Class representing a message part
6d969b 2894  *
T 2895  * @package Mail
8d4bcd 2896  */
T 2897 class rcube_message_part
2898 {
2899   var $mime_id = '';
2900   var $ctype_primary = 'text';
2901   var $ctype_secondary = 'plain';
2902   var $mimetype = 'text/plain';
2903   var $disposition = '';
5cc4b1 2904   var $filename = '';
8d4bcd 2905   var $encoding = '8bit';
T 2906   var $charset = '';
2907   var $size = 0;
2908   var $headers = array();
2909   var $d_parameters = array();
2910   var $ctype_parameters = array();
2911
2912 }
4e17e6 2913
T 2914
7e93ff 2915 /**
T 2916  * Class for sorting an array of iilBasicHeader objects in a predetermined order.
2917  *
6d969b 2918  * @package Mail
7e93ff 2919  * @author Eric Stadtherr
T 2920  */
2921 class rcube_header_sorter
2922 {
2923    var $sequence_numbers = array();
2924    
2925    /**
6d969b 2926     * Set the predetermined sort order.
7e93ff 2927     *
6d969b 2928     * @param array Numerically indexed array of IMAP message sequence numbers
7e93ff 2929     */
T 2930    function set_sequence_numbers($seqnums)
2931    {
05d180 2932       $this->sequence_numbers = array_flip($seqnums);
7e93ff 2933    }
T 2934  
2935    /**
6d969b 2936     * Sort the array of header objects
7e93ff 2937     *
6d969b 2938     * @param array Array of iilBasicHeader objects indexed by UID
7e93ff 2939     */
T 2940    function sort_headers(&$headers)
2941    {
2942       /*
2943        * uksort would work if the keys were the sequence number, but unfortunately
2944        * the keys are the UIDs.  We'll use uasort instead and dereference the value
2945        * to get the sequence number (in the "id" field).
2946        * 
2947        * uksort($headers, array($this, "compare_seqnums")); 
2948        */
2949        uasort($headers, array($this, "compare_seqnums"));
2950    }
2951  
2952    /**
2953     * Sort method called by uasort()
2954     */
2955    function compare_seqnums($a, $b)
2956    {
2957       // First get the sequence number from the header object (the 'id' field).
2958       $seqa = $a->id;
2959       $seqb = $b->id;
2960       
2961       // then find each sequence number in my ordered list
05d180 2962       $posa = isset($this->sequence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1;
T 2963       $posb = isset($this->sequence_numbers[$seqb]) ? intval($this->sequence_numbers[$seqb]) : -1;
7e93ff 2964       
T 2965       // return the relative position as the comparison value
05d180 2966       return $posa - $posb;
7e93ff 2967    }
T 2968 }
4e17e6 2969
T 2970
7e93ff 2971 /**
T 2972  * Add quoted-printable encoding to a given string
2973  * 
6d969b 2974  * @param string   String to encode
T 2975  * @param int      Add new line after this number of characters
2976  * @param boolean  True if spaces should be converted into =20
2977  * @return string Encoded string
7e93ff 2978  */
T 2979 function quoted_printable_encode($input, $line_max=76, $space_conv=false)
4e17e6 2980   {
T 2981   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2982   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2983   $eol = "\r\n";
2984   $escape = "=";
2985   $output = "";
2986
2987   while( list(, $line) = each($lines))
2988     {
2989     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2990     $linlen = strlen($line);
2991     $newline = "";
2992     for($i = 0; $i < $linlen; $i++)
2993       {
2994       $c = substr( $line, $i, 1 );
2995       $dec = ord( $c );
2996       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2997         {
2998         $c = "=2E";
2999         }
3000       if ( $dec == 32 )
3001         {
3002         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
3003           {
3004           $c = "=20";
3005           }
3006         else if ( $space_conv )
3007           {
3008           $c = "=20";
3009           }
3010         }
3011       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
3012         {
3013         $h2 = floor($dec/16);
3014         $h1 = floor($dec%16);
3015         $c = $escape.$hex["$h2"].$hex["$h1"];
3016         }
3017          
3018       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
3019         {
3020         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
3021         $newline = "";
3022         // check if newline first character will be point or not
3023         if ( $dec == 46 )
3024           {
3025           $c = "=2E";
3026           }
3027         }
3028       $newline .= $c;
3029       } // end of for
3030     $output .= $newline.$eol;
3031     } // end of while
3032
3033   return trim($output);
3034   }
3035
8d4bcd 3036