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