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

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

Compose, save and sendmail cleanup

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