]> git.donarmstrong.com Git - roundcube.git/blob - program/include/main.inc
Imported Upstream version 0.2~stable
[roundcube.git] / program / include / main.inc
1 <?php
2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/main.inc                                              |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
8  | Copyright (C) 2005-2008, RoundCube Dev, - Switzerland                 |
9  | Licensed under the GNU GPL                                            |
10  |                                                                       |
11  | PURPOSE:                                                              |
12  |   Provide basic functions for the webmail package                     |
13  |                                                                       |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17
18  $Id: main.inc 2187 2008-12-24 14:19:27Z thomasb $
19
20 */
21
22 /**
23  * RoundCube Webmail common functions
24  *
25  * @package Core
26  * @author Thomas Bruederli <roundcube@gmail.com>
27  */
28
29 require_once('lib/utf7.inc');
30 require_once('include/rcube_shared.inc');
31
32 // define constannts for input reading
33 define('RCUBE_INPUT_GET', 0x0101);
34 define('RCUBE_INPUT_POST', 0x0102);
35 define('RCUBE_INPUT_GPC', 0x0103);
36
37
38
39 /**
40  * Return correct name for a specific database table
41  *
42  * @param string Table name
43  * @return string Translated table name
44  */
45 function get_table_name($table)
46   {
47   global $CONFIG;
48
49   // return table name if configured
50   $config_key = 'db_table_'.$table;
51
52   if (strlen($CONFIG[$config_key]))
53     return $CONFIG[$config_key];
54
55   return $table;
56   }
57
58
59 /**
60  * Return correct name for a specific database sequence
61  * (used for Postgres only)
62  *
63  * @param string Secuence name
64  * @return string Translated sequence name
65  */
66 function get_sequence_name($sequence)
67   {
68   // return table name if configured
69   $config_key = 'db_sequence_'.$sequence;
70   $opt = rcmail::get_instance()->config->get($config_key);
71
72   if (!empty($opt))
73     return $opt;
74     
75   return $sequence;
76   }
77
78
79 /**
80  * Get localized text in the desired language
81  * It's a global wrapper for rcmail::gettext()
82  *
83  * @param mixed Named parameters array or label name
84  * @return string Localized text
85  * @see rcmail::gettext()
86  */
87 function rcube_label($p)
88 {
89   return rcmail::get_instance()->gettext($p);
90 }
91
92
93 /**
94  * Overwrite action variable
95  *
96  * @param string New action value
97  */
98 function rcmail_overwrite_action($action)
99   {
100   $app = rcmail::get_instance();
101   $app->action = $action;
102   $app->output->set_env('action', $action);
103   }
104
105
106 /**
107  * Compose an URL for a specific action
108  *
109  * @param string  Request action
110  * @param array   More URL parameters
111  * @param string  Request task (omit if the same)
112  * @return The application URL
113  */
114 function rcmail_url($action, $p=array(), $task=null)
115 {
116   $app = rcmail::get_instance();
117   return $app->url((array)$p + array('_action' => $action, 'task' => $task));
118 }
119
120
121 /**
122  * Garbage collector function for temp files.
123  * Remove temp files older than two days
124  */
125 function rcmail_temp_gc()
126   {
127   $tmp = unslashify($CONFIG['temp_dir']);
128   $expire = mktime() - 172800;  // expire in 48 hours
129
130   if ($dir = opendir($tmp))
131     {
132     while (($fname = readdir($dir)) !== false)
133       {
134       if ($fname{0} == '.')
135         continue;
136
137       if (filemtime($tmp.'/'.$fname) < $expire)
138         @unlink($tmp.'/'.$fname);
139       }
140
141     closedir($dir);
142     }
143   }
144
145
146 /**
147  * Garbage collector for cache entries.
148  * Remove all expired message cache records
149  */
150 function rcmail_cache_gc()
151   {
152   $rcmail = rcmail::get_instance();
153   $db = $rcmail->get_dbh();
154   
155   // get target timestamp
156   $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
157   
158   $db->query("DELETE FROM ".get_table_name('messages')."
159              WHERE  created < " . $db->fromunixtime($ts));
160
161   $db->query("DELETE FROM ".get_table_name('cache')."
162               WHERE  created < " . $db->fromunixtime($ts));
163   }
164
165
166 /**
167  * Convert a string from one charset to another.
168  * Uses mbstring and iconv functions if possible
169  *
170  * @param  string Input string
171  * @param  string Suspected charset of the input string
172  * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
173  * @return Converted string
174  */
175 function rcube_charset_convert($str, $from, $to=NULL)
176   {
177   static $mbstring_loaded = null;
178   static $mbstring_list = null;
179   static $convert_warning = false;
180
181   $from = strtoupper($from);
182   $to = $to==NULL ? strtoupper(RCMAIL_CHARSET) : strtoupper($to);
183   $error = false; $conv = null;
184
185   if ($from==$to || $str=='' || empty($from))
186     return $str;
187     
188   $aliases = array(
189     'US-ASCII'         => 'ISO-8859-1',
190     'ANSI_X3.110-1983' => 'ISO-8859-1',
191     'ANSI_X3.4-1968'   => 'ISO-8859-1',
192     'UNKNOWN-8BIT'     => 'ISO-8859-15',
193     'X-UNKNOWN'        => 'ISO-8859-15',
194     'X-USER-DEFINED'   => 'ISO-8859-15',
195     'ISO-8859-8-I'     => 'ISO-8859-8',
196     'KS_C_5601-1987'   => 'EUC-KR',
197   );
198
199   // convert charset using iconv module  
200   if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7')
201     {
202     $aliases['GB2312'] = 'GB18030';
203     $_iconv = iconv(($aliases[$from] ? $aliases[$from] : $from), ($aliases[$to] ? $aliases[$to] : $to) . "//IGNORE", $str);
204     if ($_iconv !== false)
205       {
206         return $_iconv;
207       }
208     }
209
210
211   if (is_null($mbstring_loaded))
212     $mbstring_loaded = extension_loaded('mbstring');
213     
214   // convert charset using mbstring module
215   if ($mbstring_loaded)
216     {
217     $aliases['UTF-7'] = 'UTF7-IMAP';
218     $aliases['WINDOWS-1257'] = 'ISO-8859-13';
219     
220     if (is_null($mbstring_list)) {
221       $mbstring_list = mb_list_encodings();
222       $mbstring_list = array_map('strtoupper', $mbstring_list);
223     }
224     
225     $mb_from = $aliases[$from] ? $aliases[$from] : $from;
226     $mb_to = $aliases[$to] ? $aliases[$to] : $to;
227     
228     // return if encoding found, string matches encoding and convert succeeded
229     if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list))
230       if (mb_check_encoding($str, $mb_from))
231         if ($out = mb_convert_encoding($str, $mb_to, $mb_from))
232           return $out;
233     }
234     
235   
236   if (class_exists('utf8'))
237     $conv = new utf8();
238
239   // convert string to UTF-8
240   if ($from == 'UTF-7')
241     $str = utf7_to_utf8($str);
242   else if (($from == 'ISO-8859-1') && function_exists('utf8_encode'))
243     $str = utf8_encode($str);
244   else if ($from != 'UTF-8' && $conv)
245     {
246     $conv->loadCharset($from);
247     $str = $conv->strToUtf8($str);
248     }
249   else if ($from != 'UTF-8')
250     $error = true;
251
252   // encode string for output
253   if ($to == 'UTF-7')
254     return utf8_to_utf7($str);
255   else if ($to == 'ISO-8859-1' && function_exists('utf8_decode'))
256     return utf8_decode($str);
257   else if ($to != 'UTF-8' && $conv)
258     {
259     $conv->loadCharset($to);
260     return $conv->utf8ToStr($str);
261     }
262   else if ($to != 'UTF-8')
263     $error = true;
264
265   // report error
266   if ($error && !$convert_warning)
267     {
268     raise_error(array(
269       'code' => 500,
270       'type' => 'php',
271       'file' => __FILE__,
272       'message' => "Could not convert string charset. Make sure iconv is installed or lib/utf8.class is available"
273       ), true, false);
274     
275     $convert_warning = true;
276     }
277   
278   // return UTF-8 string
279   return $str;
280   }
281
282
283 /**
284  * Replacing specials characters to a specific encoding type
285  *
286  * @param  string  Input string
287  * @param  string  Encoding type: text|html|xml|js|url
288  * @param  string  Replace mode for tags: show|replace|remove
289  * @param  boolean Convert newlines
290  * @return The quoted string
291  */
292 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
293   {
294   global $OUTPUT;
295   static $html_encode_arr = false;
296   static $js_rep_table = false;
297   static $xml_rep_table = false;
298
299   $charset = $OUTPUT->get_charset();
300   $is_iso_8859_1 = false;
301   if ($charset == 'ISO-8859-1') {
302     $is_iso_8859_1 = true;
303   }
304   if (!$enctype)
305     $enctype = $OUTPUT->type;
306
307   // encode for plaintext
308   if ($enctype=='text')
309     return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
310
311   // encode for HTML output
312   if ($enctype=='html')
313     {
314     if (!$html_encode_arr)
315       {
316       $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);        
317       unset($html_encode_arr['?']);
318       }
319
320     $ltpos = strpos($str, '<');
321     $encode_arr = $html_encode_arr;
322
323     // don't replace quotes and html tags
324     if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
325       {
326       unset($encode_arr['"']);
327       unset($encode_arr['<']);
328       unset($encode_arr['>']);
329       unset($encode_arr['&']);
330       }
331     else if ($mode=='remove')
332       $str = strip_tags($str);
333     
334     // avoid douple quotation of &
335     $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
336       
337     return $newlines ? nl2br($out) : $out;
338     }
339
340   if ($enctype=='url')
341     return rawurlencode($str);
342
343   // if the replace tables for XML and JS are not yet defined
344   if ($js_rep_table===false)
345     {
346     $js_rep_table = $xml_rep_table = array();
347     $xml_rep_table['&'] = '&amp;';
348
349     for ($c=160; $c<256; $c++)  // can be increased to support more charsets
350       {
351       $xml_rep_table[Chr($c)] = "&#$c;";
352       
353       if ($is_iso_8859_1)
354         $js_rep_table[Chr($c)] = sprintf("\\u%04x", $c);
355       }
356
357     $xml_rep_table['"'] = '&quot;';
358     $js_rep_table['"'] = '\\"';
359     $js_rep_table["'"] = "\\'";
360     $js_rep_table["\\"] = "\\\\";
361     }
362
363   // encode for XML
364   if ($enctype=='xml')
365     return strtr($str, $xml_rep_table);
366
367   // encode for javascript use
368   if ($enctype=='js')
369     {
370     if ($charset!='UTF-8')
371       $str = rcube_charset_convert($str, RCMAIL_CHARSET,$charset);
372       
373     return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
374     }
375
376   // no encoding given -> return original string
377   return $str;
378   }
379   
380 /**
381  * Quote a given string.
382  * Shortcut function for rep_specialchars_output
383  *
384  * @return string HTML-quoted string
385  * @see rep_specialchars_output()
386  */
387 function Q($str, $mode='strict', $newlines=TRUE)
388   {
389   return rep_specialchars_output($str, 'html', $mode, $newlines);
390   }
391
392 /**
393  * Quote a given string for javascript output.
394  * Shortcut function for rep_specialchars_output
395  * 
396  * @return string JS-quoted string
397  * @see rep_specialchars_output()
398  */
399 function JQ($str)
400   {
401   return rep_specialchars_output($str, 'js');
402   }
403
404
405 /**
406  * Read input value and convert it for internal use
407  * Performs stripslashes() and charset conversion if necessary
408  * 
409  * @param  string   Field name to read
410  * @param  int      Source to get value from (GPC)
411  * @param  boolean  Allow HTML tags in field value
412  * @param  string   Charset to convert into
413  * @return string   Field value or NULL if not available
414  */
415 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
416   {
417   global $OUTPUT;
418   $value = NULL;
419   
420   if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
421     $value = $_GET[$fname];
422   else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
423     $value = $_POST[$fname];
424   else if ($source==RCUBE_INPUT_GPC)
425     {
426     if (isset($_POST[$fname]))
427       $value = $_POST[$fname];
428     else if (isset($_GET[$fname]))
429       $value = $_GET[$fname];
430     else if (isset($_COOKIE[$fname]))
431       $value = $_COOKIE[$fname];
432     }
433   
434   // strip single quotes if magic_quotes_sybase is enabled
435   if (ini_get('magic_quotes_sybase'))
436     $value = str_replace("''", "'", $value);
437   // strip slashes if magic_quotes enabled
438   else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
439     $value = stripslashes($value);
440
441   // remove HTML tags if not allowed    
442   if (!$allow_html)
443     $value = strip_tags($value);
444   
445   // convert to internal charset
446   if (is_object($OUTPUT))
447     return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
448   else
449     return $value;
450   }
451
452 /**
453  * Remove all non-ascii and non-word chars
454  * except . and -
455  */
456 function asciiwords($str, $css_id = false)
457 {
458   $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
459   return preg_replace("/[^$allowed]/i", '', $str);
460 }
461
462 /**
463  * Remove single and double quotes from given string
464  *
465  * @param string Input value
466  * @return string Dequoted string
467  */
468 function strip_quotes($str)
469 {
470   return preg_replace('/[\'"]/', '', $str);
471 }
472
473
474 /**
475  * Remove new lines characters from given string
476  *
477  * @param string Input value
478  * @return string Stripped string
479  */
480 function strip_newlines($str)
481 {
482   return preg_replace('/[\r\n]/', '', $str);
483 }
484
485
486 /**
487  * Create a HTML table based on the given data
488  *
489  * @param  array  Named table attributes
490  * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
491  * @param  array  List of cols to show
492  * @param  string Name of the identifier col
493  * @return string HTML table code
494  */
495 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
496   {
497   global $RCMAIL;
498   
499   $table = new html_table(/*array('cols' => count($a_show_cols))*/);
500     
501   // add table header
502   foreach ($a_show_cols as $col)
503     $table->add_header($col, Q(rcube_label($col)));
504   
505   $c = 0;
506   if (!is_array($table_data)) 
507   {
508     $db = $RCMAIL->get_dbh();
509     while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
510     {
511       $zebra_class = $c % 2 ? 'even' : 'odd';
512       $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => "contact $zebra_class"));
513
514       // format each col
515       foreach ($a_show_cols as $col)
516         $table->add($col, Q($sql_arr[$col]));
517       
518       $c++;
519     }
520   }
521   else 
522   {
523     foreach ($table_data as $row_data)
524     {
525       $zebra_class = $c % 2 ? 'even' : 'odd';
526       $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => "contact $zebra_class"));
527
528       // format each col
529       foreach ($a_show_cols as $col)
530         $table->add($col, Q($row_data[$col]));
531         
532       $c++;
533     }
534   }
535
536   return $table->show($attrib);
537   }
538
539
540 /**
541  * Create an edit field for inclusion on a form
542  * 
543  * @param string col field name
544  * @param string value field value
545  * @param array attrib HTML element attributes for field
546  * @param string type HTML element type (default 'text')
547  * @return string HTML field definition
548  */
549 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
550   {
551   $fname = '_'.$col;
552   $attrib['name'] = $fname;
553   
554   if ($type=='checkbox')
555     {
556     $attrib['value'] = '1';
557     $input = new html_checkbox($attrib);
558     }
559   else if ($type=='textarea')
560     {
561     $attrib['cols'] = $attrib['size'];
562     $input = new html_textarea($attrib);
563     }
564   else
565     $input = new html_inputfield($attrib);
566
567   // use value from post
568   if (!empty($_POST[$fname]))
569     $value = get_input_value($fname, RCUBE_INPUT_POST,
570             $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
571
572   $out = $input->show($value);
573          
574   return $out;
575   }
576
577
578 /**
579  * Replace all css definitions with #container [def]
580  * and remove css-inlined scripting
581  *
582  * @param string CSS source code
583  * @param string Container ID to use as prefix
584  * @return string Modified CSS source
585  */
586 function rcmail_mod_css_styles($source, $container_id, $base_url = '')
587   {
588   $a_css_values = array();
589   $last_pos = 0;
590   
591   // ignore the whole block if evil styles are detected
592   $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entitiy_decode($source));
593   if (preg_match('/expression|behavior|url\(|import/', $stripped))
594     return '';
595
596   // cut out all contents between { and }
597   while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
598   {
599     $key = sizeof($a_css_values);
600     $a_css_values[$key] = substr($source, $pos+1, $pos2-($pos+1));
601     $source = substr($source, 0, $pos+1) . "<<str_replacement[$key]>>" . substr($source, $pos2, strlen($source)-$pos2);
602     $last_pos = $pos+2;
603   }
604
605   // remove html comments and add #container to each tag selector.
606   // also replace body definition because we also stripped off the <body> tag
607   $styles = preg_replace(
608     array(
609       '/(^\s*<!--)|(-->\s*$)/',
610       '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
611       "/$container_id\s+body/i",
612     ),
613     array(
614       '',
615       "\\1#$container_id \\2",
616       "$container_id div.rcmBody",
617     ),
618     $source);
619   
620   // replace all @import statements to modify the imported CSS sources too
621   $styles = preg_replace_callback(
622     '/@import\s+(url\()?[\'"]?([^\)\'"]+)[\'"]?(\))?/im',
623     create_function('$matches', "return sprintf(\"@import url('./bin/modcss.php?u=%s&c=%s')\", urlencode(make_absolute_url(\$matches[2],'$base_url')), urlencode('$container_id'));"),
624     $styles);
625   
626   // put block contents back in
627   $styles = preg_replace_callback(
628     '/<<str_replacement\[([0-9]+)\]>>/',
629     create_function('$matches', "\$values = ".var_export($a_css_values, true)."; return \$values[\$matches[1]];"),
630     $styles);
631
632   return $styles;
633   }
634
635
636 /**
637  * Decode escaped entities used by known XSS exploits.
638  * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
639  *
640  * @param string CSS content to decode
641  * @return string Decoded string
642  */
643 function rcmail_xss_entitiy_decode($content)
644 {
645   $out = html_entity_decode(html_entity_decode($content));
646   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', create_function('$matches', 'return chr(hexdec($matches[1]));'), $out);
647   $out = preg_replace('#/\*.*\*/#Um', '', $out);
648   return $out;
649 }
650
651
652 /**
653  * Compose a valid attribute string for HTML tags
654  *
655  * @param array Named tag attributes
656  * @param array List of allowed attributes
657  * @return string HTML formatted attribute string
658  */
659 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
660   {
661   // allow the following attributes to be added to the <iframe> tag
662   $attrib_str = '';
663   foreach ($allowed_attribs as $a)
664     if (isset($attrib[$a]))
665       $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
666
667   return $attrib_str;
668   }
669
670
671 /**
672  * Convert a HTML attribute string attributes to an associative array (name => value)
673  *
674  * @param string Input string
675  * @return array Key-value pairs of parsed attributes
676  */
677 function parse_attrib_string($str)
678   {
679   $attrib = array();
680   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
681
682   // convert attributes to an associative array (name => value)
683   if ($regs)
684     foreach ($regs as $attr)
685       {
686       $attrib[strtolower($attr[1])] = $attr[3] . $attr[4];
687       }
688
689   return $attrib;
690   }
691
692
693 /**
694  * Convert the given date to a human readable form
695  * This uses the date formatting properties from config
696  *
697  * @param mixed Date representation (string or timestamp)
698  * @param string Date format to use
699  * @return string Formatted date string
700  */
701 function format_date($date, $format=NULL)
702   {
703   global $CONFIG;
704   
705   $ts = NULL;
706
707   if (is_numeric($date))
708     $ts = $date;
709   else if (!empty($date))
710     {
711     // if date parsing fails, we have a date in non-rfc format.
712     // remove token from the end and try again
713     while ((($ts = @strtotime($date))===false) || ($ts < 0))
714       {
715         $d = explode(' ', $date);
716         array_pop($d);
717         if (!$d) break;
718         $date = implode(' ', $d);
719       }
720     }
721
722   if (empty($ts))
723     return '';
724    
725   // get user's timezone
726   if ($CONFIG['timezone'] === 'auto')
727     $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
728   else {
729     $tz = $CONFIG['timezone'];
730     if ($CONFIG['dst_active'])
731       $tz++;
732   }
733
734   // convert time to user's timezone
735   $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
736   
737   // get current timestamp in user's timezone
738   $now = time();  // local time
739   $now -= (int)date('Z'); // make GMT time
740   $now += ($tz * 3600); // user's time
741   $now_date = getdate($now);
742
743   $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
744   $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
745
746   // define date format depending on current time  
747   if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit && $timestamp < $now)
748     return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
749   else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit && $timestamp < $now)
750     $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
751   else if (!$format)
752     $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
753
754
755   // parse format string manually in order to provide localized weekday and month names
756   // an alternative would be to convert the date() format string to fit with strftime()
757   $out = '';
758   for($i=0; $i<strlen($format); $i++)
759     {
760     if ($format{$i}=='\\')  // skip escape chars
761       continue;
762     
763     // write char "as-is"
764     if ($format{$i}==' ' || $format{$i-1}=='\\')
765       $out .= $format{$i};
766     // weekday (short)
767     else if ($format{$i}=='D')
768       $out .= rcube_label(strtolower(date('D', $timestamp)));
769     // weekday long
770     else if ($format{$i}=='l')
771       $out .= rcube_label(strtolower(date('l', $timestamp)));
772     // month name (short)
773     else if ($format{$i}=='M')
774       $out .= rcube_label(strtolower(date('M', $timestamp)));
775     // month name (long)
776     else if ($format{$i}=='F')
777       $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
778     else if ($format{$i}=='x')
779       $out .= strftime('%x %X', $timestamp);
780     else
781       $out .= date($format{$i}, $timestamp);
782     }
783   
784   return $out;
785   }
786
787
788 /**
789  * Compose a valid representaion of name and e-mail address
790  *
791  * @param string E-mail address
792  * @param string Person name
793  * @return string Formatted string
794  */
795 function format_email_recipient($email, $name='')
796   {
797   if ($name && $name != $email)
798     {
799     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
800     return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, $email);
801     }
802   else
803     return $email;
804   }
805
806
807
808 /****** debugging functions ********/
809
810
811 /**
812  * Print or write debug messages
813  *
814  * @param mixed Debug message or data
815  */
816 function console()
817   {
818   $msg = array();
819   foreach (func_get_args() as $arg)
820     $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
821
822   if (!($GLOBALS['CONFIG']['debug_level'] & 4))
823     write_log('console', join(";\n", $msg));
824   else if ($GLOBALS['OUTPUT']->ajax_call)
825     print "/*\n " . join(";\n", $msg) . " \n*/\n";
826   else
827     {
828     print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
829     print join(";<br/>\n", $msg);
830     print "</pre></div>\n";
831     }
832   }
833
834
835 /**
836  * Append a line to a logfile in the logs directory.
837  * Date will be added automatically to the line.
838  *
839  * @param $name name of log file
840  * @param line Line to append
841  */
842 function write_log($name, $line)
843   {
844   global $CONFIG;
845
846   if (!is_string($line))
847     $line = var_export($line, true);
848   
849   $log_entry = sprintf("[%s]: %s\n",
850                  date("d-M-Y H:i:s O", mktime()),
851                  $line);
852
853   if ($CONFIG['log_driver'] == 'syslog') {
854     if ($name == 'errors')
855       $prio = LOG_ERR;
856     else
857       $prio = LOG_INFO;
858     syslog($prio, $log_entry);
859   } else {
860     // log_driver == 'file' is assumed here
861     if (empty($CONFIG['log_dir']))
862       $CONFIG['log_dir'] = INSTALL_PATH.'logs';
863
864     // try to open specific log file for writing
865     if ($fp = @fopen($CONFIG['log_dir'].'/'.$name, 'a')) {
866       fwrite($fp, $log_entry);
867       fflush($fp);
868       fclose($fp);
869     }
870   }
871 }
872
873
874 /**
875  * @access private
876  */
877 function rcube_timer()
878   {
879   list($usec, $sec) = explode(" ", microtime());
880   return ((float)$usec + (float)$sec);
881   }
882   
883
884 /**
885  * @access private
886  */
887 function rcube_print_time($timer, $label='Timer')
888   {
889   static $print_count = 0;
890   
891   $print_count++;
892   $now = rcube_timer();
893   $diff = $now-$timer;
894   
895   if (empty($label))
896     $label = 'Timer '.$print_count;
897   
898   console(sprintf("%s: %0.4f sec", $label, $diff));
899   }
900
901
902 /**
903  * Return the mailboxlist in HTML
904  *
905  * @param array Named parameters
906  * @return string HTML code for the gui object
907  */
908 function rcmail_mailbox_list($attrib)
909 {
910   global $RCMAIL;
911   static $a_mailboxes;
912   
913   $attrib += array('maxlength' => 100, 'relanames' => false);
914
915   // add some labels to client
916   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
917   
918   $type = $attrib['type'] ? $attrib['type'] : 'ul';
919   unset($attrib['type']);
920
921   if ($type=='ul' && !$attrib['id'])
922     $attrib['id'] = 'rcmboxlist';
923
924   // get mailbox list
925   $mbox_name = $RCMAIL->imap->get_mailbox_name();
926   
927   // build the folders tree
928   if (empty($a_mailboxes)) {
929     // get mailbox list
930     $a_folders = $RCMAIL->imap->list_mailboxes();
931     $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
932     $a_mailboxes = array();
933
934     foreach ($a_folders as $folder)
935       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
936   }
937
938   if ($type=='select') {
939     $select = new html_select($attrib);
940     
941     // add no-selection option
942     if ($attrib['noselection'])
943       $select->add(rcube_label($attrib['noselection']), '0');
944     
945     rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
946     $out = $select->show();
947   }
948   else {
949     $js_mailboxlist = array();
950     $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
951     
952     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
953     $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
954     $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
955   }
956
957   return $out;
958 }
959
960
961 /**
962  * Return the mailboxlist as html_select object
963  *
964  * @param array Named parameters
965  * @return object html_select HTML drop-down object
966  */
967 function rcmail_mailbox_select($p = array())
968 {
969   global $RCMAIL;
970   
971   $p += array('maxlength' => 100, 'relanames' => false);
972   $a_mailboxes = array();
973   
974   foreach ($RCMAIL->imap->list_mailboxes() as $folder)
975     rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
976
977   $select = new html_select($p);
978   
979   if ($p['noselection'])
980     $select->add($p['noselection'], '');
981     
982   rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
983   
984   return $select;
985 }
986
987
988 /**
989  * Create a hierarchical array of the mailbox list
990  * @access private
991  */
992 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
993 {
994   $pos = strpos($folder, $delm);
995   if ($pos !== false) {
996     $subFolders = substr($folder, $pos+1);
997     $currentFolder = substr($folder, 0, $pos);
998     $virtual = !isset($arrFolders[$currentFolder]);
999   }
1000   else {
1001     $subFolders = false;
1002     $currentFolder = $folder;
1003     $virtual = false;
1004   }
1005
1006   $path .= $currentFolder;
1007
1008   if (!isset($arrFolders[$currentFolder])) {
1009     $arrFolders[$currentFolder] = array(
1010       'id' => $path,
1011       'name' => rcube_charset_convert($currentFolder, 'UTF-7'),
1012       'virtual' => $virtual,
1013       'folders' => array());
1014   }
1015   else
1016     $arrFolders[$currentFolder]['virtual'] = $virtual;
1017
1018   if (!empty($subFolders))
1019     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1020 }
1021   
1022
1023 /**
1024  * Return html for a structured list &lt;ul&gt; for the mailbox tree
1025  * @access private
1026  */
1027 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1028 {
1029   global $RCMAIL, $CONFIG;
1030   
1031   $maxlength = intval($attrib['maxlength']);
1032   $realnames = (bool)$attrib['realnames'];
1033   $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1034
1035   $idx = 0;
1036   $out = '';
1037   foreach ($arrFolders as $key => $folder) {
1038     $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1039     $title = null;
1040
1041     if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1042       $foldername = rcube_label($folder_class);
1043     }
1044     else {
1045       $foldername = $folder['name'];
1046
1047       // shorten the folder name to a given length
1048       if ($maxlength && $maxlength > 1) {
1049         $fname = abbreviate_string($foldername, $maxlength);
1050         if ($fname != $foldername)
1051           $title = $foldername;
1052         $foldername = $fname;
1053       }
1054     }
1055
1056     // make folder name safe for ids and class names
1057     $folder_id = asciiwords($folder['id'], true);
1058     $classes = array('mailbox');
1059
1060     // set special class for Sent, Drafts, Trash and Junk
1061     if ($folder['id']==$CONFIG['sent_mbox'])
1062       $classes[] = 'sent';
1063     else if ($folder['id']==$CONFIG['drafts_mbox'])
1064       $classes[] = 'drafts';
1065     else if ($folder['id']==$CONFIG['trash_mbox'])
1066       $classes[] = 'trash';
1067     else if ($folder['id']==$CONFIG['junk_mbox'])
1068       $classes[] = 'junk';
1069     else if ($folder['id']=='INBOX')
1070       $classes[] = 'inbox';
1071     else
1072       $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1073       
1074     $classes[] = $zebra_class;
1075     
1076     if ($folder['id'] == $mbox_name)
1077       $classes[] = 'selected';
1078
1079     $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1080     $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1081     
1082     if ($folder['virtual'])
1083       $classes[] = 'virtual';
1084     else if ($unread)
1085       $classes[] = 'unread';
1086
1087     $js_name = JQ($folder['id']);
1088     $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1089     $link_attrib = $folder['virtual'] ? array() : array(
1090       'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1091       'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1092       'title' => $title,
1093     );
1094
1095     $out .= html::tag('li', array(
1096         'id' => "rcmli".$folder_id,
1097         'class' => join(' ', $classes),
1098         'noclose' => true),
1099       html::a($link_attrib, $html_name) .
1100       (!empty($folder['folders']) ? html::div(array(
1101         'class' => ($collapsed ? 'collapsed' : 'expanded'),
1102         'style' => "position:absolute",
1103         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1104       ), '&nbsp;') : ''));
1105     
1106     $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1107     
1108     if (!empty($folder['folders'])) {
1109       $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1110         rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1111     }
1112
1113     $out .= "</li>\n";
1114     $idx++;
1115   }
1116
1117   return $out;
1118 }
1119
1120
1121 /**
1122  * Return html for a flat list <select> for the mailbox tree
1123  * @access private
1124  */
1125 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1126   {
1127   $idx = 0;
1128   $out = '';
1129   foreach ($arrFolders as $key=>$folder)
1130     {
1131     if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1132       $foldername = rcube_label($folder_class);
1133     else
1134       {
1135       $foldername = $folder['name'];
1136       
1137       // shorten the folder name to a given length
1138       if ($maxlength && $maxlength>1)
1139         $foldername = abbreviate_string($foldername, $maxlength);
1140       }
1141
1142     $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1143
1144     if (!empty($folder['folders']))
1145       $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1146
1147     $idx++;
1148     }
1149
1150   return $out;
1151   }
1152
1153
1154 /**
1155  * Return internal name for the given folder if it matches the configured special folders
1156  * @access private
1157  */
1158 function rcmail_folder_classname($folder_id)
1159 {
1160   global $CONFIG;
1161
1162   // for these mailboxes we have localized labels and css classes
1163   foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1164   {
1165     if ($folder_id == $CONFIG[$smbx.'_mbox'])
1166       return $smbx;
1167   }
1168
1169   if ($folder_id == 'INBOX')
1170     return 'inbox';
1171 }
1172
1173
1174 /**
1175  * Try to localize the given IMAP folder name.
1176  * UTF-7 decode it in case no localized text was found
1177  *
1178  * @param string Folder name
1179  * @return string Localized folder name in UTF-8 encoding
1180  */
1181 function rcmail_localize_foldername($name)
1182 {
1183   if ($folder_class = rcmail_folder_classname($name))
1184     return rcube_label($folder_class);
1185   else
1186     return rcube_charset_convert($name, 'UTF-7');
1187 }
1188
1189
1190 /**
1191  * Output HTML editor scripts
1192  *
1193  * @param string Editor mode
1194  */
1195 function rcube_html_editor($mode='')
1196 {
1197   global $OUTPUT, $CONFIG;
1198
1199   $lang = $tinylang = strtolower(substr($_SESSION['language'], 0, 2));
1200   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$tinylang.'.js'))
1201     $tinylang = 'en';
1202
1203   $OUTPUT->include_script('tiny_mce/tiny_mce.js');
1204   $OUTPUT->include_script('editor.js');
1205   $OUTPUT->add_script('rcmail_editor_init("$__skin_path", "'.JQ($tinylang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1206 }
1207
1208 ?>