source: github/program/include/main.inc @ 11be933

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