source: subversion/trunk/roundcubemail/program/include/rcube_imap.inc @ 277

Last change on this file since 277 was 277, checked in by thomasb, 7 years ago

Improved usability (Ticket #1483807) and HTML validity; applied patch #1328032; fixed bug #1443200

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.3 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/rcube_imap.inc                                        |
6 |                                                                       |
7 | This file is part of the RoundCube Webmail client                     |
8 | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   IMAP wrapper that implements the Iloha IMAP Library (IIL)           |
13 |   See http://ilohamail.org/ for details                               |
14 |                                                                       |
15 +-----------------------------------------------------------------------+
16 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17 +-----------------------------------------------------------------------+
18
19 $Id$
20
21*/
22
23
24/**
25 * Obtain classes from the Iloha IMAP library
26 */
27require_once('lib/imap.inc');
28require_once('lib/mime.inc');
29require_once('lib/utf7.inc');
30
31
32/**
33 * Interface class for accessing an IMAP server
34 *
35 * This is a wrapper that implements the Iloha IMAP Library (IIL)
36 *
37 * @package    RoundCube Webmail
38 * @author     Thomas Bruederli <roundcube@gmail.com>
39 * @version    1.26
40 * @link       http://ilohamail.org
41 */
42class rcube_imap
43  {
44  var $db;
45  var $conn;
46  var $root_ns = '';
47  var $root_dir = '';
48  var $mailbox = 'INBOX';
49  var $list_page = 1;
50  var $page_size = 10;
51  var $sort_field = 'date';
52  var $sort_order = 'DESC';
53  var $delimiter = NULL;
54  var $caching_enabled = FALSE;
55  var $default_folders = array('inbox', 'drafts', 'sent', 'junk', 'trash');
56  var $cache = array();
57  var $cache_keys = array(); 
58  var $cache_changes = array();
59  var $uid_id_map = array();
60  var $msg_headers = array();
61  var $capabilities = array();
62  var $skip_deleted = FALSE;
63  var $debug_level = 1;
64
65
66  /**
67   * Object constructor
68   *
69   * @param  object  Database connection
70   */
71  function __construct($db_conn)
72    {
73    $this->db = $db_conn;
74    }
75
76
77  /**
78   * PHP 4 object constructor
79   *
80   * @see  rcube_imap::__construct
81   */
82  function rcube_imap($db_conn)
83    {
84    $this->__construct($db_conn);
85    }
86
87
88  /**
89   * Connect to an IMAP server
90   *
91   * @param  string   Host to connect
92   * @param  string   Username for IMAP account
93   * @param  string   Password for IMAP account
94   * @param  number   Port to connect to
95   * @param  boolean  Use SSL connection
96   * @return boolean  TRUE on success, FALSE on failure
97   * @access public
98   */
99  function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
100    {
101    global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
102   
103    // check for Open-SSL support in PHP build
104    if ($use_ssl && in_array('openssl', get_loaded_extensions()))
105      $ICL_SSL = TRUE;
106    else if ($use_ssl)
107      {
108      raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
109                        'message' => 'Open SSL not available;'), TRUE, FALSE);
110      $port = 143;
111      }
112
113    $ICL_PORT = $port;
114    $IMAP_USE_INTERNAL_DATE = false;
115   
116    $this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
117    $this->host = $host;
118    $this->user = $user;
119    $this->pass = $pass;
120    $this->port = $port;
121    $this->ssl = $use_ssl;
122   
123    // print trace mesages
124    if ($this->conn && ($this->debug_level & 8))
125      console($this->conn->message);
126   
127    // write error log
128    else if (!$this->conn && $GLOBALS['iil_error'])
129      {
130      raise_error(array('code' => 403,
131                       'type' => 'imap',
132                       'message' => $GLOBALS['iil_error']), TRUE, FALSE);
133      }
134
135    // get account namespace
136    if ($this->conn)
137      {
138      $this->_parse_capability($this->conn->capability);
139      iil_C_NameSpace($this->conn);
140     
141      if (!empty($this->conn->delimiter))
142        $this->delimiter = $this->conn->delimiter;
143      if (!empty($this->conn->rootdir))
144        {
145        $this->set_rootdir($this->conn->rootdir);
146        $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
147        }
148      }
149
150    return $this->conn ? TRUE : FALSE;
151    }
152
153
154  /**
155   * Close IMAP connection
156   * Usually done on script shutdown
157   *
158   * @access public
159   */
160  function close()
161    {   
162    if ($this->conn)
163      iil_Close($this->conn);
164    }
165
166
167  /**
168   * Close IMAP connection and re-connect
169   * This is used to avoid some strange socket errors when talking to Courier IMAP
170   *
171   * @access public
172   */
173  function reconnect()
174    {
175    $this->close();
176    $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
177    }
178
179
180  /**
181   * Set a root folder for the IMAP connection.
182   *
183   * Only folders within this root folder will be displayed
184   * and all folder paths will be translated using this folder name
185   *
186   * @param  string   Root folder
187   * @access public
188   */
189  function set_rootdir($root)
190    {
191    if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
192      $root = substr($root, 0, -1);
193
194    $this->root_dir = $root;
195   
196    if (empty($this->delimiter))
197      $this->get_hierarchy_delimiter();
198    }
199
200
201  /**
202   * This list of folders will be listed above all other folders
203   *
204   * @param  array  Indexed list of folder names
205   * @access public
206   */
207  function set_default_mailboxes($arr)
208    {
209    if (is_array($arr))
210      {
211      $this->default_folders = array();
212     
213      // add mailbox names lower case
214      foreach ($arr as $mbox_row)
215        $this->default_folders[] = strtolower($mbox_row);
216     
217      // add inbox if not included
218      if (!in_array('inbox', $this->default_folders))
219        array_unshift($arr, 'inbox');
220      }
221    }
222
223
224  /**
225   * Set internal mailbox reference.
226   *
227   * All operations will be perfomed on this mailbox/folder
228   *
229   * @param  string  Mailbox/Folder name
230   * @access public
231   */
232  function set_mailbox($new_mbox)
233    {
234    $mailbox = $this->_mod_mailbox($new_mbox);
235
236    if ($this->mailbox == $mailbox)
237      return;
238
239    $this->mailbox = $mailbox;
240
241    // clear messagecount cache for this mailbox
242    $this->_clear_messagecount($mailbox);
243    }
244
245
246  /**
247   * Set internal list page
248   *
249   * @param  number  Page number to list
250   * @access public
251   */
252  function set_page($page)
253    {
254    $this->list_page = (int)$page;
255    }
256
257
258  /**
259   * Set internal page size
260   *
261   * @param  number  Number of messages to display on one page
262   * @access public
263   */
264  function set_pagesize($size)
265    {
266    $this->page_size = (int)$size;
267    }
268
269
270  /**
271   * Returns the currently used mailbox name
272   *
273   * @return  string Name of the mailbox/folder
274   * @access  public
275   */
276  function get_mailbox_name()
277    {
278    return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
279    }
280
281
282  /**
283   * Returns the IMAP server's capability
284   *
285   * @param   string  Capability name
286   * @return  mixed   Capability value or TRUE if supported, FALSE if not
287   * @access  public
288   */
289  function get_capability($cap)
290    {
291    $cap = strtoupper($cap);
292    return $this->capabilities[$cap];
293    }
294
295
296  /**
297   * Returns the delimiter that is used by the IMAP server for folder separation
298   *
299   * @return  string  Delimiter string
300   * @access  public
301   */
302  function get_hierarchy_delimiter()
303    {
304    if ($this->conn && empty($this->delimiter))
305      $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
306
307    if (empty($this->delimiter))
308      $this->delimiter = '/';
309
310    return $this->delimiter;
311    }
312
313
314  /**
315   * Public method for mailbox listing.
316   *
317   * Converts mailbox name with root dir first
318   *
319   * @param   string  Optional root folder
320   * @param   string  Optional filter for mailbox listing
321   * @return  array   List of mailboxes/folders
322   * @access  public
323   */
324  function list_mailboxes($root='', $filter='*')
325    {
326    $a_out = array();
327    $a_mboxes = $this->_list_mailboxes($root, $filter);
328
329    foreach ($a_mboxes as $mbox_row)
330      {
331      $name = $this->_mod_mailbox($mbox_row, 'out');
332      if (strlen($name))
333        $a_out[] = $name;
334      }
335
336    // sort mailboxes
337    $a_out = $this->_sort_mailbox_list($a_out);
338
339    return $a_out;
340    }
341
342
343  /**
344   * Private method for mailbox listing
345   *
346   * @return  array   List of mailboxes/folders
347   * @access  private
348   * @see     rcube_imap::list_mailboxes
349   */
350  function _list_mailboxes($root='', $filter='*')
351    {
352    $a_defaults = $a_out = array();
353   
354    // get cached folder list   
355    $a_mboxes = $this->get_cache('mailboxes');
356    if (is_array($a_mboxes))
357      return $a_mboxes;
358
359    // retrieve list of folders from IMAP server
360    $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
361   
362    if (!is_array($a_folders) || !sizeof($a_folders))
363      $a_folders = array();
364
365    // create Default folders if they do not exist
366    global $CONFIG;
367    foreach ($CONFIG['default_imap_folders'] as $folder)
368      {
369      if (!in_array_nocase($folder, $a_folders))
370        {
371        $this->create_mailbox($folder, TRUE);
372        $this->subscribe($folder);
373        }
374      }
375
376    $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
377    $a_mailbox_cache = array();
378
379    // write mailboxlist to cache
380    $this->update_cache('mailboxes', $a_folders);
381   
382    return $a_folders;
383    }
384
385
386  /**
387   * Get message count for a specific mailbox
388   *
389   * @param   string   Mailbox/folder name
390   * @param   string   Mode for count [ALL|UNSEEN|RECENT]
391   * @param   boolean  Force reading from server and update cache
392   * @return  number   Number of messages
393   * @access  public   
394   */
395  function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
396    {
397    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
398    return $this->_messagecount($mailbox, $mode, $force);
399    }
400
401
402  /**
403   * Private method for getting nr of messages
404   *
405   * @access  private
406   * @see     rcube_imap::messagecount
407   */
408  function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
409    {
410    $a_mailbox_cache = FALSE;
411    $mode = strtoupper($mode);
412
413    if (empty($mailbox))
414      $mailbox = $this->mailbox;
415
416    $a_mailbox_cache = $this->get_cache('messagecount');
417   
418    // return cached value
419    if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
420      return $a_mailbox_cache[$mailbox][$mode];
421
422    // RECENT count is fetched abit different     
423    if ($mode == 'RECENT')
424       $count = iil_C_CheckForRecent($this->conn, $mailbox);
425
426    // use SEARCH for message counting
427    else if ($this->skip_deleted)
428      {
429      $search_str = "ALL UNDELETED";
430
431      // get message count and store in cache
432      if ($mode == 'UNSEEN')
433        $search_str .= " UNSEEN";
434
435      // get message count using SEARCH
436      // not very performant but more precise (using UNDELETED)
437      $count = 0;
438      $index = $this->_search_index($mailbox, $search_str);
439      if (is_array($index))
440        {
441        $str = implode(",", $index);
442        if (!empty($str))
443          $count = count($index);
444        }
445      }
446    else
447      {
448      if ($mode == 'UNSEEN')
449        $count = iil_C_CountUnseen($this->conn, $mailbox);
450      else
451        $count = iil_C_CountMessages($this->conn, $mailbox);
452      }
453
454    if (!is_array($a_mailbox_cache[$mailbox]))
455      $a_mailbox_cache[$mailbox] = array();
456     
457    $a_mailbox_cache[$mailbox][$mode] = (int)$count;
458
459    // write back to cache
460    $this->update_cache('messagecount', $a_mailbox_cache);
461
462    return (int)$count;
463    }
464
465
466  /**
467   * Public method for listing headers
468   * convert mailbox name with root dir first
469   *
470   * @param   string   Mailbox/folder name
471   * @param   number   Current page to list
472   * @param   string   Header field to sort by
473   * @param   string   Sort order [ASC|DESC]
474   * @return  array    Indexed array with message header objects
475   * @access  public   
476   */
477  function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
478    {
479    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
480    return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
481    }
482
483
484  /**
485   * Private method for listing message headers
486   *
487   * @access  private
488   * @see     rcube_imap::list_headers
489   */
490  function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
491    {
492    if (!strlen($mailbox))
493      return array();
494     
495    if ($sort_field!=NULL)
496      $this->sort_field = $sort_field;
497    if ($sort_order!=NULL)
498      $this->sort_order = strtoupper($sort_order);
499
500    $max = $this->_messagecount($mailbox);
501    $start_msg = ($this->list_page-1) * $this->page_size;
502   
503    list($begin, $end) = $this->_get_message_range($max, $page);
504 
505        // mailbox is empty
506    if ($begin >= $end)
507      return array();
508       
509//console("fetch headers $start_msg to ".($start_msg+$this->page_size)." (msg $begin to $end)");
510
511    $headers_sorted = FALSE;
512    $cache_key = $mailbox.'.msg';
513    $cache_status = $this->check_cache_status($mailbox, $cache_key);
514
515//console("Cache status = $cache_status");
516   
517    // cache is OK, we can get all messages from local cache
518    if ($cache_status>0)
519      {
520      $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
521      $headers_sorted = TRUE;
522      }
523    else
524      {
525      // retrieve headers from IMAP
526      if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
527        {
528//console("$mailbox: ".join(',', $msg_index));
529       
530        $msgs = $msg_index[$begin];
531        for ($i=$begin+1; $i < $end; $i++)
532          $msgs = $msgs.','.$msg_index[$i];
533
534        $sorted = TRUE;
535        }
536      else
537        {
538        $msgs = sprintf("%d:%d", $begin+1, $end);       
539        $sorted = FALSE;
540        }
541
542
543      // cache is dirty, sync it
544      if ($this->caching_enabled && $cache_status==-1 && !$recursive)
545        {
546        $this->sync_header_index($mailbox);
547        return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
548        }     
549
550       
551      // fetch reuested headers from server
552      $a_msg_headers = array();
553      $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
554
555      // delete cached messages with a higher index than $max
556      $this->clear_message_cache($cache_key, $max);
557
558       
559      // kick child process to sync cache
560      // ...
561     
562      }
563
564
565    // return empty array if no messages found
566        if (!is_array($a_msg_headers) || empty($a_msg_headers))
567                return array();
568
569
570    // if not already sorted
571//    if (!$headers_sorted)
572//      $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
573
574
575      if (!$headers_sorted && $this->sort_order == 'DESC')
576        $a_msg_headers = array_reverse($a_msg_headers);
577     
578
579    return array_values($a_msg_headers);
580    }
581   
582
583  /**
584   * Public method for listing a specific set of headers
585   * convert mailbox name with root dir first
586   *
587   * @param   string   Mailbox/folder name
588   * @param   array    List of message ids to list
589   * @param   number   Current page to list
590   * @param   string   Header field to sort by
591   * @param   string   Sort order [ASC|DESC]
592   * @return  array    Indexed array with message header objects
593   * @access  public   
594   */
595  function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
596    {
597    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
598    return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);   
599    }
600   
601
602  /**
603   * Private method for listing a set of message headers
604   *
605   * @access  private
606   * @see     rcube_imap::list_header_set
607   */
608  function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
609    {
610    // also accept a comma-separated list of message ids
611    if (is_string($msgs))
612      $msgs = split(',', $msgs);
613     
614    if (!strlen($mailbox) || empty($msgs))
615      return array();
616
617    if ($sort_field!=NULL)
618      $this->sort_field = $sort_field;
619    if ($sort_order!=NULL)
620      $this->sort_order = strtoupper($sort_order);
621
622    $max = count($msgs);
623    $start_msg = ($this->list_page-1) * $this->page_size;
624
625    // fetch reuested headers from server
626    $a_msg_headers = array();
627    $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
628
629    // return empty array if no messages found
630        if (!is_array($a_msg_headers) || empty($a_msg_headers))
631                return array();
632
633    // if not already sorted
634    $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
635
636        // only return the requested part of the set
637        return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
638    }
639
640
641  /**
642   * Helper function to get first and last index of the requested set
643   *
644   * @param  number  message count
645   * @param  mixed   page number to show, or string 'all'
646   * @return array   array with two values: first index, last index
647   * @access private
648   */
649  function _get_message_range($max, $page)
650    {
651    $start_msg = ($this->list_page-1) * $this->page_size;
652   
653    if ($page=='all')
654      {
655      $begin = 0;
656      $end = $max;
657      }
658    else if ($this->sort_order=='DESC')
659      {
660      $begin = $max - $this->page_size - $start_msg;
661      $end =   $max - $start_msg;
662      }
663    else
664      {
665      $begin = $start_msg;
666      $end   = $start_msg + $this->page_size;
667      }
668
669    if ($begin < 0) $begin = 0;
670    if ($end < 0) $end = $max;
671    if ($end > $max) $end = $max;
672   
673    return array($begin, $end);
674    }
675   
676   
677
678  /**
679   * Fetches message headers
680   * Used for loop
681   *
682   * @param  string  Mailbox name
683   * @param  string  Message index to fetch
684   * @param  array   Reference to message headers array
685   * @param  array   Array with cache index
686   * @return number  Number of deleted messages
687   * @access private
688   */
689  function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
690    {
691    // cache is incomplete
692    $cache_index = $this->get_message_cache_index($cache_key);
693   
694    // fetch reuested headers from server
695    $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
696    $deleted_count = 0;
697   
698    if (!empty($a_header_index))
699      {
700      foreach ($a_header_index as $i => $headers)
701        {
702        if ($headers->deleted && $this->skip_deleted)
703          {
704          // delete from cache
705          if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
706            $this->remove_message_cache($cache_key, $headers->id);
707
708          $deleted_count++;
709          continue;
710          }
711
712        // add message to cache
713        if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
714          $this->add_message_cache($cache_key, $headers->id, $headers);
715
716        $a_msg_headers[$headers->uid] = $headers;
717        }
718      }
719       
720    return $deleted_count;
721    }
722   
723 
724  // return sorted array of message UIDs
725  function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
726    {
727    if ($sort_field!=NULL)
728      $this->sort_field = $sort_field;
729    if ($sort_order!=NULL)
730      $this->sort_order = strtoupper($sort_order);
731
732    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
733    $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
734
735    // have stored it in RAM
736    if (isset($this->cache[$key]))
737      return $this->cache[$key];
738
739    // check local cache
740    $cache_key = $mailbox.'.msg';
741    $cache_status = $this->check_cache_status($mailbox, $cache_key);
742
743    // cache is OK
744    if ($cache_status>0)
745      {
746      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
747      return array_values($a_index);
748      }
749
750
751    // fetch complete message index
752    $msg_count = $this->_messagecount($mailbox);
753    if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
754      {
755      if ($this->sort_order == 'DESC')
756        $a_index = array_reverse($a_index);
757
758      $this->cache[$key] = $a_index;
759
760      }
761    else
762      {
763      $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
764      $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
765   
766      if ($this->sort_order=="ASC")
767        asort($a_index);
768      else if ($this->sort_order=="DESC")
769        arsort($a_index);
770       
771      $i = 0;
772      $this->cache[$key] = array();
773      foreach ($a_index as $index => $value)
774        $this->cache[$key][$i++] = $a_uids[$index];
775      }
776
777    return $this->cache[$key];
778    }
779
780
781  function sync_header_index($mailbox)
782    {
783    $cache_key = $mailbox.'.msg';
784    $cache_index = $this->get_message_cache_index($cache_key);
785    $msg_count = $this->_messagecount($mailbox);
786
787    // fetch complete message index
788    $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
789       
790    foreach ($a_message_index as $id => $uid)
791      {
792      // message in cache at correct position
793      if ($cache_index[$id] == $uid)
794        {
795// console("$id / $uid: OK");
796        unset($cache_index[$id]);
797        continue;
798        }
799       
800      // message in cache but in wrong position
801      if (in_array((string)$uid, $cache_index, TRUE))
802        {
803// console("$id / $uid: Moved");
804        unset($cache_index[$id]);       
805        }
806     
807      // other message at this position
808      if (isset($cache_index[$id]))
809        {
810// console("$id / $uid: Delete");
811        $this->remove_message_cache($cache_key, $id);
812        unset($cache_index[$id]);
813        }
814       
815
816// console("$id / $uid: Add");
817
818      // fetch complete headers and add to cache
819      $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
820      $this->add_message_cache($cache_key, $headers->id, $headers);
821      }
822
823    // those ids that are still in cache_index have been deleted     
824    if (!empty($cache_index))
825      {
826      foreach ($cache_index as $id => $uid)
827        $this->remove_message_cache($cache_key, $id);
828      }
829    }
830
831
832  /**
833   * Invoke search request to IMAP server
834   *
835   * @param  string  mailbox name to search in
836   * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
837   * @param  string  search string
838   * @return array   search results as list of message ids
839   * @access public
840   */
841  function search($mbox_name='', $criteria='ALL', $str=NULL)
842    {
843    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
844    if ($str && $criteria)
845      {
846      $criteria = 'CHARSET UTF-8 '.$criteria.' "'.UTF7EncodeString($str).'"';
847      return $this->_search_index($mailbox, $criteria);
848      }
849    else
850      return $this->_search_index($mailbox, $criteria);
851    }   
852
853
854  /**
855   * Private search method
856   *
857   * @return array   search results as list of message ids
858   * @access private
859   * @see rcube_imap::search()
860   */
861  function _search_index($mailbox, $criteria='ALL')
862    {
863    $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
864    // clean message list (there might be some empty entries)
865    if (is_array($a_messages))
866      {
867      foreach ($a_messages as $i => $val)
868        if (empty($val))
869          unset($a_messages[$i]);
870      }
871       
872    return $a_messages;
873    }
874
875
876  function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
877    {
878    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
879
880    // get cached headers
881    if ($is_uid && ($headers = $this->get_cached_message($mailbox.'.msg', $id)))
882      return $headers;
883
884    $msg_id = $is_uid ? $this->_uid2id($id) : $id;
885    $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
886
887    // write headers cache
888    if ($headers)
889      $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
890
891    return $headers;
892    }
893
894
895  function get_body($uid, $part=1)
896    {
897    if (!($msg_id = $this->_uid2id($uid)))
898      return FALSE;
899
900        $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
901        $structure = iml_GetRawStructureArray($structure_str);
902    $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
903
904    $encoding = iml_GetPartEncodingCode($structure, $part);
905   
906    if ($encoding==3) $body = $this->mime_decode($body, 'base64');
907    else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
908
909    return $body;
910    }
911
912
913  function get_raw_body($uid)
914    {
915    if (!($msg_id = $this->_uid2id($uid)))
916      return FALSE;
917
918        $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
919        $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
920
921    return $body;   
922    }
923
924
925  // set message flag to one or several messages
926  // possible flags are: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
927  function set_flag($uids, $flag)
928    {
929    $flag = strtoupper($flag);
930    $msg_ids = array();
931    if (!is_array($uids))
932      $uids = explode(',',$uids);
933     
934    foreach ($uids as $uid) {
935      $msg_ids[$uid] = $this->_uid2id($uid);
936    }
937     
938    if ($flag=='UNDELETED')
939      $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
940    else if ($flag=='UNSEEN')
941      $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
942    else
943      $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
944
945    // reload message headers if cached
946    $cache_key = $this->mailbox.'.msg';
947    if ($this->caching_enabled)
948      {
949      foreach ($msg_ids as $uid => $id)
950        {
951        if ($cached_headers = $this->get_cached_message($cache_key, $uid))
952          {
953          $this->remove_message_cache($cache_key, $id);
954          //$this->get_headers($uid);
955          }
956        }
957
958      // close and re-open connection
959      // this prevents connection problems with Courier
960      $this->reconnect();
961      }
962
963    // set nr of messages that were flaged
964    $count = count($msg_ids);
965
966    // clear message count cache
967    if ($result && $flag=='SEEN')
968      $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
969    else if ($result && $flag=='UNSEEN')
970      $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
971    else if ($result && $flag=='DELETED')
972      $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
973
974    return $result;
975    }
976
977
978  // append a mail message (source) to a specific mailbox
979  function save_message($mbox_name, &$message)
980    {
981    $mbox_name = stripslashes($mbox_name);
982    $mailbox = $this->_mod_mailbox($mbox_name);
983
984    // make sure mailbox exists
985    if (in_array($mailbox, $this->_list_mailboxes()))
986      $saved = iil_C_Append($this->conn, $mailbox, $message);
987
988    if ($saved)
989      {
990      // increase messagecount of the target mailbox
991      $this->_set_messagecount($mailbox, 'ALL', 1);
992      }
993         
994    return $saved;
995    }
996
997
998  // move a message from one mailbox to another
999  function move_message($uids, $to_mbox, $from_mbox='')
1000    {
1001    $to_mbox = stripslashes($to_mbox);
1002    $from_mbox = stripslashes($from_mbox);
1003    $to_mbox = $this->_mod_mailbox($to_mbox);
1004    $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
1005
1006    // make sure mailbox exists
1007    if (!in_array($to_mbox, $this->_list_mailboxes()))
1008      {
1009      if (in_array(strtolower($to_mbox), $this->default_folders))
1010        $this->create_mailbox($to_mbox, TRUE);
1011      else
1012        return FALSE;
1013      }
1014
1015    // convert the list of uids to array
1016    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1017   
1018    // exit if no message uids are specified
1019    if (!is_array($a_uids))
1020      return false;
1021
1022    // convert uids to message ids
1023    $a_mids = array();
1024    foreach ($a_uids as $uid)
1025      $a_mids[] = $this->_uid2id($uid, $from_mbox);
1026
1027    $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
1028   
1029    // send expunge command in order to have the moved message
1030    // really deleted from the source mailbox
1031    if ($moved)
1032      {
1033      $this->_expunge($from_mbox, FALSE);
1034      $this->_clear_messagecount($from_mbox);
1035      $this->_clear_messagecount($to_mbox);
1036      }
1037
1038    // update cached message headers
1039    $cache_key = $from_mbox.'.msg';
1040    if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1041      {
1042      $start_index = 100000;
1043      foreach ($a_uids as $uid)
1044        {
1045        if(($index = array_search($uid, $a_cache_index)) !== FALSE)
1046          $start_index = min($index, $start_index);
1047        }
1048
1049      // clear cache from the lowest index on
1050      $this->clear_message_cache($cache_key, $start_index);
1051      }
1052
1053    return $moved;
1054    }
1055
1056
1057  // mark messages as deleted and expunge mailbox
1058  function delete_message($uids, $mbox_name='')
1059    {
1060    $mbox_name = stripslashes($mbox_name);
1061    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1062
1063    // convert the list of uids to array
1064    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1065   
1066    // exit if no message uids are specified
1067    if (!is_array($a_uids))
1068      return false;
1069
1070
1071    // convert uids to message ids
1072    $a_mids = array();
1073    foreach ($a_uids as $uid)
1074      $a_mids[] = $this->_uid2id($uid, $mailbox);
1075       
1076    $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1077   
1078    // send expunge command in order to have the deleted message
1079    // really deleted from the mailbox
1080    if ($deleted)
1081      {
1082      $this->_expunge($mailbox, FALSE);
1083      $this->_clear_messagecount($mailbox);
1084      }
1085
1086    // remove deleted messages from cache
1087    $cache_key = $mailbox.'.msg';
1088    if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1089      {
1090      $start_index = 100000;
1091      foreach ($a_uids as $uid)
1092        {
1093        $index = array_search($uid, $a_cache_index);
1094        $start_index = min($index, $start_index);
1095        }
1096
1097      // clear cache from the lowest index on
1098      $this->clear_message_cache($cache_key, $start_index);
1099      }
1100
1101    return $deleted;
1102    }
1103
1104
1105  // clear all messages in a specific mailbox
1106  function clear_mailbox($mbox_name=NULL)
1107    {
1108    $mbox_name = stripslashes($mbox_name);
1109    $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1110    $msg_count = $this->_messagecount($mailbox, 'ALL');
1111   
1112    if ($msg_count>0)
1113      {
1114      $cleared = iil_C_ClearFolder($this->conn, $mailbox);
1115     
1116      // make sure the message count cache is cleared as well
1117      if ($cleared)
1118        {
1119        $this->clear_message_cache($mailbox.'.msg');     
1120        $a_mailbox_cache = $this->get_cache('messagecount');
1121        unset($a_mailbox_cache[$mailbox]);
1122        $this->update_cache('messagecount', $a_mailbox_cache);
1123        }
1124       
1125      return $cleared;
1126      }
1127    else
1128      return 0;
1129    }
1130
1131
1132  // send IMAP expunge command and clear cache
1133  function expunge($mbox_name='', $clear_cache=TRUE)
1134    {
1135    $mbox_name = stripslashes($mbox_name);
1136    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1137    return $this->_expunge($mailbox, $clear_cache);
1138    }
1139
1140
1141  // send IMAP expunge command and clear cache
1142  function _expunge($mailbox, $clear_cache=TRUE)
1143    {
1144    $result = iil_C_Expunge($this->conn, $mailbox);
1145
1146    if ($result>=0 && $clear_cache)
1147      {
1148      //$this->clear_message_cache($mailbox.'.msg');
1149      $this->_clear_messagecount($mailbox);
1150      }
1151     
1152    return $result;
1153    }
1154
1155
1156  /* --------------------------------
1157   *        folder managment
1158   * --------------------------------*/
1159
1160
1161  // return an array with all folders available in IMAP server
1162  function list_unsubscribed($root='')
1163    {
1164    static $sa_unsubscribed;
1165   
1166    if (is_array($sa_unsubscribed))
1167      return $sa_unsubscribed;
1168     
1169    // retrieve list of folders from IMAP server
1170    $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1171
1172    // modify names with root dir
1173    foreach ($a_mboxes as $mbox_name)
1174      {
1175      $name = $this->_mod_mailbox($mbox_name, 'out');
1176      if (strlen($name))
1177        $a_folders[] = $name;
1178      }
1179
1180    // filter folders and sort them
1181    $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1182    return $sa_unsubscribed;
1183    }
1184
1185
1186  /**
1187   * Get quota
1188   * added by Nuny
1189   */
1190  function get_quota()
1191    {
1192    if ($this->get_capability('QUOTA'))
1193      {
1194      $result = iil_C_GetQuota($this->conn);
1195      if ($result["total"])
1196        return sprintf("%.2fMB / %.2fMB (%.0f%%)", $result["used"] / 1000.0, $result["total"] / 1000.0, $result["percent"]);       
1197      }
1198
1199    return FALSE;
1200    }
1201
1202
1203  // subscribe to a specific mailbox(es)
1204  function subscribe($mbox_name, $mode='subscribe')
1205    {
1206    if (is_array($mbox_name))
1207      $a_mboxes = $mbox_name;
1208    else if (is_string($mbox_name) && strlen($mbox_name))
1209      $a_mboxes = explode(',', $mbox_name);
1210   
1211    // let this common function do the main work
1212    return $this->_change_subscription($a_mboxes, 'subscribe');
1213    }
1214
1215
1216  // unsubscribe mailboxes
1217  function unsubscribe($mbox_name)
1218    {
1219    if (is_array($mbox_name))
1220      $a_mboxes = $mbox_name;
1221    else if (is_string($mbox_name) && strlen($mbox_name))
1222      $a_mboxes = explode(',', $mbox_name);
1223
1224    // let this common function do the main work
1225    return $this->_change_subscription($a_mboxes, 'unsubscribe');
1226    }
1227
1228
1229  // create a new mailbox on the server and register it in local cache
1230  function create_mailbox($name, $subscribe=FALSE)
1231    {
1232    $result = FALSE;
1233   
1234    // replace backslashes
1235    $name = preg_replace('/[\\\]+/', '-', $name);
1236
1237    $name_enc = UTF7EncodeString($name);
1238
1239    // reduce mailbox name to 100 chars
1240    $name_enc = substr($name_enc, 0, 100);
1241
1242    $abs_name = $this->_mod_mailbox($name_enc);
1243    $a_mailbox_cache = $this->get_cache('mailboxes');
1244       
1245    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
1246      $result = iil_C_CreateFolder($this->conn, $abs_name);
1247
1248    // update mailboxlist cache
1249    if ($result && $subscribe)
1250      $this->subscribe($name_enc);
1251
1252    return $result ? $name : FALSE;
1253    }
1254
1255
1256  // set a new name to an existing mailbox
1257  function rename_mailbox($mbox_name, $new_name, $subscribe=TRUE)
1258    {
1259    $result = FALSE;
1260
1261    // replace backslashes
1262    $name = preg_replace('/[\\\]+/', '-', $new_name);
1263
1264    $name_enc = UTF7EncodeString($new_name);
1265
1266    // reduce mailbox name to 100 chars
1267    $name_enc = substr($name_enc, 0, 100);
1268
1269    $abs_name = $this->_mod_mailbox($name_enc);
1270    $a_mailbox_cache = $this->get_cache('mailboxes');
1271
1272    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
1273      $result = iil_C_RenameFolder($this->conn, $mbox_name, $abs_name);
1274
1275    // update mailboxlist cache
1276    if ($result && $subscribe)
1277      $this->unsubscribe($mbox_name);
1278      $this->subscribe($name_enc);
1279
1280    return $result ? $name : FALSE;
1281    }
1282
1283
1284  // remove mailboxes from server
1285  function delete_mailbox($mbox_name)
1286    {
1287    $deleted = FALSE;
1288
1289    if (is_array($mbox_name))
1290      $a_mboxes = $mbox_name;
1291    else if (is_string($mbox_name) && strlen($mbox_name))
1292      $a_mboxes = explode(',', $mbox_name);
1293
1294    if (is_array($a_mboxes))
1295      foreach ($a_mboxes as $mbox_name)
1296        {
1297        $mailbox = $this->_mod_mailbox($mbox_name);
1298
1299        // unsubscribe mailbox before deleting
1300        iil_C_UnSubscribe($this->conn, $mailbox);
1301       
1302        // send delete command to server
1303        $result = iil_C_DeleteFolder($this->conn, $mailbox);
1304        if ($result>=0)
1305          $deleted = TRUE;
1306        }
1307
1308    // clear mailboxlist cache
1309    if ($deleted)
1310      {
1311      $this->clear_message_cache($mailbox.'.msg');
1312      $this->clear_cache('mailboxes');
1313      }
1314
1315    return $deleted;
1316    }
1317
1318
1319
1320
1321  /* --------------------------------
1322   *   internal caching methods
1323   * --------------------------------*/
1324
1325
1326  function set_caching($set)
1327    {
1328    if ($set && is_object($this->db))
1329      $this->caching_enabled = TRUE;
1330    else
1331      $this->caching_enabled = FALSE;
1332    }
1333
1334
1335  function get_cache($key)
1336    {
1337    // read cache
1338    if (!isset($this->cache[$key]) && $this->caching_enabled)
1339      {
1340      $cache_data = $this->_read_cache_record('IMAP.'.$key);
1341      $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
1342      }
1343   
1344    return $this->cache[$key];
1345    }
1346
1347
1348  function update_cache($key, $data)
1349    {
1350    $this->cache[$key] = $data;
1351    $this->cache_changed = TRUE;
1352    $this->cache_changes[$key] = TRUE;
1353    }
1354
1355
1356  function write_cache()
1357    {
1358    if ($this->caching_enabled && $this->cache_changed)
1359      {
1360      foreach ($this->cache as $key => $data)
1361        {
1362        if ($this->cache_changes[$key])
1363          $this->_write_cache_record('IMAP.'.$key, serialize($data));
1364        }
1365      }   
1366    }
1367
1368
1369  function clear_cache($key=NULL)
1370    {
1371    if ($key===NULL)
1372      {
1373      foreach ($this->cache as $key => $data)
1374        $this->_clear_cache_record('IMAP.'.$key);
1375
1376      $this->cache = array();
1377      $this->cache_changed = FALSE;
1378      $this->cache_changes = array();
1379      }
1380    else
1381      {
1382      $this->_clear_cache_record('IMAP.'.$key);
1383      $this->cache_changes[$key] = FALSE;
1384      unset($this->cache[$key]);
1385      }
1386    }
1387
1388
1389
1390  function _read_cache_record($key)
1391    {
1392    $cache_data = FALSE;
1393   
1394    if ($this->db)
1395      {
1396      // get cached data from DB
1397      $sql_result = $this->db->query(
1398        "SELECT cache_id, data
1399         FROM ".get_table_name('cache')."
1400         WHERE  user_id=?
1401         AND    cache_key=?",
1402        $_SESSION['user_id'],
1403        $key);
1404
1405      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1406        {
1407        $cache_data = $sql_arr['data'];
1408        $this->cache_keys[$key] = $sql_arr['cache_id'];
1409        }
1410      }
1411
1412    return $cache_data;   
1413    }
1414   
1415
1416  function _write_cache_record($key, $data)
1417    {
1418    if (!$this->db)
1419      return FALSE;
1420
1421    // check if we already have a cache entry for this key
1422    if (!isset($this->cache_keys[$key]))
1423      {
1424      $sql_result = $this->db->query(
1425        "SELECT cache_id
1426         FROM ".get_table_name('cache')."
1427         WHERE  user_id=?
1428         AND    cache_key=?",
1429        $_SESSION['user_id'],
1430        $key);
1431                                     
1432      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1433        $this->cache_keys[$key] = $sql_arr['cache_id'];
1434      else
1435        $this->cache_keys[$key] = FALSE;
1436      }
1437
1438    // update existing cache record
1439    if ($this->cache_keys[$key])
1440      {
1441      $this->db->query(
1442        "UPDATE ".get_table_name('cache')."
1443         SET    created=now(),
1444                data=?
1445         WHERE  user_id=?
1446         AND    cache_key=?",
1447        $data,
1448        $_SESSION['user_id'],
1449        $key);
1450      }
1451    // add new cache record
1452    else
1453      {
1454      $this->db->query(
1455        "INSERT INTO ".get_table_name('cache')."
1456         (created, user_id, cache_key, data)
1457         VALUES (now(), ?, ?, ?)",
1458        $_SESSION['user_id'],
1459        $key,
1460        $data);
1461      }
1462    }
1463
1464
1465  function _clear_cache_record($key)
1466    {
1467    $this->db->query(
1468      "DELETE FROM ".get_table_name('cache')."
1469       WHERE  user_id=?
1470       AND    cache_key=?",
1471      $_SESSION['user_id'],
1472      $key);
1473    }
1474
1475
1476
1477  /* --------------------------------
1478   *   message caching methods
1479   * --------------------------------*/
1480   
1481
1482  // checks if the cache is up-to-date
1483  // return: -3 = off, -2 = incomplete, -1 = dirty
1484  function check_cache_status($mailbox, $cache_key)
1485    {
1486    if (!$this->caching_enabled)
1487      return -3;
1488
1489    $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1490    $msg_count = $this->_messagecount($mailbox);
1491    $cache_count = count($cache_index);
1492
1493    // console("Cache check: $msg_count !== ".count($cache_index));
1494
1495    if ($cache_count==$msg_count)
1496      {
1497      // get highest index
1498      $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1499      $cache_uid = array_pop($cache_index);
1500     
1501      // uids of highest message matches -> cache seems OK
1502      if ($cache_uid == $header->uid)
1503        return 1;
1504
1505      // cache is dirty
1506      return -1;
1507      }
1508    // if cache count differs less than 10% report as dirty
1509    else if (abs($msg_count - $cache_count) < $msg_count/10)
1510      return -1;
1511    else
1512      return -2;
1513    }
1514
1515
1516
1517  function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1518    {
1519    $cache_key = "$key:$from:$to:$sort_field:$sort_order";
1520    $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
1521   
1522    if (!in_array($sort_field, $db_header_fields))
1523      $sort_field = 'idx';
1524   
1525    if ($this->caching_enabled && !isset($this->cache[$cache_key]))
1526      {
1527      $this->cache[$cache_key] = array();
1528      $sql_result = $this->db->limitquery(
1529        "SELECT idx, uid, headers
1530         FROM ".get_table_name('messages')."
1531         WHERE  user_id=?
1532         AND    cache_key=?
1533         ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
1534         strtoupper($sort_order),
1535        $from,
1536        $to-$from,
1537        $_SESSION['user_id'],
1538        $key);
1539
1540      while ($sql_arr = $this->db->fetch_assoc($sql_result))
1541        {
1542        $uid = $sql_arr['uid'];
1543        $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
1544        }
1545      }
1546     
1547    return $this->cache[$cache_key];
1548    }
1549
1550
1551  function get_cached_message($key, $uid, $body=FALSE)
1552    {
1553    if (!$this->caching_enabled)
1554      return FALSE;
1555
1556    $internal_key = '__single_msg';
1557    if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
1558      {
1559      $sql_select = "idx, uid, headers";
1560      if ($body)
1561        $sql_select .= ", body";
1562     
1563      $sql_result = $this->db->query(
1564        "SELECT $sql_select
1565         FROM ".get_table_name('messages')."
1566         WHERE  user_id=?
1567         AND    cache_key=?
1568         AND    uid=?",
1569        $_SESSION['user_id'],
1570        $key,
1571        $uid);
1572     
1573      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1574        {
1575        $headers = unserialize($sql_arr['headers']);
1576        if (is_object($headers) && !empty($sql_arr['body']))
1577          $headers->body = $sql_arr['body'];
1578
1579        $this->cache[$internal_key][$uid] = $headers;
1580        }
1581      }
1582
1583    return $this->cache[$internal_key][$uid];
1584    }
1585
1586   
1587  function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
1588    {
1589    static $sa_message_index = array();
1590   
1591    // empty key -> empty array
1592    if (empty($key))
1593      return array();
1594   
1595    if (!empty($sa_message_index[$key]) && !$force)
1596      return $sa_message_index[$key];
1597   
1598    $sa_message_index[$key] = array();
1599    $sql_result = $this->db->query(
1600      "SELECT idx, uid
1601       FROM ".get_table_name('messages')."
1602       WHERE  user_id=?
1603       AND    cache_key=?
1604       ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
1605      $_SESSION['user_id'],
1606      $key);
1607
1608    while ($sql_arr = $this->db->fetch_assoc($sql_result))
1609      $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
1610     
1611    return $sa_message_index[$key];
1612    }
1613
1614
1615  function add_message_cache($key, $index, $headers)
1616    {
1617    if (!$key || !is_object($headers) || empty($headers->uid))
1618      return;
1619
1620    $this->db->query(
1621      "INSERT INTO ".get_table_name('messages')."
1622       (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
1623       VALUES (?, 0, ?, now(), ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
1624      $_SESSION['user_id'],
1625      $key,
1626      $index,
1627      $headers->uid,
1628      (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
1629      (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
1630      (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
1631      (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
1632      (int)$headers->size,
1633      serialize($headers));
1634    }
1635   
1636   
1637  function remove_message_cache($key, $index)
1638    {
1639    $this->db->query(
1640      "DELETE FROM ".get_table_name('messages')."
1641       WHERE  user_id=?
1642       AND    cache_key=?
1643       AND    idx=?",
1644      $_SESSION['user_id'],
1645      $key,
1646      $index);
1647    }
1648
1649
1650  function clear_message_cache($key, $start_index=1)
1651    {
1652    $this->db->query(
1653      "DELETE FROM ".get_table_name('messages')."
1654       WHERE  user_id=?
1655       AND    cache_key=?
1656       AND    idx>=?",
1657      $_SESSION['user_id'],
1658      $key,
1659      $start_index);
1660    }
1661
1662
1663
1664
1665  /* --------------------------------
1666   *   encoding/decoding methods
1667   * --------------------------------*/
1668
1669 
1670  function decode_address_list($input, $max=NULL)
1671    {
1672    $a = $this->_parse_address_list($input);
1673    $out = array();
1674
1675    if (!is_array($a))
1676      return $out;
1677
1678    $c = count($a);
1679    $j = 0;
1680
1681    foreach ($a as $val)
1682      {
1683      $j++;
1684      $address = $val['address'];
1685      $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
1686      $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
1687     
1688      $out[$j] = array('name' => $name,
1689                       'mailto' => $address,
1690                       'string' => $string);
1691             
1692      if ($max && $j==$max)
1693        break;
1694      }
1695   
1696    return $out;
1697    }
1698
1699
1700  function decode_header($input, $remove_quotes=FALSE)
1701    {
1702    $str = $this->decode_mime_string((string)$input);
1703    if ($str{0}=='"' && $remove_quotes)
1704      {
1705      $str = str_replace('"', '', $str);
1706      }
1707   
1708    return $str;
1709    }
1710
1711
1712  /**
1713   * Decode a mime-encoded string to internal charset
1714   *
1715   * @access static
1716   */
1717  function decode_mime_string($input, $recursive=false)
1718    {
1719    $out = '';
1720
1721    $pos = strpos($input, '=?');
1722    if ($pos !== false)
1723      {
1724      $out = substr($input, 0, $pos);
1725 
1726      $end_cs_pos = strpos($input, "?", $pos+2);
1727      $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1728      $end_pos = strpos($input, "?=", $end_en_pos+1);
1729 
1730      $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1731      $rest = substr($input, $end_pos+2);
1732
1733      $out .= rcube_imap::_decode_mime_string_part($encstr);
1734      $out .= rcube_imap::decode_mime_string($rest);
1735
1736      return $out;
1737      }
1738     
1739    // no encoding information, defaults to what is specified in the class header
1740    return rcube_charset_convert($input, 'ISO-8859-1');
1741    }
1742
1743
1744  /**
1745   * Decode a part of a mime-encoded string
1746   *
1747   * @access static
1748   */
1749  function _decode_mime_string_part($str)
1750    {
1751    $a = explode('?', $str);
1752    $count = count($a);
1753
1754    // should be in format "charset?encoding?base64_string"
1755    if ($count >= 3)
1756      {
1757      for ($i=2; $i<$count; $i++)
1758        $rest.=$a[$i];
1759
1760      if (($a[1]=="B")||($a[1]=="b"))
1761        $rest = base64_decode($rest);
1762      else if (($a[1]=="Q")||($a[1]=="q"))
1763        {
1764        $rest = str_replace("_", " ", $rest);
1765        $rest = quoted_printable_decode($rest);
1766        }
1767
1768      return rcube_charset_convert($rest, $a[0]);
1769      }
1770    else
1771      return $str;    // we dont' know what to do with this 
1772    }
1773
1774
1775  function mime_decode($input, $encoding='7bit')
1776    {
1777    switch (strtolower($encoding))
1778      {
1779      case '7bit':
1780        return $input;
1781        break;
1782     
1783      case 'quoted-printable':
1784        return quoted_printable_decode($input);
1785        break;
1786     
1787      case 'base64':
1788        return base64_decode($input);
1789        break;
1790     
1791      default:
1792        return $input;
1793      }
1794    }
1795
1796
1797  function mime_encode($input, $encoding='7bit')
1798    {
1799    switch ($encoding)
1800      {
1801      case 'quoted-printable':
1802        return quoted_printable_encode($input);
1803        break;
1804
1805      case 'base64':
1806        return base64_encode($input);
1807        break;
1808
1809      default:
1810        return $input;
1811      }
1812    }
1813
1814
1815  // convert body chars according to the ctype_parameters
1816  function charset_decode($body, $ctype_param)
1817    {
1818    if (is_array($ctype_param) && !empty($ctype_param['charset']))
1819      return rcube_charset_convert($body, $ctype_param['charset']);
1820
1821    // defaults to what is specified in the class header
1822    return rcube_charset_convert($body,  'ISO-8859-1');
1823    }
1824
1825
1826
1827
1828  /* --------------------------------
1829   *         private methods
1830   * --------------------------------*/
1831
1832
1833  function _mod_mailbox($mbox_name, $mode='in')
1834    {
1835    if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || ($mbox_name == 'INBOX' && $mode == 'in'))
1836      return $mbox_name;
1837
1838    if (!empty($this->root_dir) && $mode=='in')
1839      $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
1840    else if (strlen($this->root_dir) && $mode=='out')
1841      $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
1842
1843    return $mbox_name;
1844    }
1845
1846
1847  // sort mailboxes first by default folders and then in alphabethical order
1848  function _sort_mailbox_list($a_folders)
1849    {
1850    $a_out = $a_defaults = array();
1851
1852    // find default folders and skip folders starting with '.'
1853    foreach($a_folders as $i => $folder)
1854      {
1855      if ($folder{0}=='.')
1856        continue;
1857       
1858      if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1859        $a_defaults[$p] = $folder;
1860      else
1861        $a_out[] = $folder;
1862      }
1863
1864    sort($a_out);
1865    ksort($a_defaults);
1866   
1867    return array_merge($a_defaults, $a_out);
1868    }
1869
1870  function get_id($uid, $mbox_name=NULL)
1871    {
1872      return $this->_uid2id($uid, $mbox_name);
1873    }
1874 
1875  function get_uid($id,$mbox_name=NULL)
1876    {
1877      return $this->_id2uid($id, $mbox_name);
1878    }
1879
1880  function _uid2id($uid, $mbox_name=NULL)
1881    {
1882    if (!$mbox_name)
1883      $mbox_name = $this->mailbox;
1884     
1885    if (!isset($this->uid_id_map[$mbox_name][$uid]))
1886      $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
1887
1888    return $this->uid_id_map[$mbox_name][$uid];
1889    }
1890
1891  function _id2uid($id, $mbox_name=NULL)
1892    {
1893    if (!$mbox_name)
1894      $mbox_name = $this->mailbox;
1895     
1896    return iil_C_ID2UID($this->conn, $mbox_name, $id);
1897    }
1898
1899
1900  // parse string or array of server capabilities and put them in internal array
1901  function _parse_capability($caps)
1902    {
1903    if (!is_array($caps))
1904      $cap_arr = explode(' ', $caps);
1905    else
1906      $cap_arr = $caps;
1907   
1908    foreach ($cap_arr as $cap)
1909      {
1910      if ($cap=='CAPABILITY')
1911        continue;
1912
1913      if (strpos($cap, '=')>0)
1914        {
1915        list($key, $value) = explode('=', $cap);
1916        if (!is_array($this->capabilities[$key]))
1917          $this->capabilities[$key] = array();
1918         
1919        $this->capabilities[$key][] = $value;
1920        }
1921      else
1922        $this->capabilities[$cap] = TRUE;
1923      }
1924    }
1925
1926
1927  // subscribe/unsubscribe a list of mailboxes and update local cache
1928  function _change_subscription($a_mboxes, $mode)
1929    {
1930    $updated = FALSE;
1931   
1932    if (is_array($a_mboxes))
1933      foreach ($a_mboxes as $i => $mbox_name)
1934        {
1935        $mailbox = $this->_mod_mailbox($mbox_name);
1936        $a_mboxes[$i] = $mailbox;
1937
1938        if ($mode=='subscribe')
1939          $result = iil_C_Subscribe($this->conn, $mailbox);
1940        else if ($mode=='unsubscribe')
1941          $result = iil_C_UnSubscribe($this->conn, $mailbox);
1942
1943        if ($result>=0)
1944          $updated = TRUE;
1945        }
1946       
1947    // get cached mailbox list   
1948    if ($updated)
1949      {
1950      $a_mailbox_cache = $this->get_cache('mailboxes');
1951      if (!is_array($a_mailbox_cache))
1952        return $updated;
1953
1954      // modify cached list
1955      if ($mode=='subscribe')
1956        $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1957      else if ($mode=='unsubscribe')
1958        $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1959       
1960      // write mailboxlist to cache
1961      $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1962      }
1963
1964    return $updated;
1965    }
1966
1967
1968  // increde/decrese messagecount for a specific mailbox
1969  function _set_messagecount($mbox_name, $mode, $increment)
1970    {
1971    $a_mailbox_cache = FALSE;
1972    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
1973    $mode = strtoupper($mode);
1974
1975    $a_mailbox_cache = $this->get_cache('messagecount');
1976   
1977    if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1978      return FALSE;
1979   
1980    // add incremental value to messagecount
1981    $a_mailbox_cache[$mailbox][$mode] += $increment;
1982   
1983    // there's something wrong, delete from cache
1984    if ($a_mailbox_cache[$mailbox][$mode] < 0)
1985      unset($a_mailbox_cache[$mailbox][$mode]);
1986
1987    // write back to cache
1988    $this->update_cache('messagecount', $a_mailbox_cache);
1989   
1990    return TRUE;
1991    }
1992
1993
1994  // remove messagecount of a specific mailbox from cache
1995  function _clear_messagecount($mbox_name='')
1996    {
1997    $a_mailbox_cache = FALSE;
1998    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
1999
2000    $a_mailbox_cache = $this->get_cache('messagecount');
2001
2002    if (is_array($a_mailbox_cache[$mailbox]))
2003      {
2004      unset($a_mailbox_cache[$mailbox]);
2005      $this->update_cache('messagecount', $a_mailbox_cache);
2006      }
2007    }
2008
2009
2010  function _parse_address_list($str)
2011    {
2012    $a = $this->_explode_quoted_string(',', $str);
2013    $result = array();
2014
2015    foreach ($a as $key => $val)
2016      {
2017      $val = str_replace("\"<", "\" <", $val);
2018      $sub_a = $this->_explode_quoted_string(' ', $val);
2019     
2020      foreach ($sub_a as $k => $v)
2021        {
2022        if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0))
2023          $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
2024        else
2025          $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
2026        }
2027       
2028      if (empty($result[$key]['name']))
2029        $result[$key]['name'] = $result[$key]['address'];
2030       
2031      $result[$key]['name'] = $this->decode_header($result[$key]['name']);
2032      }
2033   
2034    return $result;
2035    }
2036
2037
2038  function _explode_quoted_string($delimiter, $string)
2039    {
2040    $quotes = explode("\"", $string);
2041    foreach ($quotes as $key => $val)
2042      if (($key % 2) == 1)
2043        $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
2044       
2045    $string = implode("\"", $quotes);
2046
2047    $result = explode($delimiter, $string);
2048    foreach ($result as $key => $val)
2049      $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
2050   
2051    return $result;
2052    }
2053  }
2054
2055
2056
2057
2058
2059function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
2060  {
2061  $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2062  $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2063  $eol = "\r\n";
2064  $escape = "=";
2065  $output = "";
2066
2067  while( list(, $line) = each($lines))
2068    {
2069    //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2070    $linlen = strlen($line);
2071    $newline = "";
2072    for($i = 0; $i < $linlen; $i++)
2073      {
2074      $c = substr( $line, $i, 1 );
2075      $dec = ord( $c );
2076      if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2077        {
2078        $c = "=2E";
2079        }
2080      if ( $dec == 32 )
2081        {
2082        if ( $i == ( $linlen - 1 ) ) // convert space at eol only
2083          {
2084          $c = "=20";
2085          }
2086        else if ( $space_conv )
2087          {
2088          $c = "=20";
2089          }
2090        }
2091      else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
2092        {
2093        $h2 = floor($dec/16);
2094        $h1 = floor($dec%16);
2095        $c = $escape.$hex["$h2"].$hex["$h1"];
2096        }
2097         
2098      if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
2099        {
2100        $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
2101        $newline = "";
2102        // check if newline first character will be point or not
2103        if ( $dec == 46 )
2104          {
2105          $c = "=2E";
2106          }
2107        }
2108      $newline .= $c;
2109      } // end of for
2110    $output .= $newline.$eol;
2111    } // end of while
2112
2113  return trim($output);
2114  }
2115
2116?>
Note: See TracBrowser for help on using the repository browser.