thomascube
2010-04-01 1d773d71414316e0b9836a15c35576593427ee21
commit | author | age
4e17e6 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/main.inc                                              |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
cbbef3 8  | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 |
30233b 9  | Licensed under the GNU GPL                                            |
4e17e6 10  |                                                                       |
T 11  | PURPOSE:                                                              |
12  |   Provide basic functions for the webmail package                     |
13  |                                                                       |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17
18  $Id$
19
20 */
21
6d969b 22 /**
T 23  * RoundCube Webmail common functions
24  *
25  * @package Core
26  * @author Thomas Bruederli <roundcube@gmail.com>
27  */
28
0af7e8 29 require_once('lib/utf7.inc');
97bd2c 30 require_once('include/rcube_shared.inc');
4e17e6 31
1a7f99 32 // fallback if not PHP modules are available
T 33 @include_once('lib/des.inc');
34 @include_once('lib/utf8.class.php');
4e17e6 35
ea7c46 36 // define constannts for input reading
T 37 define('RCUBE_INPUT_GET', 0x0101);
38 define('RCUBE_INPUT_POST', 0x0102);
39 define('RCUBE_INPUT_GPC', 0x0103);
40
41
4e17e6 42
6d969b 43 /**
T 44  * Return correct name for a specific database table
45  *
46  * @param string Table name
47  * @return string Translated table name
48  */
4e17e6 49 function get_table_name($table)
T 50   {
51   global $CONFIG;
653242 52
4e17e6 53   // return table name if configured
T 54   $config_key = 'db_table_'.$table;
55
56   if (strlen($CONFIG[$config_key]))
57     return $CONFIG[$config_key];
653242 58
4e17e6 59   return $table;
T 60   }
61
62
6d969b 63 /**
T 64  * Return correct name for a specific database sequence
653242 65  * (used for Postgres only)
6d969b 66  *
T 67  * @param string Secuence name
68  * @return string Translated sequence name
69  */
1cded8 70 function get_sequence_name($sequence)
T 71   {
e1ac21 72   // return sequence name if configured
1cded8 73   $config_key = 'db_sequence_'.$sequence;
54dd42 74   $opt = rcmail::get_instance()->config->get($config_key);
1cded8 75
54dd42 76   if (!empty($opt))
3e8483 77     return $opt;
54dd42 78     
ae8f19 79   return $sequence;
1cded8 80   }
0af7e8 81
T 82
6d969b 83 /**
1854c4 84  * Get localized text in the desired language
T 85  * It's a global wrapper for rcmail::gettext()
6d969b 86  *
1854c4 87  * @param mixed Named parameters array or label name
T 88  * @return string Localized text
89  * @see rcmail::gettext()
6d969b 90  */
cc97ea 91 function rcube_label($p, $domain=null)
1854c4 92 {
cc97ea 93   return rcmail::get_instance()->gettext($p, $domain);
1854c4 94 }
4e17e6 95
T 96
6d969b 97 /**
T 98  * Overwrite action variable
99  *
100  * @param string New action value
101  */
10a699 102 function rcmail_overwrite_action($action)
T 103   {
197601 104   $app = rcmail::get_instance();
T 105   $app->action = $action;
106   $app->output->set_env('action', $action);
10a699 107   }
T 108
109
41bece 110 /**
T 111  * Compose an URL for a specific action
112  *
113  * @param string  Request action
114  * @param array   More URL parameters
115  * @param string  Request task (omit if the same)
116  * @return The application URL
117  */
118 function rcmail_url($action, $p=array(), $task=null)
f11541 119 {
197601 120   $app = rcmail::get_instance();
fde466 121   return $app->url((array)$p + array('_action' => $action, 'task' => $task));
f11541 122 }
9fee0e 123
4e17e6 124
6d969b 125 /**
T 126  * Garbage collector function for temp files.
127  * Remove temp files older than two days
128  */
70d4b9 129 function rcmail_temp_gc()
1cded8 130   {
c5ee03 131   $rcmail = rcmail::get_instance();
A 132
133   $tmp = unslashify($rcmail->config->get('temp_dir'));
70d4b9 134   $expire = mktime() - 172800;  // expire in 48 hours
1cded8 135
70d4b9 136   if ($dir = opendir($tmp))
1cded8 137     {
70d4b9 138     while (($fname = readdir($dir)) !== false)
T 139       {
140       if ($fname{0} == '.')
141         continue;
142
143       if (filemtime($tmp.'/'.$fname) < $expire)
144         @unlink($tmp.'/'.$fname);
145       }
146
147     closedir($dir);
148     }
1cded8 149   }
T 150
151
6d969b 152 /**
T 153  * Garbage collector for cache entries.
154  * Remove all expired message cache records
155  */
29c64b 156 function rcmail_cache_gc()
cc9570 157   {
29c64b 158   $rcmail = rcmail::get_instance();
T 159   $db = $rcmail->get_dbh();
cc9570 160   
T 161   // get target timestamp
29c64b 162   $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
cc9570 163   
29c64b 164   $db->query("DELETE FROM ".get_table_name('messages')."
T 165              WHERE  created < " . $db->fromunixtime($ts));
166
167   $db->query("DELETE FROM ".get_table_name('cache')."
168               WHERE  created < " . $db->fromunixtime($ts));
cc9570 169   }
T 170
1cded8 171
2bca6e 172 /**
f52e7a 173  * Catch an error and throw an exception.
A 174  *
175  * @param  int    Level of the error
176  * @param  string Error message
177  */ 
178 function rcube_error_handler($errno, $errstr)
179   {
180   throw new ErrorException($errstr, 0, $errno);
181   }
182
183
184 /**
2bca6e 185  * Convert a string from one charset to another.
T 186  * Uses mbstring and iconv functions if possible
187  *
188  * @param  string Input string
189  * @param  string Suspected charset of the input string
f11541 190  * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
2bca6e 191  * @return Converted string
T 192  */
3f9edb 193 function rcube_charset_convert($str, $from, $to=NULL)
0af7e8 194   {
f52e7a 195   static $iconv_options = null;
77e232 196   static $mbstring_loaded = null;
A 197   static $mbstring_list = null;
198   static $convert_warning = false;
ce72e0 199   static $conv = null;
A 200   
dbe44c 201   $error = false;
f88d41 202
b77c9d 203   $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
dbe44c 204   $from = rcube_parse_charset($from);
65d710 205
c9a2fa 206   if ($from == $to || empty($str) || empty($from))
3f9edb 207     return $str;
ca85b1 208
b8e65c 209   // convert charset using iconv module  
ae8a60 210   if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
f52e7a 211     if ($iconv_options === null) {
895d4e 212       // ignore characters not available in output charset
A 213       $iconv_options = '//IGNORE';
f52e7a 214       if (iconv('', $iconv_options, '') === false) {
A 215         // iconv implementation does not support options
216         $iconv_options = '';
217       }
218     }
b69560 219
f52e7a 220     // throw an exception if iconv reports an illegal character in input
A 221     // it means that input string has been truncated
222     set_error_handler('rcube_error_handler', E_NOTICE);
223     try {
224       $_iconv = iconv($from, $to . $iconv_options, $str);
225     } catch (ErrorException $e) {
226       $_iconv = false;
227     }
228     restore_error_handler();
ae8a60 229     if ($_iconv !== false) {
f52e7a 230       return $_iconv;
0393da 231     }
ae8a60 232   }
5f56a5 233
f52e7a 234   if ($mbstring_loaded === null)
d99b93 235     $mbstring_loaded = extension_loaded('mbstring');
A 236     
197601 237   // convert charset using mbstring module
ae8a60 238   if ($mbstring_loaded) {
b19536 239     $aliases['WINDOWS-1257'] = 'ISO-8859-13';
88f66e 240     
f52e7a 241     if ($mbstring_list === null) {
77e232 242       $mbstring_list = mb_list_encodings();
A 243       $mbstring_list = array_map('strtoupper', $mbstring_list);
244     }
dbe44c 245
77e232 246     $mb_from = $aliases[$from] ? $aliases[$from] : $from;
A 247     $mb_to = $aliases[$to] ? $aliases[$to] : $to;
248     
249     // return if encoding found, string matches encoding and convert succeeded
ae8a60 250     if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
T 251       if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
252         return $out;
83dbb7 253     }
ae8a60 254   }
65d710 255
ce72e0 256   // convert charset using bundled classes/functions
a5897a 257   if ($to == 'UTF-8') {
A 258     if ($from == 'UTF7-IMAP') {
259       if ($_str = utf7_to_utf8($str))
ce72e0 260         return $_str;
a5897a 261     }
A 262     else if ($from == 'UTF-7') {
263       if ($_str = rcube_utf7_to_utf8($str))
ce72e0 264         return $_str;
a5897a 265     }
A 266     else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
ce72e0 267       return utf8_encode($str);
a5897a 268     }
ce72e0 269     else if (class_exists('utf8')) {
A 270       if (!$conv)
271         $conv = new utf8($from);
272       else
273         $conv->loadCharset($from);
274
275       if($_str = $conv->strToUtf8($str))
276         return $_str;
a5897a 277     }
ce72e0 278     $error = true;
ae8a60 279   }
a5897a 280   
3f9edb 281   // encode string for output
a5897a 282   if ($from == 'UTF-8') {
A 283     // @TODO: we need a function for UTF-7 (RFC2152) conversion
284     if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
285       if ($_str = utf8_to_utf7($str))
ce72e0 286         return $_str;
a5897a 287     }
A 288     else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
289       return utf8_decode($str);
290     }
ce72e0 291     else if (class_exists('utf8')) {
A 292       if (!$conv)
293         $conv = new utf8($to);
294       else
295         $conv->loadCharset($from);
296
297       if ($_str = $conv->strToUtf8($str))
298         return $_str;
a5897a 299     }
ce72e0 300     $error = true;
ae8a60 301   }
T 302   
1a7f99 303   // report error
ce72e0 304   if ($error && !$convert_warning) {
1a7f99 305     raise_error(array(
T 306       'code' => 500,
307       'type' => 'php',
308       'file' => __FILE__,
ce72e0 309       'line' => __LINE__,
dbe44c 310       'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
1a7f99 311       ), true, false);
T 312     
313     $convert_warning = true;
ae8a60 314   }
1a7f99 315   
ce72e0 316   // return UTF-8 or original string
3f9edb 317   return $str;
0af7e8 318   }
3f9edb 319
0af7e8 320
2bca6e 321 /**
dbe44c 322  * Parse and validate charset name string (see #1485758).
A 323  * Sometimes charset string is malformed, there are also charset aliases 
324  * but we need strict names for charset conversion (specially utf8 class)
325  *
326  * @param  string  Input charset name
327  * @return The validated charset name
328  */
329 function rcube_parse_charset($charset)
330   {
331   $charset = strtoupper($charset);
332
7d0b34 333   $charset = preg_replace(array(
A 334     '/^[^0-9A-Z]+/',    // e.g. _ISO-8859-JP$SIO
335     '/\$.*$/',        // e.g. _ISO-8859-JP$SIO
1e3271 336     '/UNICODE-1-1-*/',    // RFC1641/1642
7d0b34 337     ), '', $charset);
dbe44c 338
b1fb69 339   # Aliases: some of them from HTML5 spec.
dbe44c 340   $aliases = array(
ca85b1 341     'USASCII'       => 'WINDOWS-1252',
A 342     'ANSIX31101983' => 'WINDOWS-1252',
343     'ANSIX341968'   => 'WINDOWS-1252',
dbe44c 344     'UNKNOWN8BIT'   => 'ISO-8859-15',
5dc7c2 345     'UNKNOWN'       => 'ISO-8859-15',
A 346     'USERDEFINED'   => 'ISO-8859-15',
dbe44c 347     'KSC56011987'   => 'EUC-KR',
b1fb69 348     'GB2312'         => 'GBK',
A 349     'GB231280'        => 'GBK',
dbe44c 350     'UNICODE'        => 'UTF-8',
b1fb69 351     'UTF7IMAP'        => 'UTF7-IMAP',
A 352     'TIS620'        => 'WINDOWS-874',
353     'ISO88599'        => 'WINDOWS-1254',
354     'ISO885911'        => 'WINDOWS-874',
c8729e 355     'MACROMAN'        => 'MACINTOSH',
1e3271 356     '238'           => 'WINDOWS-1250',
A 357     '178'           => 'WINDOWS-1256',
358     '177'           => 'WINDOWS-1255',
359     '204'           => 'WINDOWS-1251',
360     '161'           => 'WINDOWS-1253',
361     '222'           => 'WINDOWS-874',
362     '134'           => 'GBK',
363     '238'           => 'WINDOWS-1250',
364     '128'           => 'SHIFT-JIS'
dbe44c 365   );
A 366
5dc7c2 367   // allow a-z and 0-9 only and remove X- prefix (e.g. X-ROMAN8 => ROMAN8)
1e3271 368   $str = preg_replace(array('/[^A-Z0-9]/', '/^X+/'), '', $charset);
dbe44c 369
A 370   if (isset($aliases[$str]))
371     return $aliases[$str];
372
1e3271 373   if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
dbe44c 374     return 'UTF-' . $m[1] . $m[2];
A 375
ca85b1 376   if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
A 377     $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
378     # some clients sends windows-1252 text as latin1,
379     # it is safe to use windows-1252 for all latin1
380     return $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
381     }
dbe44c 382
1e3271 383   // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
A 384   if (preg_match('/WINDOWS([0-9]+)/', $str, $m)) {
385     return 'WINDOWS-' . $m[1];
386     }
387
dbe44c 388   return $charset;
A 389   }
a5897a 390
A 391
392 /**
393  * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
394  *
395  * @param  string  Input string
396  * @return The converted string
397  */
398 function rcube_utf7_to_utf8($str)
399 {
400   $Index_64 = array(
401     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
402     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
403     0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
404     1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
405     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
406     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
407     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
408     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
409   );
410
411   $u7len = strlen($str);
412   $str = strval($str);
413   $res = '';
414
415   for ($i=0; $u7len > 0; $i++, $u7len--)
416   {
417     $u7 = $str[$i];
418     if ($u7 == '+')
419     {
420       $i++;
421       $u7len--;
422       $ch = '';
423
424       for (; $u7len > 0; $i++, $u7len--)
425       {
426         $u7 = $str[$i];
427
428         if (!$Index_64[ord($u7)])
429           break;
430
431     $ch .= $u7;
432       }
433
434       if ($ch == '') {
435         if ($u7 == '-')
436           $res .= '+';
437         continue;
438       }
439
440       $res .= rcube_utf16_to_utf8(base64_decode($ch));
441     }
442     else
443     {
444       $res .= $u7;
445     }
446   }
447
448   return $res;
449 }
450
451 /**
452  * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
453  *
454  * @param  string  Input string
455  * @return The converted string
456  */
457 function rcube_utf16_to_utf8($str)
458 {
459   $len = strlen($str);
460   $dec = '';
461
462   for ($i = 0; $i < $len; $i += 2) {
463     $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
464     if ($c >= 0x0001 && $c <= 0x007F) {
465       $dec .= chr($c);
466     } else if ($c > 0x07FF) {
467       $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
468       $dec .= chr(0x80 | (($c >>  6) & 0x3F));
469       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
470     } else {
471       $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
472       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
473     }
474   }
475   return $dec;
476 }
dbe44c 477
A 478
479 /**
2bca6e 480  * Replacing specials characters to a specific encoding type
T 481  *
482  * @param  string  Input string
483  * @param  string  Encoding type: text|html|xml|js|url
484  * @param  string  Replace mode for tags: show|replace|remove
485  * @param  boolean Convert newlines
486  * @return The quoted string
487  */
1cded8 488 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
T 489   {
257782 490   static $html_encode_arr = false;
A 491   static $js_rep_table = false;
492   static $xml_rep_table = false;
1cded8 493
T 494   if (!$enctype)
c8a21d 495     $enctype = $OUTPUT->type;
1cded8 496
T 497   // encode for HTML output
498   if ($enctype=='html')
499     {
500     if (!$html_encode_arr)
501       {
0af7e8 502       $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);        
1cded8 503       unset($html_encode_arr['?']);
T 504       }
505
506     $ltpos = strpos($str, '<');
507     $encode_arr = $html_encode_arr;
508
509     // don't replace quotes and html tags
510     if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
511       {
512       unset($encode_arr['"']);
513       unset($encode_arr['<']);
514       unset($encode_arr['>']);
10c92b 515       unset($encode_arr['&']);
1cded8 516       }
T 517     else if ($mode=='remove')
518       $str = strip_tags($str);
674a0f 519     
T 520     // avoid douple quotation of &
fdebae 521     $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
0af7e8 522       
1cded8 523     return $newlines ? nl2br($out) : $out;
T 524     }
525
2bca6e 526   // if the replace tables for XML and JS are not yet defined
257782 527   if ($js_rep_table===false)
1cded8 528     {
f91a49 529     $js_rep_table = $xml_rep_table = array();
88375f 530     $xml_rep_table['&'] = '&amp;';
1cded8 531
T 532     for ($c=160; $c<256; $c++)  // can be increased to support more charsets
bc6ac4 533       $xml_rep_table[chr($c)] = "&#$c;";
1cded8 534
T 535     $xml_rep_table['"'] = '&quot;';
c21d6d 536     $js_rep_table['"'] = '\\"';
T 537     $js_rep_table["'"] = "\\'";
3d54e6 538     $js_rep_table["\\"] = "\\\\";
bc6ac4 539     // Unicode line and paragraph separators (#1486310)
A 540     $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
541     $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
1cded8 542     }
T 543
74eb6c 544   // encode for javascript use
A 545   if ($enctype=='js')
546     return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
547
548   // encode for plaintext
549   if ($enctype=='text')
550     return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
551
552   if ($enctype=='url')
553     return rawurlencode($str);
554
2bca6e 555   // encode for XML
1cded8 556   if ($enctype=='xml')
T 557     return strtr($str, $xml_rep_table);
558
559   // no encoding given -> return original string
560   return $str;
2bca6e 561   }
f11541 562   
2bca6e 563 /**
6d969b 564  * Quote a given string.
T 565  * Shortcut function for rep_specialchars_output
566  *
567  * @return string HTML-quoted string
568  * @see rep_specialchars_output()
2bca6e 569  */
T 570 function Q($str, $mode='strict', $newlines=TRUE)
571   {
572   return rep_specialchars_output($str, 'html', $mode, $newlines);
573   }
574
575 /**
6d969b 576  * Quote a given string for javascript output.
T 577  * Shortcut function for rep_specialchars_output
578  * 
579  * @return string JS-quoted string
580  * @see rep_specialchars_output()
2bca6e 581  */
18e2a3 582 function JQ($str)
2bca6e 583   {
18e2a3 584   return rep_specialchars_output($str, 'js');
10a699 585   }
ea7c46 586
T 587
588 /**
589  * Read input value and convert it for internal use
590  * Performs stripslashes() and charset conversion if necessary
591  * 
592  * @param  string   Field name to read
593  * @param  int      Source to get value from (GPC)
594  * @param  boolean  Allow HTML tags in field value
595  * @param  string   Charset to convert into
596  * @return string   Field value or NULL if not available
597  */
598 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
759696 599 {
ea7c46 600   $value = NULL;
T 601   
602   if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
603     $value = $_GET[$fname];
604   else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
605     $value = $_POST[$fname];
606   else if ($source==RCUBE_INPUT_GPC)
607     {
026d68 608     if (isset($_POST[$fname]))
ea7c46 609       $value = $_POST[$fname];
026d68 610     else if (isset($_GET[$fname]))
T 611       $value = $_GET[$fname];
ea7c46 612     else if (isset($_COOKIE[$fname]))
T 613       $value = $_COOKIE[$fname];
614     }
c5ee03 615
72b140 616   return parse_input_value($value, $allow_html, $charset);
A 617 }
618
619 /**
620  * Parse/validate input value. See get_input_value()
621  * Performs stripslashes() and charset conversion if necessary
622  * 
623  * @param  string   Input value
624  * @param  boolean  Allow HTML tags in field value
625  * @param  string   Charset to convert into
626  * @return string   Parsed value
627  */
628 function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
629 {
630   global $OUTPUT;
631
c5ee03 632   if (empty($value))
A 633     return $value;
72b140 634
A 635   if (is_array($value)) {
636     foreach ($value as $idx => $val)
637       $value[$idx] = parse_input_value($val, $allow_html, $charset);
638     return $value;
639   }
c5ee03 640
6e47c0 641   // strip single quotes if magic_quotes_sybase is enabled
T 642   if (ini_get('magic_quotes_sybase'))
643     $value = str_replace("''", "'", $value);
ea7c46 644   // strip slashes if magic_quotes enabled
6e47c0 645   else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
ea7c46 646     $value = stripslashes($value);
T 647
648   // remove HTML tags if not allowed    
649   if (!$allow_html)
650     $value = strip_tags($value);
651   
652   // convert to internal charset
72b140 653   if (is_object($OUTPUT) && $charset)
026d68 654     return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
T 655   else
656     return $value;
759696 657 }
T 658
659 /**
660  * Convert array of request parameters (prefixed with _)
661  * to a regular array with non-prefixed keys.
662  *
663  * @param  int   Source to get value from (GPC)
664  * @return array Hash array with all request parameters
665  */
666 function request2param($mode = RCUBE_INPUT_GPC)
667 {
668   $out = array();
669   $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
670   foreach ($src as $key => $value) {
671     $fname = $key[0] == '_' ? substr($key, 1) : $key;
672     $out[$fname] = get_input_value($key, $mode);
ea7c46 673   }
759696 674   
T 675   return $out;
676 }
ea7c46 677
d5342a 678 /**
T 679  * Remove all non-ascii and non-word chars
8ca0c7 680  * except ., -, _
d5342a 681  */
8ca0c7 682 function asciiwords($str, $css_id = false, $replace_with = '')
d5342a 683 {
6d6e06 684   $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
8ca0c7 685   return preg_replace("/[^$allowed]/i", $replace_with, $str);
d5342a 686 }
6d969b 687
e34ae1 688 /**
T 689  * Remove single and double quotes from given string
6d969b 690  *
T 691  * @param string Input value
692  * @return string Dequoted string
e34ae1 693  */
T 694 function strip_quotes($str)
695 {
696   return preg_replace('/[\'"]/', '', $str);
697 }
10a699 698
6d969b 699
3cf664 700 /**
T 701  * Remove new lines characters from given string
6d969b 702  *
T 703  * @param string Input value
704  * @return string Stripped string
3cf664 705  */
T 706 function strip_newlines($str)
707 {
708   return preg_replace('/[\r\n]/', '', $str);
f11541 709 }
4e17e6 710
T 711
6d969b 712 /**
T 713  * Create a HTML table based on the given data
714  *
715  * @param  array  Named table attributes
716  * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
717  * @param  array  List of cols to show
718  * @param  string Name of the identifier col
719  * @return string HTML table code
720  */
d1d2c4 721 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
4e17e6 722   {
83a763 723   global $RCMAIL;
4e17e6 724   
83a763 725   $table = new html_table(/*array('cols' => count($a_show_cols))*/);
4e17e6 726     
83a763 727   // add table header
4e17e6 728   foreach ($a_show_cols as $col)
83a763 729     $table->add_header($col, Q(rcube_label($col)));
4e17e6 730   
T 731   $c = 0;
d1d2c4 732   if (!is_array($table_data)) 
83a763 733   {
T 734     $db = $RCMAIL->get_dbh();
735     while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
4e17e6 736     {
83a763 737       $zebra_class = $c % 2 ? 'even' : 'odd';
1fcad1 738       $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
d1d2c4 739
S 740       // format each col
741       foreach ($a_show_cols as $col)
83a763 742         $table->add($col, Q($sql_arr[$col]));
T 743       
d1d2c4 744       $c++;
S 745     }
83a763 746   }
d1d2c4 747   else 
83a763 748   {
d1d2c4 749     foreach ($table_data as $row_data)
83a763 750     {
T 751       $zebra_class = $c % 2 ? 'even' : 'odd';
b69560 752       if (!empty($row_data['class']))
A 753         $zebra_class .= ' '.$row_data['class'];
754
1fcad1 755       $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
d1d2c4 756
S 757       // format each col
758       foreach ($a_show_cols as $col)
83a763 759         $table->add($col, Q($row_data[$col]));
T 760         
d1d2c4 761       $c++;
4e17e6 762     }
83a763 763   }
4e17e6 764
83a763 765   return $table->show($attrib);
4e17e6 766   }
T 767
768
a0109c 769 /**
S 770  * Create an edit field for inclusion on a form
771  * 
772  * @param string col field name
773  * @param string value field value
774  * @param array attrib HTML element attributes for field
775  * @param string type HTML element type (default 'text')
776  * @return string HTML field definition
777  */
4e17e6 778 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
T 779   {
780   $fname = '_'.$col;
781   $attrib['name'] = $fname;
782   
783   if ($type=='checkbox')
784     {
785     $attrib['value'] = '1';
47124c 786     $input = new html_checkbox($attrib);
4e17e6 787     }
T 788   else if ($type=='textarea')
789     {
790     $attrib['cols'] = $attrib['size'];
47124c 791     $input = new html_textarea($attrib);
4e17e6 792     }
T 793   else
47124c 794     $input = new html_inputfield($attrib);
4e17e6 795
T 796   // use value from post
597170 797   if (!empty($_POST[$fname]))
407dcf 798     $value = get_input_value($fname, RCUBE_INPUT_POST,
A 799         $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
4e17e6 800
T 801   $out = $input->show($value);
802          
803   return $out;
f11541 804   }
T 805
806
6d969b 807 /**
97bd2c 808  * Replace all css definitions with #container [def]
a3e5b4 809  * and remove css-inlined scripting
97bd2c 810  *
T 811  * @param string CSS source code
812  * @param string Container ID to use as prefix
813  * @return string Modified CSS source
814  */
aa055c 815 function rcmail_mod_css_styles($source, $container_id)
97bd2c 816   {
T 817   $last_pos = 0;
aa055c 818   $replacements = new rcube_string_replacer;
a3e5b4 819   
T 820   // ignore the whole block if evil styles are detected
c5ee03 821   $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
1c499a 822   if (preg_match('/expression|behavior|url\(|import/', $stripped))
aa055c 823     return '/* evil! */';
97bd2c 824
T 825   // cut out all contents between { and }
826   while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
827   {
aa055c 828     $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
T 829     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
97bd2c 830     $last_pos = $pos+2;
T 831   }
aa055c 832   
a3e5b4 833   // remove html comments and add #container to each tag selector.
97bd2c 834   // also replace body definition because we also stripped off the <body> tag
T 835   $styles = preg_replace(
836     array(
837       '/(^\s*<!--)|(-->\s*$)/',
838       '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
1608f4 839       "/$container_id\s+body/i",
97bd2c 840     ),
T 841     array(
842       '',
843       "\\1#$container_id \\2",
1608f4 844       "$container_id div.rcmBody",
97bd2c 845     ),
T 846     $source);
1608f4 847   
T 848   // put block contents back in
aa055c 849   $styles = $replacements->resolve($styles);
97bd2c 850
T 851   return $styles;
852   }
fba1f5 853
97bd2c 854
T 855 /**
1c499a 856  * Decode escaped entities used by known XSS exploits.
T 857  * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
858  *
859  * @param string CSS content to decode
860  * @return string Decoded string
861  */
c5ee03 862 function rcmail_xss_entity_decode($content)
1c499a 863 {
T 864   $out = html_entity_decode(html_entity_decode($content));
c5ee03 865   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
85a913 866   $out = preg_replace('#/\*.*\*/#Um', '', $out);
1c499a 867   return $out;
T 868 }
869
aa055c 870
T 871 /**
c5ee03 872  * preg_replace_callback callback for rcmail_xss_entity_decode_callback
aa055c 873  *
T 874  * @param array matches result from preg_replace_callback
875  * @return string decoded entity
876  */ 
c5ee03 877 function rcmail_xss_entity_decode_callback($matches)
aa055c 878
T 879   return chr(hexdec($matches[1]));
880 }
1c499a 881
T 882 /**
6d969b 883  * Compose a valid attribute string for HTML tags
T 884  *
885  * @param array Named tag attributes
886  * @param array List of allowed attributes
887  * @return string HTML formatted attribute string
888  */
4e17e6 889 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
T 890   {
891   // allow the following attributes to be added to the <iframe> tag
892   $attrib_str = '';
893   foreach ($allowed_attribs as $a)
894     if (isset($attrib[$a]))
fe79b1 895       $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
4e17e6 896
T 897   return $attrib_str;
898   }
899
900
6d969b 901 /**
T 902  * Convert a HTML attribute string attributes to an associative array (name => value)
903  *
904  * @param string Input string
905  * @return array Key-value pairs of parsed attributes
906  */
fe79b1 907 function parse_attrib_string($str)
T 908   {
909   $attrib = array();
d59aaa 910   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
fe79b1 911
T 912   // convert attributes to an associative array (name => value)
cc97ea 913   if ($regs) {
T 914     foreach ($regs as $attr) {
915       $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
916     }
917   }
fe79b1 918
T 919   return $attrib;
920   }
921
4e17e6 922
6d969b 923 /**
T 924  * Convert the given date to a human readable form
925  * This uses the date formatting properties from config
926  *
927  * @param mixed Date representation (string or timestamp)
928  * @param string Date format to use
929  * @return string Formatted date string
930  */
4e17e6 931 function format_date($date, $format=NULL)
T 932   {
197601 933   global $CONFIG;
4e17e6 934   
4647e1 935   $ts = NULL;
ea090c 936
4e17e6 937   if (is_numeric($date))
T 938     $ts = $date;
b076a4 939   else if (!empty($date))
ea090c 940     {
33875d 941     // support non-standard "GMTXXXX" literal
A 942     $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
c9ca6a 943     // if date parsing fails, we have a date in non-rfc format.
S 944     // remove token from the end and try again
945     while ((($ts = @strtotime($date))===false) || ($ts < 0))
ea090c 946       {
A 947         $d = explode(' ', $date);
197601 948         array_pop($d);
T 949         if (!$d) break;
950         $date = implode(' ', $d);
ea090c 951       }
A 952     }
953
4647e1 954   if (empty($ts))
b076a4 955     return '';
4647e1 956    
T 957   // get user's timezone
62784a 958   if ($CONFIG['timezone'] === 'auto')
T 959     $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
c8ae24 960   else {
T 961     $tz = $CONFIG['timezone'];
962     if ($CONFIG['dst_active'])
963       $tz++;
964   }
4e17e6 965
T 966   // convert time to user's timezone
4647e1 967   $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
4e17e6 968   
T 969   // get current timestamp in user's timezone
970   $now = time();  // local time
971   $now -= (int)date('Z'); // make GMT time
4647e1 972   $now += ($tz * 3600); // user's time
c45eb5 973   $now_date = getdate($now);
4e17e6 974
749b07 975   $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
T 976   $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
4e17e6 977
539df6 978   // define date format depending on current time
A 979   if (!$format) {
980     if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now)
981       return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
982     else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
983       $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
984     else
985       $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
986     }
4e17e6 987
b6b593 988   // strftime() format
A 989   if (preg_match('/%[a-z]+/i', $format))
990     return strftime($format, $timestamp);
4e17e6 991
T 992   // parse format string manually in order to provide localized weekday and month names
993   // an alternative would be to convert the date() format string to fit with strftime()
994   $out = '';
995   for($i=0; $i<strlen($format); $i++)
996     {
997     if ($format{$i}=='\\')  // skip escape chars
998       continue;
999     
1000     // write char "as-is"
1001     if ($format{$i}==' ' || $format{$i-1}=='\\')
1002       $out .= $format{$i};
1003     // weekday (short)
1004     else if ($format{$i}=='D')
1005       $out .= rcube_label(strtolower(date('D', $timestamp)));
1006     // weekday long
1007     else if ($format{$i}=='l')
1008       $out .= rcube_label(strtolower(date('l', $timestamp)));
1009     // month name (short)
1010     else if ($format{$i}=='M')
1011       $out .= rcube_label(strtolower(date('M', $timestamp)));
1012     // month name (long)
1013     else if ($format{$i}=='F')
7479cc 1014       $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
5b1de5 1015     else if ($format{$i}=='x')
A 1016       $out .= strftime('%x %X', $timestamp);
4e17e6 1017     else
T 1018       $out .= date($format{$i}, $timestamp);
1019     }
1020   
1021   return $out;
1022   }
1023
1024
6d969b 1025 /**
a9bfe2 1026  * Compose a valid representation of name and e-mail address
6d969b 1027  *
T 1028  * @param string E-mail address
1029  * @param string Person name
1030  * @return string Formatted string
1031  */
f11541 1032 function format_email_recipient($email, $name='')
T 1033   {
1034   if ($name && $name != $email)
0c6f4b 1035     {
T 1036     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
a9bfe2 1037     return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
0c6f4b 1038     }
f11541 1039   else
a9bfe2 1040     return trim($email);
f11541 1041   }
T 1042
1043
1044
c39957 1045 /****** debugging functions ********/
T 1046
1047
1048 /**
1049  * Print or write debug messages
1050  *
1051  * @param mixed Debug message or data
1052  */
ed132e 1053 function console()
c39957 1054   {
cc97ea 1055   $args = func_get_args();
76db10 1056
759696 1057   if (class_exists('rcmail', false)) {
T 1058     $rcmail = rcmail::get_instance();
1059     if (is_object($rcmail->plugins))
1060       $rcmail->plugins->exec_hook('console', $args);
1061   }
cc97ea 1062
ed132e 1063   $msg = array();
cc97ea 1064   foreach ($args as $arg)
76db10 1065     $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
c39957 1066
T 1067   if (!($GLOBALS['CONFIG']['debug_level'] & 4))
ed132e 1068     write_log('console', join(";\n", $msg));
197601 1069   else if ($GLOBALS['OUTPUT']->ajax_call)
ed132e 1070     print "/*\n " . join(";\n", $msg) . " \n*/\n";
c39957 1071   else
T 1072     {
1073     print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
ed132e 1074     print join(";<br/>\n", $msg);
c39957 1075     print "</pre></div>\n";
T 1076     }
1077   }
1078
1079
1080 /**
1081  * Append a line to a logfile in the logs directory.
1082  * Date will be added automatically to the line.
1083  *
653242 1084  * @param $name name of log file
S 1085  * @param line Line to append
c39957 1086  */
T 1087 function write_log($name, $line)
1088   {
75fd64 1089   global $CONFIG, $RCMAIL;
e170b4 1090
T 1091   if (!is_string($line))
1092     $line = var_export($line, true);
0ad27c 1093  
A 1094   if (empty($CONFIG['log_date_format']))
1095     $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
75fd64 1096   
a0c4cb 1097   $date = date($CONFIG['log_date_format']);
T 1098   
75fd64 1099   // trigger logging hook
T 1100   if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
a0c4cb 1101     $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
75fd64 1102     $name = $log['name'];
T 1103     $line = $log['line'];
a0c4cb 1104     $date = $log['date'];
75fd64 1105     if ($log['abort'])
20e251 1106       return true;
75fd64 1107   }
0ad27c 1108  
a0c4cb 1109   $log_entry = sprintf("[%s]: %s\n", $date, $line);
c9ca6a 1110
b77d0d 1111   if ($CONFIG['log_driver'] == 'syslog') {
75fd64 1112     $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
b77d0d 1113     syslog($prio, $log_entry);
186938 1114     return true;
75fd64 1115   }
T 1116   else {
b77d0d 1117     // log_driver == 'file' is assumed here
A 1118     if (empty($CONFIG['log_dir']))
1119       $CONFIG['log_dir'] = INSTALL_PATH.'logs';
c9ca6a 1120
b77d0d 1121     // try to open specific log file for writing
cb8961 1122     $logfile = $CONFIG['log_dir'].'/'.$name;
T 1123     if ($fp = @fopen($logfile, 'a')) {
b77d0d 1124       fwrite($fp, $log_entry);
c9ca6a 1125       fflush($fp);
b77d0d 1126       fclose($fp);
186938 1127       return true;
c39957 1128     }
cb8961 1129     else
T 1130       trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
c39957 1131   }
186938 1132   return false;
b77d0d 1133 }
c39957 1134
cc9570 1135
6d969b 1136 /**
T 1137  * @access private
1138  */
15a9d1 1139 function rcube_timer()
533e86 1140 {
T 1141   return microtime(true);
1142 }
15a9d1 1143   
T 1144
6d969b 1145 /**
T 1146  * @access private
1147  */
8bc018 1148 function rcube_print_time($timer, $label='Timer', $dest='console')
533e86 1149 {
15a9d1 1150   static $print_count = 0;
T 1151   
1152   $print_count++;
1153   $now = rcube_timer();
1154   $diff = $now-$timer;
1155   
1156   if (empty($label))
1157     $label = 'Timer '.$print_count;
1158   
8bc018 1159   write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
533e86 1160 }
15a9d1 1161
93be5b 1162
6d969b 1163 /**
T 1164  * Return the mailboxlist in HTML
1165  *
1166  * @param array Named parameters
1167  * @return string HTML code for the gui object
1168  */
93be5b 1169 function rcmail_mailbox_list($attrib)
62e542 1170 {
25f80d 1171   global $RCMAIL;
93be5b 1172   static $a_mailboxes;
64f20d 1173   
b822b6 1174   $attrib += array('maxlength' => 100, 'realnames' => false);
93be5b 1175
S 1176   // add some labels to client
112c91 1177   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
93be5b 1178   
S 1179   $type = $attrib['type'] ? $attrib['type'] : 'ul';
6d6e06 1180   unset($attrib['type']);
T 1181
93be5b 1182   if ($type=='ul' && !$attrib['id'])
S 1183     $attrib['id'] = 'rcmboxlist';
1184
1185   // get mailbox list
25f80d 1186   $mbox_name = $RCMAIL->imap->get_mailbox_name();
93be5b 1187   
S 1188   // build the folders tree
62e542 1189   if (empty($a_mailboxes)) {
93be5b 1190     // get mailbox list
25f80d 1191     $a_folders = $RCMAIL->imap->list_mailboxes();
T 1192     $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
93be5b 1193     $a_mailboxes = array();
S 1194
1195     foreach ($a_folders as $folder)
1196       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
62e542 1197   }
f62d5f 1198   
T 1199   // allow plugins to alter the folder tree or to localize folder names
4fa127 1200   $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
93be5b 1201
6d6e06 1202   if ($type=='select') {
T 1203     $select = new html_select($attrib);
1204     
1205     // add no-selection option
1206     if ($attrib['noselection'])
1207       $select->add(rcube_label($attrib['noselection']), '0');
1208     
f62d5f 1209     rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
62e542 1210     $out = $select->show();
6d6e06 1211   }
T 1212   else {
f89f03 1213     $js_mailboxlist = array();
f62d5f 1214     $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
f89f03 1215     
25f80d 1216     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
f89f03 1217     $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
25f80d 1218     $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
T 1219   }
93be5b 1220
6d6e06 1221   return $out;
62e542 1222 }
93be5b 1223
S 1224
cb3538 1225 /**
T 1226  * Return the mailboxlist as html_select object
1227  *
1228  * @param array Named parameters
1229  * @return object html_select HTML drop-down object
1230  */
1231 function rcmail_mailbox_select($p = array())
1232 {
1233   global $RCMAIL;
1234   
93a88c 1235   $p += array('maxlength' => 100, 'realnames' => false);
cb3538 1236   $a_mailboxes = array();
T 1237   
1238   foreach ($RCMAIL->imap->list_mailboxes() as $folder)
93a88c 1239     if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
A 1240       rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
cb3538 1241
T 1242   $select = new html_select($p);
1243   
1244   if ($p['noselection'])
1245     $select->add($p['noselection'], '');
1246     
64f20d 1247   rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
cb3538 1248   
T 1249   return $select;
1250 }
93be5b 1251
S 1252
6d969b 1253 /**
T 1254  * Create a hierarchical array of the mailbox list
1255  * @access private
1256  */
93be5b 1257 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
f89f03 1258 {
93be5b 1259   $pos = strpos($folder, $delm);
f89f03 1260   if ($pos !== false) {
93be5b 1261     $subFolders = substr($folder, $pos+1);
S 1262     $currentFolder = substr($folder, 0, $pos);
f89f03 1263     $virtual = !isset($arrFolders[$currentFolder]);
T 1264   }
1265   else {
93be5b 1266     $subFolders = false;
S 1267     $currentFolder = $folder;
f89f03 1268     $virtual = false;
T 1269   }
93be5b 1270
S 1271   $path .= $currentFolder;
1272
f89f03 1273   if (!isset($arrFolders[$currentFolder])) {
6d6e06 1274     $arrFolders[$currentFolder] = array(
T 1275       'id' => $path,
a5897a 1276       'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
f89f03 1277       'virtual' => $virtual,
6d6e06 1278       'folders' => array());
f89f03 1279   }
T 1280   else
1281     $arrFolders[$currentFolder]['virtual'] = $virtual;
93be5b 1282
S 1283   if (!empty($subFolders))
1284     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
f89f03 1285 }
93be5b 1286   
S 1287
6d969b 1288 /**
T 1289  * Return html for a structured list &lt;ul&gt; for the mailbox tree
1290  * @access private
1291  */
f89f03 1292 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
T 1293 {
25f80d 1294   global $RCMAIL, $CONFIG;
f89f03 1295   
T 1296   $maxlength = intval($attrib['maxlength']);
1297   $realnames = (bool)$attrib['realnames'];
1298   $msgcounts = $RCMAIL->imap->get_cache('messagecount');
93be5b 1299
S 1300   $idx = 0;
1301   $out = '';
f89f03 1302   foreach ($arrFolders as $key => $folder) {
cb3538 1303     $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
6d6e06 1304     $title = null;
93be5b 1305
f89f03 1306     if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
cb3bad 1307       $foldername = rcube_label($folder_class);
f89f03 1308     }
T 1309     else {
93be5b 1310       $foldername = $folder['name'];
S 1311
1312       // shorten the folder name to a given length
f89f03 1313       if ($maxlength && $maxlength > 1) {
6f2f2d 1314         $fname = abbreviate_string($foldername, $maxlength);
93be5b 1315         if ($fname != $foldername)
6d6e06 1316           $title = $foldername;
93be5b 1317         $foldername = $fname;
S 1318       }
f89f03 1319     }
93be5b 1320
S 1321     // make folder name safe for ids and class names
8ca0c7 1322     $folder_id = asciiwords($folder['id'], true, '_');
6d6e06 1323     $classes = array('mailbox');
93be5b 1324
S 1325     // set special class for Sent, Drafts, Trash and Junk
1326     if ($folder['id']==$CONFIG['sent_mbox'])
6d6e06 1327       $classes[] = 'sent';
93be5b 1328     else if ($folder['id']==$CONFIG['drafts_mbox'])
6d6e06 1329       $classes[] = 'drafts';
93be5b 1330     else if ($folder['id']==$CONFIG['trash_mbox'])
6d6e06 1331       $classes[] = 'trash';
93be5b 1332     else if ($folder['id']==$CONFIG['junk_mbox'])
6d6e06 1333       $classes[] = 'junk';
d6497f 1334     else if ($folder['id']=='INBOX')
A 1335       $classes[] = 'inbox';
6d6e06 1336     else
d6497f 1337       $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
6d6e06 1338       
T 1339     $classes[] = $zebra_class;
1340     
1341     if ($folder['id'] == $mbox_name)
1342       $classes[] = 'selected';
93be5b 1343
f5aa16 1344     $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
f89f03 1345     $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
T 1346     
1347     if ($folder['virtual'])
1348       $classes[] = 'virtual';
1349     else if ($unread)
1350       $classes[] = 'unread';
f5aa16 1351
6d6e06 1352     $js_name = JQ($folder['id']);
f89f03 1353     $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
T 1354     $link_attrib = $folder['virtual'] ? array() : array(
1355       'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1356       'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1357       'title' => $title,
1358     );
1359
6d6e06 1360     $out .= html::tag('li', array(
T 1361         'id' => "rcmli".$folder_id,
1362         'class' => join(' ', $classes),
1363         'noclose' => true),
f89f03 1364       html::a($link_attrib, $html_name) .
e1eb70 1365       (!empty($folder['folders']) ? html::div(array(
T 1366         'class' => ($collapsed ? 'collapsed' : 'expanded'),
1367         'style' => "position:absolute",
1368         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1369       ), '&nbsp;') : ''));
6d6e06 1370     
f89f03 1371     $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
T 1372     
1373     if (!empty($folder['folders'])) {
1374       $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1375         rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1376     }
93be5b 1377
S 1378     $out .= "</li>\n";
1379     $idx++;
f89f03 1380   }
93be5b 1381
S 1382   return $out;
f89f03 1383 }
93be5b 1384
S 1385
6d969b 1386 /**
T 1387  * Return html for a flat list <select> for the mailbox tree
1388  * @access private
1389  */
64f20d 1390 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
93be5b 1391   {
S 1392   $idx = 0;
1393   $out = '';
1394   foreach ($arrFolders as $key=>$folder)
1395     {
64f20d 1396     if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
cb3bad 1397       $foldername = rcube_label($folder_class);
93be5b 1398     else
S 1399       {
1400       $foldername = $folder['name'];
1401       
1402       // shorten the folder name to a given length
1403       if ($maxlength && $maxlength>1)
6f2f2d 1404         $foldername = abbreviate_string($foldername, $maxlength);
93be5b 1405       }
S 1406
6d6e06 1407     $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
93be5b 1408
S 1409     if (!empty($folder['folders']))
64f20d 1410       $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
93be5b 1411
S 1412     $idx++;
1413     }
1414
1415   return $out;
1416   }
1417
cb3bad 1418
T 1419 /**
1420  * Return internal name for the given folder if it matches the configured special folders
1421  * @access private
1422  */
1423 function rcmail_folder_classname($folder_id)
1424 {
1425   global $CONFIG;
1426
1427   // for these mailboxes we have localized labels and css classes
64c9b5 1428   foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
cb3bad 1429   {
f94629 1430     if ($folder_id == $CONFIG[$smbx.'_mbox'])
64c9b5 1431       return $smbx;
cb3bad 1432   }
64c9b5 1433
A 1434   if ($folder_id == 'INBOX')
1435     return 'inbox';
cb3bad 1436 }
T 1437
1438
fed22f 1439 /**
T 1440  * Try to localize the given IMAP folder name.
1441  * UTF-7 decode it in case no localized text was found
1442  *
1443  * @param string Folder name
1444  * @return string Localized folder name in UTF-8 encoding
1445  */
1446 function rcmail_localize_foldername($name)
1447 {
1448   if ($folder_class = rcmail_folder_classname($name))
1449     return rcube_label($folder_class);
1450   else
a5897a 1451     return rcube_charset_convert($name, 'UTF7-IMAP');
fed22f 1452 }
T 1453
1454
b8ae50 1455 /**
A 1456  * Output HTML editor scripts
1457  *
be5d4a 1458  * @param string Editor mode
b8ae50 1459  */
A 1460 function rcube_html_editor($mode='')
1461 {
9ab7bc 1462   global $RCMAIL, $CONFIG;
b8ae50 1463
dc0040 1464   $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
9ab7bc 1465
A 1466   if ($hook['abort'])
1467     return;  
1468
dc0040 1469   $lang = strtolower(substr($_SESSION['language'], 0, 2));
A 1470   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1471     $lang = 'en';
1472
9ab7bc 1473   $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
A 1474   $RCMAIL->output->include_script('editor.js');
1475   $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
dc0040 1476     "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
b8ae50 1477 }
aa055c 1478
T 1479
1480 /**
5818e4 1481  * Check if working in SSL mode
A 1482  *
1483  * @param integer HTTPS port number
1484  * @param boolean Enables 'use_https' option checking
1485  */
1486 function rcube_https_check($port=null, $use_https=true)
1487 {
1488   global $RCMAIL;
1489   
6c5aa6 1490   if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
5818e4 1491     return true;
A 1492   if ($port && $_SERVER['SERVER_PORT'] == $port)
1493     return true;
929a50 1494   if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
5818e4 1495     return true;
A 1496
1497   return false;
1498 }
1499
929a50 1500 // for backward compatibility
A 1501 function rcube_sess_unset($var_name=null)
1502 {
1503   global $RCMAIL;
1504
1505   $RCMAIL->session->remove($var_name);
1506 }
1507
5818e4 1508
A 1509 /**
e4acbb 1510  * E-mail address validation
A 1511  */
1baeb6 1512 function check_email($email, $dns_check=true)
e4acbb 1513 {
A 1514   // Check for invalid characters
1515   if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1516     return false;
1517
aba092 1518   // Check for length limit specified by RFC 5321 (#1486453)
A 1519   if (strlen($email) > 254) 
1520     return false;
1521
1baeb6 1522   $email_array = explode('@', $email);
A 1523
aba092 1524   // Check that there's one @ symbol
1baeb6 1525   if (count($email_array) < 2)
e4acbb 1526     return false;
A 1527
1baeb6 1528   $domain_part = array_pop($email_array);
A 1529   $local_part = implode('@', $email_array);
1530
1531   // from PEAR::Validate
1532   $regexp = '&^(?:
1533     ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                  #1 quoted name
1534     ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*))     #2 OR dot-atom
1535     $&xi';
1536
1537   if (!preg_match($regexp, $local_part))
1538     return false;
e4acbb 1539
A 1540   // Check domain part
1baeb6 1541   if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part))
A 1542     return true; // IP address
e4acbb 1543   else {
A 1544     // If not an IP address
1baeb6 1545     $domain_array = explode('.', $domain_part);
e4acbb 1546     if (sizeof($domain_array) < 2)
A 1547       return false; // Not enough parts to be a valid domain
1548
1baeb6 1549     foreach ($domain_array as $part)
A 1550       if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
e4acbb 1551         return false;
A 1552
1baeb6 1553     if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
e4acbb 1554       return true;
A 1555
1556     if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<'))
1557       return true;
1558
1559     // find MX record(s)
1baeb6 1560     if (getmxrr($domain_part, $mx_records))
e4acbb 1561       return true;
A 1562
1563     // find any DNS record
1baeb6 1564     if (checkdnsrr($domain_part, 'ANY'))
e4acbb 1565       return true;
A 1566   }
1567
1568   return false;
1569 }
1570
1571
1572 /**
aa055c 1573  * Helper class to turn relative urls into absolute ones
T 1574  * using a predefined base
1575  */
1576 class rcube_base_replacer
1577 {
1578   private $base_url;
1579   
1580   public function __construct($base)
1581   {
1582     $this->base_url = $base;
1583   }
1584   
1585   public function callback($matches)
1586   {
1587     return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1588   }
1589 }
1590
d1d2c4 1591 ?>