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

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

Re-enabled message sorting; removed unused vars

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.0 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    $headers_sorted = FALSE;
510    $cache_key = $mailbox.'.msg';
511    $cache_status = $this->check_cache_status($mailbox, $cache_key);
512
513    // cache is OK, we can get all messages from local cache
514    if ($cache_status>0)
515      {
516      $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
517      $headers_sorted = TRUE;
518      }
519    else
520      {
521      // retrieve headers from IMAP
522      if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
523        {
524        $msgs = $msg_index[$begin];
525        for ($i=$begin+1; $i < $end; $i++)
526          $msgs = $msgs.','.$msg_index[$i];
527        }
528      else
529        {
530        $msgs = sprintf("%d:%d", $begin+1, $end);       
531        }
532
533
534      // cache is dirty, sync it
535      if ($this->caching_enabled && $cache_status==-1 && !$recursive)
536        {
537        $this->sync_header_index($mailbox);
538        return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
539        }     
540
541
542      // fetch reuested headers from server
543      $a_msg_headers = array();
544      $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
545
546      // delete cached messages with a higher index than $max
547      $this->clear_message_cache($cache_key, $max);
548
549
550      // kick child process to sync cache
551      // ...
552
553      }
554
555
556    // return empty array if no messages found
557        if (!is_array($a_msg_headers) || empty($a_msg_headers))
558                return array();
559
560
561    // if not already sorted
562    if (!$headers_sorted)
563      $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
564
565
566    if (!$headers_sorted && $this->sort_order == 'DESC')
567      $a_msg_headers = array_reverse($a_msg_headers);
568
569
570    return array_values($a_msg_headers);
571    }
572   
573
574  /**
575   * Public method for listing a specific set of headers
576   * convert mailbox name with root dir first
577   *
578   * @param   string   Mailbox/folder name
579   * @param   array    List of message ids to list
580   * @param   number   Current page to list
581   * @param   string   Header field to sort by
582   * @param   string   Sort order [ASC|DESC]
583   * @return  array    Indexed array with message header objects
584   * @access  public   
585   */
586  function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
587    {
588    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
589    return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);   
590    }
591   
592
593  /**
594   * Private method for listing a set of message headers
595   *
596   * @access  private
597   * @see     rcube_imap::list_header_set
598   */
599  function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
600    {
601    // also accept a comma-separated list of message ids
602    if (is_string($msgs))
603      $msgs = split(',', $msgs);
604     
605    if (!strlen($mailbox) || empty($msgs))
606      return array();
607
608    if ($sort_field!=NULL)
609      $this->sort_field = $sort_field;
610    if ($sort_order!=NULL)
611      $this->sort_order = strtoupper($sort_order);
612
613    $max = count($msgs);
614    $start_msg = ($this->list_page-1) * $this->page_size;
615
616    // fetch reuested headers from server
617    $a_msg_headers = array();
618    $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
619
620    // return empty array if no messages found
621        if (!is_array($a_msg_headers) || empty($a_msg_headers))
622                return array();
623
624    // if not already sorted
625    $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
626
627        // only return the requested part of the set
628        return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
629    }
630
631
632  /**
633   * Helper function to get first and last index of the requested set
634   *
635   * @param  number  message count
636   * @param  mixed   page number to show, or string 'all'
637   * @return array   array with two values: first index, last index
638   * @access private
639   */
640  function _get_message_range($max, $page)
641    {
642    $start_msg = ($this->list_page-1) * $this->page_size;
643   
644    if ($page=='all')
645      {
646      $begin = 0;
647      $end = $max;
648      }
649    else if ($this->sort_order=='DESC')
650      {
651      $begin = $max - $this->page_size - $start_msg;
652      $end =   $max - $start_msg;
653      }
654    else
655      {
656      $begin = $start_msg;
657      $end   = $start_msg + $this->page_size;
658      }
659
660    if ($begin < 0) $begin = 0;
661    if ($end < 0) $end = $max;
662    if ($end > $max) $end = $max;
663   
664    return array($begin, $end);
665    }
666   
667   
668
669  /**
670   * Fetches message headers
671   * Used for loop
672   *
673   * @param  string  Mailbox name
674   * @param  string  Message index to fetch
675   * @param  array   Reference to message headers array
676   * @param  array   Array with cache index
677   * @return number  Number of deleted messages
678   * @access private
679   */
680  function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
681    {
682    // cache is incomplete
683    $cache_index = $this->get_message_cache_index($cache_key);
684   
685    // fetch reuested headers from server
686    $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
687    $deleted_count = 0;
688   
689    if (!empty($a_header_index))
690      {
691      foreach ($a_header_index as $i => $headers)
692        {
693        if ($headers->deleted && $this->skip_deleted)
694          {
695          // delete from cache
696          if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
697            $this->remove_message_cache($cache_key, $headers->id);
698
699          $deleted_count++;
700          continue;
701          }
702
703        // add message to cache
704        if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
705          $this->add_message_cache($cache_key, $headers->id, $headers);
706
707        $a_msg_headers[$headers->uid] = $headers;
708        }
709      }
710       
711    return $deleted_count;
712    }
713   
714 
715  // return sorted array of message UIDs
716  function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
717    {
718    if ($sort_field!=NULL)
719      $this->sort_field = $sort_field;
720    if ($sort_order!=NULL)
721      $this->sort_order = strtoupper($sort_order);
722
723    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
724    $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
725
726    // have stored it in RAM
727    if (isset($this->cache[$key]))
728      return $this->cache[$key];
729
730    // check local cache
731    $cache_key = $mailbox.'.msg';
732    $cache_status = $this->check_cache_status($mailbox, $cache_key);
733
734    // cache is OK
735    if ($cache_status>0)
736      {
737      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
738      return array_values($a_index);
739      }
740
741
742    // fetch complete message index
743    $msg_count = $this->_messagecount($mailbox);
744    if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
745      {
746      if ($this->sort_order == 'DESC')
747        $a_index = array_reverse($a_index);
748
749      $this->cache[$key] = $a_index;
750
751      }
752    else
753      {
754      $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
755      $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
756   
757      if ($this->sort_order=="ASC")
758        asort($a_index);
759      else if ($this->sort_order=="DESC")
760        arsort($a_index);
761       
762      $i = 0;
763      $this->cache[$key] = array();
764      foreach ($a_index as $index => $value)
765        $this->cache[$key][$i++] = $a_uids[$index];
766      }
767
768    return $this->cache[$key];
769    }
770
771
772  function sync_header_index($mailbox)
773    {
774    $cache_key = $mailbox.'.msg';
775    $cache_index = $this->get_message_cache_index($cache_key);
776    $msg_count = $this->_messagecount($mailbox);
777
778    // fetch complete message index
779    $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
780       
781    foreach ($a_message_index as $id => $uid)
782      {
783      // message in cache at correct position
784      if ($cache_index[$id] == $uid)
785        {
786// console("$id / $uid: OK");
787        unset($cache_index[$id]);
788        continue;
789        }
790       
791      // message in cache but in wrong position
792      if (in_array((string)$uid, $cache_index, TRUE))
793        {
794// console("$id / $uid: Moved");
795        unset($cache_index[$id]);       
796        }
797     
798      // other message at this position
799      if (isset($cache_index[$id]))
800        {
801// console("$id / $uid: Delete");
802        $this->remove_message_cache($cache_key, $id);
803        unset($cache_index[$id]);
804        }
805       
806
807// console("$id / $uid: Add");
808
809      // fetch complete headers and add to cache
810      $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
811      $this->add_message_cache($cache_key, $headers->id, $headers);
812      }
813
814    // those ids that are still in cache_index have been deleted     
815    if (!empty($cache_index))
816      {
817      foreach ($cache_index as $id => $uid)
818        $this->remove_message_cache($cache_key, $id);
819      }
820    }
821
822
823  /**
824   * Invoke search request to IMAP server
825   *
826   * @param  string  mailbox name to search in
827   * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
828   * @param  string  search string
829   * @return array   search results as list of message ids
830   * @access public
831   */
832  function search($mbox_name='', $criteria='ALL', $str=NULL)
833    {
834    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
835    if ($str && $criteria)
836      {
837      $criteria = 'CHARSET UTF-8 '.$criteria.' "'.UTF7EncodeString($str).'"';
838      return $this->_search_index($mailbox, $criteria);
839      }
840    else
841      return $this->_search_index($mailbox, $criteria);
842    }   
843
844
845  /**
846   * Private search method
847   *
848   * @return array   search results as list of message ids
849   * @access private
850   * @see rcube_imap::search()
851   */
852  function _search_index($mailbox, $criteria='ALL')
853    {
854    $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
855    // clean message list (there might be some empty entries)
856    if (is_array($a_messages))
857      {
858      foreach ($a_messages as $i => $val)
859        if (empty($val))
860          unset($a_messages[$i]);
861      }
862       
863    return $a_messages;
864    }
865
866
867  function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
868    {
869    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
870
871    // get cached headers
872    if ($is_uid && ($headers = $this->get_cached_message($mailbox.'.msg', $id)))
873      return $headers;
874
875    $msg_id = $is_uid ? $this->_uid2id($id) : $id;
876    $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
877
878    // write headers cache
879    if ($headers)
880      $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
881
882    return $headers;
883    }
884
885
886  function get_body($uid, $part=1)
887    {
888    if (!($msg_id = $this->_uid2id($uid)))
889      return FALSE;
890
891        $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
892        $structure = iml_GetRawStructureArray($structure_str);
893    $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
894
895    $encoding = iml_GetPartEncodingCode($structure, $part);
896   
897    if ($encoding==3) $body = $this->mime_decode($body, 'base64');
898    else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
899
900    return $body;
901    }
902
903
904  function get_raw_body($uid)
905    {
906    if (!($msg_id = $this->_uid2id($uid)))
907      return FALSE;
908
909        $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
910        $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
911
912    return $body;   
913    }
914
915
916  // set message flag to one or several messages
917  // possible flags are: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
918  function set_flag($uids, $flag)
919    {
920    $flag = strtoupper($flag);
921    $msg_ids = array();
922    if (!is_array($uids))
923      $uids = explode(',',$uids);
924     
925    foreach ($uids as $uid) {
926      $msg_ids[$uid] = $this->_uid2id($uid);
927    }
928     
929    if ($flag=='UNDELETED')
930      $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
931    else if ($flag=='UNSEEN')
932      $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
933    else
934      $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
935
936    // reload message headers if cached
937    $cache_key = $this->mailbox.'.msg';
938    if ($this->caching_enabled)
939      {
940      foreach ($msg_ids as $uid => $id)
941        {
942        if ($cached_headers = $this->get_cached_message($cache_key, $uid))
943          {
944          $this->remove_message_cache($cache_key, $id);
945          //$this->get_headers($uid);
946          }
947        }
948
949      // close and re-open connection
950      // this prevents connection problems with Courier
951      $this->reconnect();
952      }
953
954    // set nr of messages that were flaged
955    $count = count($msg_ids);
956
957    // clear message count cache
958    if ($result && $flag=='SEEN')
959      $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
960    else if ($result && $flag=='UNSEEN')
961      $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
962    else if ($result && $flag=='DELETED')
963      $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
964
965    return $result;
966    }
967
968
969  // append a mail message (source) to a specific mailbox
970  function save_message($mbox_name, &$message)
971    {
972    $mbox_name = stripslashes($mbox_name);
973    $mailbox = $this->_mod_mailbox($mbox_name);
974
975    // make sure mailbox exists
976    if (in_array($mailbox, $this->_list_mailboxes()))
977      $saved = iil_C_Append($this->conn, $mailbox, $message);
978
979    if ($saved)
980      {
981      // increase messagecount of the target mailbox
982      $this->_set_messagecount($mailbox, 'ALL', 1);
983      }
984         
985    return $saved;
986    }
987
988
989  // move a message from one mailbox to another
990  function move_message($uids, $to_mbox, $from_mbox='')
991    {
992    $to_mbox = stripslashes($to_mbox);
993    $from_mbox = stripslashes($from_mbox);
994    $to_mbox = $this->_mod_mailbox($to_mbox);
995    $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
996
997    // make sure mailbox exists
998    if (!in_array($to_mbox, $this->_list_mailboxes()))
999      {
1000      if (in_array(strtolower($to_mbox), $this->default_folders))
1001        $this->create_mailbox($to_mbox, TRUE);
1002      else
1003        return FALSE;
1004      }
1005
1006    // convert the list of uids to array
1007    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1008   
1009    // exit if no message uids are specified
1010    if (!is_array($a_uids))
1011      return false;
1012
1013    // convert uids to message ids
1014    $a_mids = array();
1015    foreach ($a_uids as $uid)
1016      $a_mids[] = $this->_uid2id($uid, $from_mbox);
1017
1018    $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
1019   
1020    // send expunge command in order to have the moved message
1021    // really deleted from the source mailbox
1022    if ($moved)
1023      {
1024      $this->_expunge($from_mbox, FALSE);
1025      $this->_clear_messagecount($from_mbox);
1026      $this->_clear_messagecount($to_mbox);
1027      }
1028
1029    // update cached message headers
1030    $cache_key = $from_mbox.'.msg';
1031    if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1032      {
1033      $start_index = 100000;
1034      foreach ($a_uids as $uid)
1035        {
1036        if(($index = array_search($uid, $a_cache_index)) !== FALSE)
1037          $start_index = min($index, $start_index);
1038        }
1039
1040      // clear cache from the lowest index on
1041      $this->clear_message_cache($cache_key, $start_index);
1042      }
1043
1044    return $moved;
1045    }
1046
1047
1048  // mark messages as deleted and expunge mailbox
1049  function delete_message($uids, $mbox_name='')
1050    {
1051    $mbox_name = stripslashes($mbox_name);
1052    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1053
1054    // convert the list of uids to array
1055    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1056   
1057    // exit if no message uids are specified
1058    if (!is_array($a_uids))
1059      return false;
1060
1061
1062    // convert uids to message ids
1063    $a_mids = array();
1064    foreach ($a_uids as $uid)
1065      $a_mids[] = $this->_uid2id($uid, $mailbox);
1066       
1067    $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1068   
1069    // send expunge command in order to have the deleted message
1070    // really deleted from the mailbox
1071    if ($deleted)
1072      {
1073      $this->_expunge($mailbox, FALSE);
1074      $this->_clear_messagecount($mailbox);
1075      }
1076
1077    // remove deleted messages from cache
1078    $cache_key = $mailbox.'.msg';
1079    if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1080      {
1081      $start_index = 100000;
1082      foreach ($a_uids as $uid)
1083        {
1084        $index = array_search($uid, $a_cache_index);
1085        $start_index = min($index, $start_index);
1086        }
1087
1088      // clear cache from the lowest index on
1089      $this->clear_message_cache($cache_key, $start_index);
1090      }
1091
1092    return $deleted;
1093    }
1094
1095
1096  // clear all messages in a specific mailbox
1097  function clear_mailbox($mbox_name=NULL)
1098    {
1099    $mbox_name = stripslashes($mbox_name);
1100    $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1101    $msg_count = $this->_messagecount($mailbox, 'ALL');
1102   
1103    if ($msg_count>0)
1104      {
1105      $cleared = iil_C_ClearFolder($this->conn, $mailbox);
1106     
1107      // make sure the message count cache is cleared as well
1108      if ($cleared)
1109        {
1110        $this->clear_message_cache($mailbox.'.msg');     
1111        $a_mailbox_cache = $this->get_cache('messagecount');
1112        unset($a_mailbox_cache[$mailbox]);
1113        $this->update_cache('messagecount', $a_mailbox_cache);
1114        }
1115       
1116      return $cleared;
1117      }
1118    else
1119      return 0;
1120    }
1121
1122
1123  // send IMAP expunge command and clear cache
1124  function expunge($mbox_name='', $clear_cache=TRUE)
1125    {
1126    $mbox_name = stripslashes($mbox_name);
1127    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1128    return $this->_expunge($mailbox, $clear_cache);
1129    }
1130
1131
1132  // send IMAP expunge command and clear cache
1133  function _expunge($mailbox, $clear_cache=TRUE)
1134    {
1135    $result = iil_C_Expunge($this->conn, $mailbox);
1136
1137    if ($result>=0 && $clear_cache)
1138      {
1139      //$this->clear_message_cache($mailbox.'.msg');
1140      $this->_clear_messagecount($mailbox);
1141      }
1142     
1143    return $result;
1144    }
1145
1146
1147  /* --------------------------------
1148   *        folder managment
1149   * --------------------------------*/
1150
1151
1152  // return an array with all folders available in IMAP server
1153  function list_unsubscribed($root='')
1154    {
1155    static $sa_unsubscribed;
1156   
1157    if (is_array($sa_unsubscribed))
1158      return $sa_unsubscribed;
1159     
1160    // retrieve list of folders from IMAP server
1161    $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1162
1163    // modify names with root dir
1164    foreach ($a_mboxes as $mbox_name)
1165      {
1166      $name = $this->_mod_mailbox($mbox_name, 'out');
1167      if (strlen($name))
1168        $a_folders[] = $name;
1169      }
1170
1171    // filter folders and sort them
1172    $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1173    return $sa_unsubscribed;
1174    }
1175
1176
1177  /**
1178   * Get quota
1179   * added by Nuny
1180   */
1181  function get_quota()
1182    {
1183    if ($this->get_capability('QUOTA'))
1184      {
1185      $result = iil_C_GetQuota($this->conn);
1186      if ($result["total"])
1187        return sprintf("%.2fMB / %.2fMB (%.0f%%)", $result["used"] / 1000.0, $result["total"] / 1000.0, $result["percent"]);       
1188      }
1189
1190    return FALSE;
1191    }
1192
1193
1194  // subscribe to a specific mailbox(es)
1195  function subscribe($mbox_name, $mode='subscribe')
1196    {
1197    if (is_array($mbox_name))
1198      $a_mboxes = $mbox_name;
1199    else if (is_string($mbox_name) && strlen($mbox_name))
1200      $a_mboxes = explode(',', $mbox_name);
1201   
1202    // let this common function do the main work
1203    return $this->_change_subscription($a_mboxes, 'subscribe');
1204    }
1205
1206
1207  // unsubscribe mailboxes
1208  function unsubscribe($mbox_name)
1209    {
1210    if (is_array($mbox_name))
1211      $a_mboxes = $mbox_name;
1212    else if (is_string($mbox_name) && strlen($mbox_name))
1213      $a_mboxes = explode(',', $mbox_name);
1214
1215    // let this common function do the main work
1216    return $this->_change_subscription($a_mboxes, 'unsubscribe');
1217    }
1218
1219
1220  // create a new mailbox on the server and register it in local cache
1221  function create_mailbox($name, $subscribe=FALSE)
1222    {
1223    $result = FALSE;
1224   
1225    // replace backslashes
1226    $name = preg_replace('/[\\\]+/', '-', $name);
1227
1228    $name_enc = UTF7EncodeString($name);
1229
1230    // reduce mailbox name to 100 chars
1231    $name_enc = substr($name_enc, 0, 100);
1232
1233    $abs_name = $this->_mod_mailbox($name_enc);
1234    $a_mailbox_cache = $this->get_cache('mailboxes');
1235       
1236    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
1237      $result = iil_C_CreateFolder($this->conn, $abs_name);
1238
1239    // update mailboxlist cache
1240    if ($result && $subscribe)
1241      $this->subscribe($name_enc);
1242
1243    return $result ? $name : FALSE;
1244    }
1245
1246
1247  // set a new name to an existing mailbox
1248  function rename_mailbox($mbox_name, $new_name, $subscribe=TRUE)
1249    {
1250    $result = FALSE;
1251
1252    // replace backslashes
1253    $name = preg_replace('/[\\\]+/', '-', $new_name);
1254
1255    $name_enc = UTF7EncodeString($new_name);
1256
1257    // reduce mailbox name to 100 chars
1258    $name_enc = substr($name_enc, 0, 100);
1259
1260    $abs_name = $this->_mod_mailbox($name_enc);
1261    $a_mailbox_cache = $this->get_cache('mailboxes');
1262
1263    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
1264      $result = iil_C_RenameFolder($this->conn, $mbox_name, $abs_name);
1265
1266    // update mailboxlist cache
1267    if ($result && $subscribe)
1268      $this->unsubscribe($mbox_name);
1269      $this->subscribe($name_enc);
1270
1271    return $result ? $name : FALSE;
1272    }
1273
1274
1275  // remove mailboxes from server
1276  function delete_mailbox($mbox_name)
1277    {
1278    $deleted = FALSE;
1279
1280    if (is_array($mbox_name))
1281      $a_mboxes = $mbox_name;
1282    else if (is_string($mbox_name) && strlen($mbox_name))
1283      $a_mboxes = explode(',', $mbox_name);
1284
1285    if (is_array($a_mboxes))
1286      foreach ($a_mboxes as $mbox_name)
1287        {
1288        $mailbox = $this->_mod_mailbox($mbox_name);
1289
1290        // unsubscribe mailbox before deleting
1291        iil_C_UnSubscribe($this->conn, $mailbox);
1292       
1293        // send delete command to server
1294        $result = iil_C_DeleteFolder($this->conn, $mailbox);
1295        if ($result>=0)
1296          $deleted = TRUE;
1297        }
1298
1299    // clear mailboxlist cache
1300    if ($deleted)
1301      {
1302      $this->clear_message_cache($mailbox.'.msg');
1303      $this->clear_cache('mailboxes');
1304      }
1305
1306    return $deleted;
1307    }
1308
1309
1310
1311
1312  /* --------------------------------
1313   *   internal caching methods
1314   * --------------------------------*/
1315
1316
1317  function set_caching($set)
1318    {
1319    if ($set && is_object($this->db))
1320      $this->caching_enabled = TRUE;
1321    else
1322      $this->caching_enabled = FALSE;
1323    }
1324
1325
1326  function get_cache($key)
1327    {
1328    // read cache
1329    if (!isset($this->cache[$key]) && $this->caching_enabled)
1330      {
1331      $cache_data = $this->_read_cache_record('IMAP.'.$key);
1332      $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
1333      }
1334   
1335    return $this->cache[$key];
1336    }
1337
1338
1339  function update_cache($key, $data)
1340    {
1341    $this->cache[$key] = $data;
1342    $this->cache_changed = TRUE;
1343    $this->cache_changes[$key] = TRUE;
1344    }
1345
1346
1347  function write_cache()
1348    {
1349    if ($this->caching_enabled && $this->cache_changed)
1350      {
1351      foreach ($this->cache as $key => $data)
1352        {
1353        if ($this->cache_changes[$key])
1354          $this->_write_cache_record('IMAP.'.$key, serialize($data));
1355        }
1356      }   
1357    }
1358
1359
1360  function clear_cache($key=NULL)
1361    {
1362    if ($key===NULL)
1363      {
1364      foreach ($this->cache as $key => $data)
1365        $this->_clear_cache_record('IMAP.'.$key);
1366
1367      $this->cache = array();
1368      $this->cache_changed = FALSE;
1369      $this->cache_changes = array();
1370      }
1371    else
1372      {
1373      $this->_clear_cache_record('IMAP.'.$key);
1374      $this->cache_changes[$key] = FALSE;
1375      unset($this->cache[$key]);
1376      }
1377    }
1378
1379
1380
1381  function _read_cache_record($key)
1382    {
1383    $cache_data = FALSE;
1384   
1385    if ($this->db)
1386      {
1387      // get cached data from DB
1388      $sql_result = $this->db->query(
1389        "SELECT cache_id, data
1390         FROM ".get_table_name('cache')."
1391         WHERE  user_id=?
1392         AND    cache_key=?",
1393        $_SESSION['user_id'],
1394        $key);
1395
1396      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1397        {
1398        $cache_data = $sql_arr['data'];
1399        $this->cache_keys[$key] = $sql_arr['cache_id'];
1400        }
1401      }
1402
1403    return $cache_data;   
1404    }
1405   
1406
1407  function _write_cache_record($key, $data)
1408    {
1409    if (!$this->db)
1410      return FALSE;
1411
1412    // check if we already have a cache entry for this key
1413    if (!isset($this->cache_keys[$key]))
1414      {
1415      $sql_result = $this->db->query(
1416        "SELECT cache_id
1417         FROM ".get_table_name('cache')."
1418         WHERE  user_id=?
1419         AND    cache_key=?",
1420        $_SESSION['user_id'],
1421        $key);
1422                                     
1423      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1424        $this->cache_keys[$key] = $sql_arr['cache_id'];
1425      else
1426        $this->cache_keys[$key] = FALSE;
1427      }
1428
1429    // update existing cache record
1430    if ($this->cache_keys[$key])
1431      {
1432      $this->db->query(
1433        "UPDATE ".get_table_name('cache')."
1434         SET    created=now(),
1435                data=?
1436         WHERE  user_id=?
1437         AND    cache_key=?",
1438        $data,
1439        $_SESSION['user_id'],
1440        $key);
1441      }
1442    // add new cache record
1443    else
1444      {
1445      $this->db->query(
1446        "INSERT INTO ".get_table_name('cache')."
1447         (created, user_id, cache_key, data)
1448         VALUES (now(), ?, ?, ?)",
1449        $_SESSION['user_id'],
1450        $key,
1451        $data);
1452      }
1453    }
1454
1455
1456  function _clear_cache_record($key)
1457    {
1458    $this->db->query(
1459      "DELETE FROM ".get_table_name('cache')."
1460       WHERE  user_id=?
1461       AND    cache_key=?",
1462      $_SESSION['user_id'],
1463      $key);
1464    }
1465
1466
1467
1468  /* --------------------------------
1469   *   message caching methods
1470   * --------------------------------*/
1471   
1472
1473  // checks if the cache is up-to-date
1474  // return: -3 = off, -2 = incomplete, -1 = dirty
1475  function check_cache_status($mailbox, $cache_key)
1476    {
1477    if (!$this->caching_enabled)
1478      return -3;
1479
1480    $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1481    $msg_count = $this->_messagecount($mailbox);
1482    $cache_count = count($cache_index);
1483
1484    // console("Cache check: $msg_count !== ".count($cache_index));
1485
1486    if ($cache_count==$msg_count)
1487      {
1488      // get highest index
1489      $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1490      $cache_uid = array_pop($cache_index);
1491     
1492      // uids of highest message matches -> cache seems OK
1493      if ($cache_uid == $header->uid)
1494        return 1;
1495
1496      // cache is dirty
1497      return -1;
1498      }
1499    // if cache count differs less than 10% report as dirty
1500    else if (abs($msg_count - $cache_count) < $msg_count/10)
1501      return -1;
1502    else
1503      return -2;
1504    }
1505
1506
1507
1508  function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1509    {
1510    $cache_key = "$key:$from:$to:$sort_field:$sort_order";
1511    $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
1512   
1513    if (!in_array($sort_field, $db_header_fields))
1514      $sort_field = 'idx';
1515   
1516    if ($this->caching_enabled && !isset($this->cache[$cache_key]))
1517      {
1518      $this->cache[$cache_key] = array();
1519      $sql_result = $this->db->limitquery(
1520        "SELECT idx, uid, headers
1521         FROM ".get_table_name('messages')."
1522         WHERE  user_id=?
1523         AND    cache_key=?
1524         ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
1525         strtoupper($sort_order),
1526        $from,
1527        $to-$from,
1528        $_SESSION['user_id'],
1529        $key);
1530
1531      while ($sql_arr = $this->db->fetch_assoc($sql_result))
1532        {
1533        $uid = $sql_arr['uid'];
1534        $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
1535        }
1536      }
1537     
1538    return $this->cache[$cache_key];
1539    }
1540
1541
1542  function get_cached_message($key, $uid, $body=FALSE)
1543    {
1544    if (!$this->caching_enabled)
1545      return FALSE;
1546
1547    $internal_key = '__single_msg';
1548    if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
1549      {
1550      $sql_select = "idx, uid, headers";
1551      if ($body)
1552        $sql_select .= ", body";
1553     
1554      $sql_result = $this->db->query(
1555        "SELECT $sql_select
1556         FROM ".get_table_name('messages')."
1557         WHERE  user_id=?
1558         AND    cache_key=?
1559         AND    uid=?",
1560        $_SESSION['user_id'],
1561        $key,
1562        $uid);
1563     
1564      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1565        {
1566        $headers = unserialize($sql_arr['headers']);
1567        if (is_object($headers) && !empty($sql_arr['body']))
1568          $headers->body = $sql_arr['body'];
1569
1570        $this->cache[$internal_key][$uid] = $headers;
1571        }
1572      }
1573
1574    return $this->cache[$internal_key][$uid];
1575    }
1576
1577   
1578  function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
1579    {
1580    static $sa_message_index = array();
1581   
1582    // empty key -> empty array
1583    if (empty($key))
1584      return array();
1585   
1586    if (!empty($sa_message_index[$key]) && !$force)
1587      return $sa_message_index[$key];
1588   
1589    $sa_message_index[$key] = array();
1590    $sql_result = $this->db->query(
1591      "SELECT idx, uid
1592       FROM ".get_table_name('messages')."
1593       WHERE  user_id=?
1594       AND    cache_key=?
1595       ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
1596      $_SESSION['user_id'],
1597      $key);
1598
1599    while ($sql_arr = $this->db->fetch_assoc($sql_result))
1600      $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
1601     
1602    return $sa_message_index[$key];
1603    }
1604
1605
1606  function add_message_cache($key, $index, $headers)
1607    {
1608    if (!$key || !is_object($headers) || empty($headers->uid))
1609      return;
1610
1611    $this->db->query(
1612      "INSERT INTO ".get_table_name('messages')."
1613       (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
1614       VALUES (?, 0, ?, now(), ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
1615      $_SESSION['user_id'],
1616      $key,
1617      $index,
1618      $headers->uid,
1619      (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
1620      (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
1621      (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
1622      (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
1623      (int)$headers->size,
1624      serialize($headers));
1625    }
1626   
1627   
1628  function remove_message_cache($key, $index)
1629    {
1630    $this->db->query(
1631      "DELETE FROM ".get_table_name('messages')."
1632       WHERE  user_id=?
1633       AND    cache_key=?
1634       AND    idx=?",
1635      $_SESSION['user_id'],
1636      $key,
1637      $index);
1638    }
1639
1640
1641  function clear_message_cache($key, $start_index=1)
1642    {
1643    $this->db->query(
1644      "DELETE FROM ".get_table_name('messages')."
1645       WHERE  user_id=?
1646       AND    cache_key=?
1647       AND    idx>=?",
1648      $_SESSION['user_id'],
1649      $key,
1650      $start_index);
1651    }
1652
1653
1654
1655
1656  /* --------------------------------
1657   *   encoding/decoding methods
1658   * --------------------------------*/
1659
1660 
1661  function decode_address_list($input, $max=NULL)
1662    {
1663    $a = $this->_parse_address_list($input);
1664    $out = array();
1665
1666    if (!is_array($a))
1667      return $out;
1668
1669    $c = count($a);
1670    $j = 0;
1671
1672    foreach ($a as $val)
1673      {
1674      $j++;
1675      $address = $val['address'];
1676      $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
1677      $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
1678     
1679      $out[$j] = array('name' => $name,
1680                       'mailto' => $address,
1681                       'string' => $string);
1682             
1683      if ($max && $j==$max)
1684        break;
1685      }
1686   
1687    return $out;
1688    }
1689
1690
1691  function decode_header($input, $remove_quotes=FALSE)
1692    {
1693    $str = $this->decode_mime_string((string)$input);
1694    if ($str{0}=='"' && $remove_quotes)
1695      {
1696      $str = str_replace('"', '', $str);
1697      }
1698   
1699    return $str;
1700    }
1701
1702
1703  /**
1704   * Decode a mime-encoded string to internal charset
1705   *
1706   * @access static
1707   */
1708  function decode_mime_string($input, $recursive=false)
1709    {
1710    $out = '';
1711
1712    $pos = strpos($input, '=?');
1713    if ($pos !== false)
1714      {
1715      $out = substr($input, 0, $pos);
1716 
1717      $end_cs_pos = strpos($input, "?", $pos+2);
1718      $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1719      $end_pos = strpos($input, "?=", $end_en_pos+1);
1720 
1721      $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1722      $rest = substr($input, $end_pos+2);
1723
1724      $out .= rcube_imap::_decode_mime_string_part($encstr);
1725      $out .= rcube_imap::decode_mime_string($rest);
1726
1727      return $out;
1728      }
1729     
1730    // no encoding information, defaults to what is specified in the class header
1731    return rcube_charset_convert($input, 'ISO-8859-1');
1732    }
1733
1734
1735  /**
1736   * Decode a part of a mime-encoded string
1737   *
1738   * @access static
1739   */
1740  function _decode_mime_string_part($str)
1741    {
1742    $a = explode('?', $str);
1743    $count = count($a);
1744
1745    // should be in format "charset?encoding?base64_string"
1746    if ($count >= 3)
1747      {
1748      for ($i=2; $i<$count; $i++)
1749        $rest.=$a[$i];
1750
1751      if (($a[1]=="B")||($a[1]=="b"))
1752        $rest = base64_decode($rest);
1753      else if (($a[1]=="Q")||($a[1]=="q"))
1754        {
1755        $rest = str_replace("_", " ", $rest);
1756        $rest = quoted_printable_decode($rest);
1757        }
1758
1759      return rcube_charset_convert($rest, $a[0]);
1760      }
1761    else
1762      return $str;    // we dont' know what to do with this 
1763    }
1764
1765
1766  function mime_decode($input, $encoding='7bit')
1767    {
1768    switch (strtolower($encoding))
1769      {
1770      case '7bit':
1771        return $input;
1772        break;
1773     
1774      case 'quoted-printable':
1775        return quoted_printable_decode($input);
1776        break;
1777     
1778      case 'base64':
1779        return base64_decode($input);
1780        break;
1781     
1782      default:
1783        return $input;
1784      }
1785    }
1786
1787
1788  function mime_encode($input, $encoding='7bit')
1789    {
1790    switch ($encoding)
1791      {
1792      case 'quoted-printable':
1793        return quoted_printable_encode($input);
1794        break;
1795
1796      case 'base64':
1797        return base64_encode($input);
1798        break;
1799
1800      default:
1801        return $input;
1802      }
1803    }
1804
1805
1806  // convert body chars according to the ctype_parameters
1807  function charset_decode($body, $ctype_param)
1808    {
1809    if (is_array($ctype_param) && !empty($ctype_param['charset']))
1810      return rcube_charset_convert($body, $ctype_param['charset']);
1811
1812    // defaults to what is specified in the class header
1813    return rcube_charset_convert($body,  'ISO-8859-1');
1814    }
1815
1816
1817
1818
1819  /* --------------------------------
1820   *         private methods
1821   * --------------------------------*/
1822
1823
1824  function _mod_mailbox($mbox_name, $mode='in')
1825    {
1826    if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || ($mbox_name == 'INBOX' && $mode == 'in'))
1827      return $mbox_name;
1828
1829    if (!empty($this->root_dir) && $mode=='in')
1830      $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
1831    else if (strlen($this->root_dir) && $mode=='out')
1832      $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
1833
1834    return $mbox_name;
1835    }
1836
1837
1838  // sort mailboxes first by default folders and then in alphabethical order
1839  function _sort_mailbox_list($a_folders)
1840    {
1841    $a_out = $a_defaults = array();
1842
1843    // find default folders and skip folders starting with '.'
1844    foreach($a_folders as $i => $folder)
1845      {
1846      if ($folder{0}=='.')
1847        continue;
1848       
1849      if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1850        $a_defaults[$p] = $folder;
1851      else
1852        $a_out[] = $folder;
1853      }
1854
1855    sort($a_out);
1856    ksort($a_defaults);
1857   
1858    return array_merge($a_defaults, $a_out);
1859    }
1860
1861  function get_id($uid, $mbox_name=NULL)
1862    {
1863      return $this->_uid2id($uid, $mbox_name);
1864    }
1865 
1866  function get_uid($id,$mbox_name=NULL)
1867    {
1868      return $this->_id2uid($id, $mbox_name);
1869    }
1870
1871  function _uid2id($uid, $mbox_name=NULL)
1872    {
1873    if (!$mbox_name)
1874      $mbox_name = $this->mailbox;
1875     
1876    if (!isset($this->uid_id_map[$mbox_name][$uid]))
1877      $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
1878
1879    return $this->uid_id_map[$mbox_name][$uid];
1880    }
1881
1882  function _id2uid($id, $mbox_name=NULL)
1883    {
1884    if (!$mbox_name)
1885      $mbox_name = $this->mailbox;
1886     
1887    return iil_C_ID2UID($this->conn, $mbox_name, $id);
1888    }
1889
1890
1891  // parse string or array of server capabilities and put them in internal array
1892  function _parse_capability($caps)
1893    {
1894    if (!is_array($caps))
1895      $cap_arr = explode(' ', $caps);
1896    else
1897      $cap_arr = $caps;
1898   
1899    foreach ($cap_arr as $cap)
1900      {
1901      if ($cap=='CAPABILITY')
1902        continue;
1903
1904      if (strpos($cap, '=')>0)
1905        {
1906        list($key, $value) = explode('=', $cap);
1907        if (!is_array($this->capabilities[$key]))
1908          $this->capabilities[$key] = array();
1909         
1910        $this->capabilities[$key][] = $value;
1911        }
1912      else
1913        $this->capabilities[$cap] = TRUE;
1914      }
1915    }
1916
1917
1918  // subscribe/unsubscribe a list of mailboxes and update local cache
1919  function _change_subscription($a_mboxes, $mode)
1920    {
1921    $updated = FALSE;
1922   
1923    if (is_array($a_mboxes))
1924      foreach ($a_mboxes as $i => $mbox_name)
1925        {
1926        $mailbox = $this->_mod_mailbox($mbox_name);
1927        $a_mboxes[$i] = $mailbox;
1928
1929        if ($mode=='subscribe')
1930          $result = iil_C_Subscribe($this->conn, $mailbox);
1931        else if ($mode=='unsubscribe')
1932          $result = iil_C_UnSubscribe($this->conn, $mailbox);
1933
1934        if ($result>=0)
1935          $updated = TRUE;
1936        }
1937       
1938    // get cached mailbox list   
1939    if ($updated)
1940      {
1941      $a_mailbox_cache = $this->get_cache('mailboxes');
1942      if (!is_array($a_mailbox_cache))
1943        return $updated;
1944
1945      // modify cached list
1946      if ($mode=='subscribe')
1947        $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1948      else if ($mode=='unsubscribe')
1949        $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1950       
1951      // write mailboxlist to cache
1952      $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1953      }
1954
1955    return $updated;
1956    }
1957
1958
1959  // increde/decrese messagecount for a specific mailbox
1960  function _set_messagecount($mbox_name, $mode, $increment)
1961    {
1962    $a_mailbox_cache = FALSE;
1963    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
1964    $mode = strtoupper($mode);
1965
1966    $a_mailbox_cache = $this->get_cache('messagecount');
1967   
1968    if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1969      return FALSE;
1970   
1971    // add incremental value to messagecount
1972    $a_mailbox_cache[$mailbox][$mode] += $increment;
1973   
1974    // there's something wrong, delete from cache
1975    if ($a_mailbox_cache[$mailbox][$mode] < 0)
1976      unset($a_mailbox_cache[$mailbox][$mode]);
1977
1978    // write back to cache
1979    $this->update_cache('messagecount', $a_mailbox_cache);
1980   
1981    return TRUE;
1982    }
1983
1984
1985  // remove messagecount of a specific mailbox from cache
1986  function _clear_messagecount($mbox_name='')
1987    {
1988    $a_mailbox_cache = FALSE;
1989    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
1990
1991    $a_mailbox_cache = $this->get_cache('messagecount');
1992
1993    if (is_array($a_mailbox_cache[$mailbox]))
1994      {
1995      unset($a_mailbox_cache[$mailbox]);
1996      $this->update_cache('messagecount', $a_mailbox_cache);
1997      }
1998    }
1999
2000
2001  function _parse_address_list($str)
2002    {
2003    $a = $this->_explode_quoted_string(',', $str);
2004    $result = array();
2005
2006    foreach ($a as $key => $val)
2007      {
2008      $val = str_replace("\"<", "\" <", $val);
2009      $sub_a = $this->_explode_quoted_string(' ', $val);
2010     
2011      foreach ($sub_a as $k => $v)
2012        {
2013        if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0))
2014          $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
2015        else
2016          $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
2017        }
2018       
2019      if (empty($result[$key]['name']))
2020        $result[$key]['name'] = $result[$key]['address'];
2021       
2022      $result[$key]['name'] = $this->decode_header($result[$key]['name']);
2023      }
2024   
2025    return $result;
2026    }
2027
2028
2029  function _explode_quoted_string($delimiter, $string)
2030    {
2031    $quotes = explode("\"", $string);
2032    foreach ($quotes as $key => $val)
2033      if (($key % 2) == 1)
2034        $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
2035       
2036    $string = implode("\"", $quotes);
2037
2038    $result = explode($delimiter, $string);
2039    foreach ($result as $key => $val)
2040      $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
2041   
2042    return $result;
2043    }
2044  }
2045
2046
2047
2048
2049
2050function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
2051  {
2052  $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2053  $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2054  $eol = "\r\n";
2055  $escape = "=";
2056  $output = "";
2057
2058  while( list(, $line) = each($lines))
2059    {
2060    //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2061    $linlen = strlen($line);
2062    $newline = "";
2063    for($i = 0; $i < $linlen; $i++)
2064      {
2065      $c = substr( $line, $i, 1 );
2066      $dec = ord( $c );
2067      if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2068        {
2069        $c = "=2E";
2070        }
2071      if ( $dec == 32 )
2072        {
2073        if ( $i == ( $linlen - 1 ) ) // convert space at eol only
2074          {
2075          $c = "=20";
2076          }
2077        else if ( $space_conv )
2078          {
2079          $c = "=20";
2080          }
2081        }
2082      else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
2083        {
2084        $h2 = floor($dec/16);
2085        $h1 = floor($dec%16);
2086        $c = $escape.$hex["$h2"].$hex["$h1"];
2087        }
2088         
2089      if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
2090        {
2091        $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
2092        $newline = "";
2093        // check if newline first character will be point or not
2094        if ( $dec == 46 )
2095          {
2096          $c = "=2E";
2097          }
2098        }
2099      $newline .= $c;
2100      } // end of for
2101    $output .= $newline.$eol;
2102    } // end of while
2103
2104  return trim($output);
2105  }
2106
2107?>
Note: See TracBrowser for help on using the repository browser.