source: subversion/trunk/roundcubemail/program/include/main.inc @ 4326

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