source: github/program/include/main.inc @ 03d772e

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 03d772e was 03d772e, checked in by thomascube <thomas@…>, 2 years ago

Also wrap unread count in span on server side (#1487720)

  • Property mode set to 100644
File size: 53.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, The Roundcube Dev Team                       |
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(is_array($row_data[$col]) ? $row_data[$col][0] : $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  static $colcounts = array();
824 
825  $fname = '_'.$col;
826  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
827  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
828 
829  if ($type == 'checkbox') {
830    $attrib['value'] = '1';
831    $input = new html_checkbox($attrib);
832  }
833  else if ($type == 'textarea') {
834    $attrib['cols'] = $attrib['size'];
835    $input = new html_textarea($attrib);
836  }
837  else if ($type == 'select') {
838    $input = new html_select($attrib);
839    $input->add('---', '');
840    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
841  }
842  else {
843    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
844        $attrib['type'] = 'text';
845    $input = new html_inputfield($attrib);
846  }
847
848  // use value from post
849  if (isset($_POST[$fname])) {
850    $postvalue = get_input_value($fname, RCUBE_INPUT_POST,
851      $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
852    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
853  }
854
855  $out = $input->show($value);
856
857  return $out;
858}
859
860
861/**
862 * Replace all css definitions with #container [def]
863 * and remove css-inlined scripting
864 *
865 * @param string CSS source code
866 * @param string Container ID to use as prefix
867 * @return string Modified CSS source
868 */
869function rcmail_mod_css_styles($source, $container_id)
870  {
871  $last_pos = 0;
872  $replacements = new rcube_string_replacer;
873
874  // ignore the whole block if evil styles are detected
875  $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
876  if (preg_match('/expression|behavior|url\(|import/', $stripped))
877    return '/* evil! */';
878
879  // remove css comments (sometimes used for some ugly hacks)
880  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
881
882  // cut out all contents between { and }
883  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
884  {
885    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
886    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
887    $last_pos = $pos+2;
888  }
889
890  // remove html comments and add #container to each tag selector.
891  // also replace body definition because we also stripped off the <body> tag
892  $styles = preg_replace(
893    array(
894      '/(^\s*<!--)|(-->\s*$)/',
895      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
896      '/'.preg_quote($container_id, '/').'\s+body/i',
897    ),
898    array(
899      '',
900      "\\1#$container_id \\2",
901      $container_id,
902    ),
903    $source);
904
905  // put block contents back in
906  $styles = $replacements->resolve($styles);
907
908  return $styles;
909  }
910
911
912/**
913 * Decode escaped entities used by known XSS exploits.
914 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
915 *
916 * @param string CSS content to decode
917 * @return string Decoded string
918 */
919function rcmail_xss_entity_decode($content)
920{
921  $out = html_entity_decode(html_entity_decode($content));
922  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
923  $out = preg_replace('#/\*.*\*/#Um', '', $out);
924  return $out;
925}
926
927
928/**
929 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
930 *
931 * @param array matches result from preg_replace_callback
932 * @return string decoded entity
933 */
934function rcmail_xss_entity_decode_callback($matches)
935{
936  return chr(hexdec($matches[1]));
937}
938
939/**
940 * Compose a valid attribute string for HTML tags
941 *
942 * @param array Named tag attributes
943 * @param array List of allowed attributes
944 * @return string HTML formatted attribute string
945 */
946function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
947  {
948  // allow the following attributes to be added to the <iframe> tag
949  $attrib_str = '';
950  foreach ($allowed_attribs as $a)
951    if (isset($attrib[$a]))
952      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
953
954  return $attrib_str;
955  }
956
957
958/**
959 * Convert a HTML attribute string attributes to an associative array (name => value)
960 *
961 * @param string Input string
962 * @return array Key-value pairs of parsed attributes
963 */
964function parse_attrib_string($str)
965  {
966  $attrib = array();
967  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
968
969  // convert attributes to an associative array (name => value)
970  if ($regs) {
971    foreach ($regs as $attr) {
972      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
973    }
974  }
975
976  return $attrib;
977  }
978
979
980/**
981 * Improved equivalent to strtotime()
982 *
983 * @param string Date string
984 * @return int
985 */
986function rcube_strtotime($date)
987{
988  // check for MS Outlook vCard date format YYYYMMDD
989  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
990    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
991  }
992  else if (is_numeric($date))
993    return $date;
994
995  // support non-standard "GMTXXXX" literal
996  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
997
998  // if date parsing fails, we have a date in non-rfc format.
999  // remove token from the end and try again
1000  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
1001    $d = explode(' ', $date);
1002    array_pop($d);
1003    if (!$d) break;
1004    $date = implode(' ', $d);
1005  }
1006
1007  return $ts;
1008}
1009
1010
1011/**
1012 * Convert the given date to a human readable form
1013 * This uses the date formatting properties from config
1014 *
1015 * @param mixed Date representation (string or timestamp)
1016 * @param string Date format to use
1017 * @return string Formatted date string
1018 */
1019function format_date($date, $format=NULL)
1020  {
1021  global $CONFIG;
1022 
1023  $ts = NULL;
1024
1025  if (!empty($date))
1026    $ts = rcube_strtotime($date);
1027
1028  if (empty($ts))
1029    return '';
1030   
1031  // get user's timezone
1032  if ($CONFIG['timezone'] === 'auto')
1033    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1034  else {
1035    $tz = $CONFIG['timezone'];
1036    if ($CONFIG['dst_active'])
1037      $tz++;
1038  }
1039
1040  // convert time to user's timezone
1041  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1042 
1043  // get current timestamp in user's timezone
1044  $now = time();  // local time
1045  $now -= (int)date('Z'); // make GMT time
1046  $now += ($tz * 3600); // user's time
1047  $now_date = getdate($now);
1048
1049  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1050  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1051
1052  // define date format depending on current time
1053  if (!$format) {
1054    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now)
1055      return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
1056    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1057      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1058    else
1059      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1060    }
1061
1062  // strftime() format
1063  if (preg_match('/%[a-z]+/i', $format))
1064    return strftime($format, $timestamp);
1065
1066  // parse format string manually in order to provide localized weekday and month names
1067  // an alternative would be to convert the date() format string to fit with strftime()
1068  $out = '';
1069  for($i=0; $i<strlen($format); $i++)
1070    {
1071    if ($format{$i}=='\\')  // skip escape chars
1072      continue;
1073   
1074    // write char "as-is"
1075    if ($format{$i}==' ' || $format{$i-1}=='\\')
1076      $out .= $format{$i};
1077    // weekday (short)
1078    else if ($format{$i}=='D')
1079      $out .= rcube_label(strtolower(date('D', $timestamp)));
1080    // weekday long
1081    else if ($format{$i}=='l')
1082      $out .= rcube_label(strtolower(date('l', $timestamp)));
1083    // month name (short)
1084    else if ($format{$i}=='M')
1085      $out .= rcube_label(strtolower(date('M', $timestamp)));
1086    // month name (long)
1087    else if ($format{$i}=='F')
1088      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1089    else if ($format{$i}=='x')
1090      $out .= strftime('%x %X', $timestamp);
1091    else
1092      $out .= date($format{$i}, $timestamp);
1093    }
1094 
1095  return $out;
1096  }
1097
1098
1099/**
1100 * Compose a valid representation of name and e-mail address
1101 *
1102 * @param string E-mail address
1103 * @param string Person name
1104 * @return string Formatted string
1105 */
1106function format_email_recipient($email, $name='')
1107  {
1108  if ($name && $name != $email)
1109    {
1110    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1111    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1112    }
1113  else
1114    return trim($email);
1115  }
1116
1117
1118
1119/****** debugging functions ********/
1120
1121
1122/**
1123 * Print or write debug messages
1124 *
1125 * @param mixed Debug message or data
1126 * @return void
1127 */
1128function console()
1129  {
1130  $args = func_get_args();
1131
1132  if (class_exists('rcmail', false)) {
1133    $rcmail = rcmail::get_instance();
1134    if (is_object($rcmail->plugins))
1135      $rcmail->plugins->exec_hook('console', $args);
1136  }
1137
1138  $msg = array();
1139  foreach ($args as $arg)
1140    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1141
1142  if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1143    write_log('console', join(";\n", $msg));
1144  else if ($GLOBALS['OUTPUT']->ajax_call)
1145    print "/*\n " . join(";\n", $msg) . " \n*/\n";
1146  else
1147    {
1148    print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1149    print join(";<br/>\n", $msg);
1150    print "</pre></div>\n";
1151    }
1152  }
1153
1154
1155/**
1156 * Append a line to a logfile in the logs directory.
1157 * Date will be added automatically to the line.
1158 *
1159 * @param $name name of log file
1160 * @param line Line to append
1161 * @return void
1162 */
1163function write_log($name, $line)
1164  {
1165  global $CONFIG, $RCMAIL;
1166
1167  if (!is_string($line))
1168    $line = var_export($line, true);
1169 
1170  if (empty($CONFIG['log_date_format']))
1171    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1172 
1173  $date = date($CONFIG['log_date_format']);
1174 
1175  // trigger logging hook
1176  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1177    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1178    $name = $log['name'];
1179    $line = $log['line'];
1180    $date = $log['date'];
1181    if ($log['abort'])
1182      return true;
1183  }
1184 
1185  if ($CONFIG['log_driver'] == 'syslog') {
1186    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1187    syslog($prio, $line);
1188    return true;
1189  }
1190  else {
1191    $line = sprintf("[%s]: %s\n", $date, $line);
1192
1193    // log_driver == 'file' is assumed here
1194    if (empty($CONFIG['log_dir']))
1195      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1196
1197    // try to open specific log file for writing
1198    $logfile = $CONFIG['log_dir'].'/'.$name;
1199    if ($fp = @fopen($logfile, 'a')) {
1200      fwrite($fp, $line);
1201      fflush($fp);
1202      fclose($fp);
1203      return true;
1204    }
1205    else
1206      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1207  }
1208  return false;
1209}
1210
1211
1212/**
1213 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1214 *
1215 * @return void
1216 */
1217function rcmail_log_login()
1218{
1219  global $RCMAIL;
1220
1221  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1222    return;
1223
1224  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1225    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1226}
1227
1228
1229/**
1230 * Returns remote IP address and forwarded addresses if found
1231 *
1232 * @return string Remote IP address(es)
1233 */
1234function rcmail_remote_ip()
1235{
1236    $address = $_SERVER['REMOTE_ADDR'];
1237
1238    // append the NGINX X-Real-IP header, if set
1239    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1240        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1241    }
1242    // append the X-Forwarded-For header, if set
1243    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1244        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1245    }
1246
1247    if (!empty($remote_ip))
1248        $address .= '(' . implode(',', $remote_ip) . ')';
1249
1250    return $address;
1251}
1252
1253
1254/**
1255 * @access private
1256 * @return mixed
1257 */
1258function rcube_timer()
1259{
1260  return microtime(true);
1261}
1262
1263
1264/**
1265 * @access private
1266 * @return void
1267 */
1268function rcube_print_time($timer, $label='Timer', $dest='console')
1269{
1270  static $print_count = 0;
1271 
1272  $print_count++;
1273  $now = rcube_timer();
1274  $diff = $now-$timer;
1275 
1276  if (empty($label))
1277    $label = 'Timer '.$print_count;
1278 
1279  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1280}
1281
1282
1283/**
1284 * Return the mailboxlist in HTML
1285 *
1286 * @param array Named parameters
1287 * @return string HTML code for the gui object
1288 */
1289function rcmail_mailbox_list($attrib)
1290{
1291  global $RCMAIL;
1292  static $a_mailboxes;
1293 
1294  $attrib += array('maxlength' => 100, 'realnames' => false);
1295
1296  // add some labels to client
1297  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1298 
1299  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1300  unset($attrib['type']);
1301
1302  if ($type=='ul' && !$attrib['id'])
1303    $attrib['id'] = 'rcmboxlist';
1304
1305  // get mailbox list
1306  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1307 
1308  // build the folders tree
1309  if (empty($a_mailboxes)) {
1310    // get mailbox list
1311    $a_folders = $RCMAIL->imap->list_mailboxes();
1312    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1313    $a_mailboxes = array();
1314
1315    foreach ($a_folders as $folder)
1316      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1317  }
1318
1319  // allow plugins to alter the folder tree or to localize folder names
1320  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1321
1322  if ($type=='select') {
1323    $select = new html_select($attrib);
1324   
1325    // add no-selection option
1326    if ($attrib['noselection'])
1327      $select->add(rcube_label($attrib['noselection']), '0');
1328   
1329    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1330    $out = $select->show();
1331  }
1332  else {
1333    $js_mailboxlist = array();
1334    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1335   
1336    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1337    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1338    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1339  }
1340
1341  return $out;
1342}
1343
1344
1345/**
1346 * Return the mailboxlist as html_select object
1347 *
1348 * @param array Named parameters
1349 * @return html_select HTML drop-down object
1350 */
1351function rcmail_mailbox_select($p = array())
1352{
1353  global $RCMAIL;
1354 
1355  $p += array('maxlength' => 100, 'realnames' => false);
1356  $a_mailboxes = array();
1357
1358  if ($p['unsubscribed'])
1359    $list = $RCMAIL->imap->list_unsubscribed();
1360  else
1361    $list = $RCMAIL->imap->list_mailboxes();
1362
1363  foreach ($list as $folder)
1364    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1365      rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1366
1367  $select = new html_select($p);
1368 
1369  if ($p['noselection'])
1370    $select->add($p['noselection'], '');
1371   
1372  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1373 
1374  return $select;
1375}
1376
1377
1378/**
1379 * Create a hierarchical array of the mailbox list
1380 * @access private
1381 * @return void
1382 */
1383function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1384{
1385  global $RCMAIL;
1386
1387  $pos = strpos($folder, $delm);
1388
1389  if ($pos !== false) {
1390    $subFolders = substr($folder, $pos+1);
1391    $currentFolder = substr($folder, 0, $pos);
1392
1393    // sometimes folder has a delimiter as the last character
1394    if (!strlen($subFolders))
1395      $virtual = false;
1396    else if (!isset($arrFolders[$currentFolder]))
1397      $virtual = true;
1398    else
1399      $virtual = $arrFolders[$currentFolder]['virtual'];
1400  }
1401  else {
1402    $subFolders = false;
1403    $currentFolder = $folder;
1404    $virtual = false;
1405  }
1406
1407  $path .= $currentFolder;
1408
1409  // Check \Noselect option (if options are in cache)
1410  if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1411    $virtual = in_array('\\Noselect', $opts);
1412  }
1413
1414  if (!isset($arrFolders[$currentFolder])) {
1415    $arrFolders[$currentFolder] = array(
1416      'id' => $path,
1417      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1418      'virtual' => $virtual,
1419      'folders' => array());
1420  }
1421  else
1422    $arrFolders[$currentFolder]['virtual'] = $virtual;
1423
1424  if (strlen($subFolders))
1425    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1426}
1427 
1428
1429/**
1430 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1431 * @access private
1432 * @return string
1433 */
1434function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1435{
1436  global $RCMAIL, $CONFIG;
1437 
1438  $maxlength = intval($attrib['maxlength']);
1439  $realnames = (bool)$attrib['realnames'];
1440  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1441
1442  $idx = 0;
1443  $out = '';
1444  foreach ($arrFolders as $key => $folder) {
1445    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1446    $title = null;
1447
1448    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1449      $foldername = rcube_label($folder_class);
1450    }
1451    else {
1452      $foldername = $folder['name'];
1453
1454      // shorten the folder name to a given length
1455      if ($maxlength && $maxlength > 1) {
1456        $fname = abbreviate_string($foldername, $maxlength);
1457        if ($fname != $foldername)
1458          $title = $foldername;
1459        $foldername = $fname;
1460      }
1461    }
1462
1463    // make folder name safe for ids and class names
1464    $folder_id = asciiwords($folder['id'], true, '_');
1465    $classes = array('mailbox');
1466
1467    // set special class for Sent, Drafts, Trash and Junk
1468    if ($folder['id']==$CONFIG['sent_mbox'])
1469      $classes[] = 'sent';
1470    else if ($folder['id']==$CONFIG['drafts_mbox'])
1471      $classes[] = 'drafts';
1472    else if ($folder['id']==$CONFIG['trash_mbox'])
1473      $classes[] = 'trash';
1474    else if ($folder['id']==$CONFIG['junk_mbox'])
1475      $classes[] = 'junk';
1476    else if ($folder['id']=='INBOX')
1477      $classes[] = 'inbox';
1478    else
1479      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1480     
1481    $classes[] = $zebra_class;
1482   
1483    if ($folder['id'] == $mbox_name)
1484      $classes[] = 'selected';
1485
1486    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1487    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1488   
1489    if ($folder['virtual'])
1490      $classes[] = 'virtual';
1491    else if ($unread)
1492      $classes[] = 'unread';
1493
1494    $js_name = JQ($folder['id']);
1495    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
1496    $link_attrib = $folder['virtual'] ? array() : array(
1497      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1498      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1499      'title' => $title,
1500    );
1501
1502    $out .= html::tag('li', array(
1503        'id' => "rcmli".$folder_id,
1504        'class' => join(' ', $classes),
1505        'noclose' => true),
1506      html::a($link_attrib, $html_name) .
1507      (!empty($folder['folders']) ? html::div(array(
1508        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1509        'style' => "position:absolute",
1510        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1511      ), '&nbsp;') : ''));
1512   
1513    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1514   
1515    if (!empty($folder['folders'])) {
1516      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1517        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1518    }
1519
1520    $out .= "</li>\n";
1521    $idx++;
1522  }
1523
1524  return $out;
1525}
1526
1527
1528/**
1529 * Return html for a flat list <select> for the mailbox tree
1530 * @access private
1531 * @return string
1532 */
1533function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1534  {
1535  $idx = 0;
1536  $out = '';
1537  foreach ($arrFolders as $key=>$folder)
1538    {
1539    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1540      $foldername = rcube_label($folder_class);
1541    else
1542      {
1543      $foldername = $folder['name'];
1544     
1545      // shorten the folder name to a given length
1546      if ($maxlength && $maxlength>1)
1547        $foldername = abbreviate_string($foldername, $maxlength);
1548      }
1549
1550    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1551
1552    if (!empty($folder['folders']))
1553      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1554
1555    $idx++;
1556    }
1557
1558  return $out;
1559  }
1560
1561
1562/**
1563 * Return internal name for the given folder if it matches the configured special folders
1564 * @access private
1565 * @return string
1566 */
1567function rcmail_folder_classname($folder_id)
1568{
1569  global $CONFIG;
1570
1571  if ($folder_id == 'INBOX')
1572    return 'inbox';
1573
1574  // for these mailboxes we have localized labels and css classes
1575  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1576  {
1577    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1578      return $smbx;
1579  }
1580}
1581
1582
1583/**
1584 * Try to localize the given IMAP folder name.
1585 * UTF-7 decode it in case no localized text was found
1586 *
1587 * @param string Folder name
1588 * @return string Localized folder name in UTF-8 encoding
1589 */
1590function rcmail_localize_foldername($name)
1591{
1592  if ($folder_class = rcmail_folder_classname($name))
1593    return rcube_label($folder_class);
1594  else
1595    return rcube_charset_convert($name, 'UTF7-IMAP');
1596}
1597
1598
1599function rcmail_quota_display($attrib)
1600{
1601  global $OUTPUT;
1602
1603  if (!$attrib['id'])
1604    $attrib['id'] = 'rcmquotadisplay';
1605
1606  if(isset($attrib['display']))
1607    $_SESSION['quota_display'] = $attrib['display'];
1608
1609  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1610
1611  $quota = rcmail_quota_content($attrib);
1612
1613  $OUTPUT->add_script('$(document).ready(function(){
1614        rcmail.set_quota('.json_serialize($quota).')});', 'foot');
1615
1616  return html::span($attrib, '');
1617}
1618
1619
1620function rcmail_quota_content($attrib=NULL)
1621{
1622  global $RCMAIL;
1623
1624  $quota = $RCMAIL->imap->get_quota();
1625  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1626
1627  $quota_result = (array) $quota;
1628  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1629
1630  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1631    $quota_result['title'] = rcube_label('unlimited');
1632    $quota_result['percent'] = 0;
1633  }
1634  else if ($quota['total']) {
1635    if (!isset($quota['percent']))
1636      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1637
1638    $title = sprintf('%s / %s (%.0f%%)',
1639        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1640        $quota_result['percent']);
1641
1642    $quota_result['title'] = $title;
1643
1644    if ($attrib['width'])
1645      $quota_result['width'] = $attrib['width'];
1646    if ($attrib['height'])
1647      $quota_result['height']   = $attrib['height'];
1648  }
1649  else {
1650    $quota_result['title'] = rcube_label('unknown');
1651    $quota_result['percent'] = 0;
1652  }
1653
1654  return $quota_result;
1655}
1656
1657
1658/**
1659 * Outputs error message according to server error/response codes
1660 *
1661 * @param string Fallback message label
1662 * @param string Fallback message label arguments
1663 *
1664 * @return void
1665 */
1666function rcmail_display_server_error($fallback=null, $fallback_args=null)
1667{
1668    global $RCMAIL;
1669
1670    $err_code = $RCMAIL->imap->get_error_code();
1671    $res_code = $RCMAIL->imap->get_response_code();
1672
1673    if ($res_code == rcube_imap::NOPERM) {
1674        $RCMAIL->output->show_message('errornoperm', 'error');
1675    }
1676    else if ($res_code == rcube_imap::READONLY) {
1677        $RCMAIL->output->show_message('errorreadonly', 'error');
1678    }
1679    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1680        $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1681    }
1682    else if ($fallback) {
1683        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1684    }
1685
1686    return true;
1687}
1688
1689
1690/**
1691 * Output HTML editor scripts
1692 *
1693 * @param string Editor mode
1694 * @return void
1695 */
1696function rcube_html_editor($mode='')
1697{
1698  global $RCMAIL, $CONFIG;
1699
1700  $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1701
1702  if ($hook['abort'])
1703    return; 
1704
1705  $lang = strtolower($_SESSION['language']);
1706
1707  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1708  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1709
1710  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1711    $lang = 'en';
1712
1713  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1714  $RCMAIL->output->include_script('editor.js');
1715  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1716    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1717    'foot');
1718}
1719
1720
1721/**
1722 * Replaces TinyMCE's emoticon images with plain-text representation
1723 *
1724 * @param string HTML content
1725 * @return string HTML content
1726 */
1727function rcmail_replace_emoticons($html)
1728{
1729  $emoticons = array(
1730    '8-)' => 'smiley-cool',
1731    ':-#' => 'smiley-foot-in-mouth',
1732    ':-*' => 'smiley-kiss',
1733    ':-X' => 'smiley-sealed',
1734    ':-P' => 'smiley-tongue-out',
1735    ':-@' => 'smiley-yell',
1736    ":'(" => 'smiley-cry',
1737    ':-(' => 'smiley-frown',
1738    ':-D' => 'smiley-laughing',
1739    ':-)' => 'smiley-smile',
1740    ':-S' => 'smiley-undecided',
1741    ':-$' => 'smiley-embarassed',
1742    'O:-)' => 'smiley-innocent',
1743    ':-|' => 'smiley-money-mouth',
1744    ':-O' => 'smiley-surprised',
1745    ';-)' => 'smiley-wink',
1746  );
1747
1748  foreach ($emoticons as $idx => $file) {
1749    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1750    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1751    $replace[] = $idx;
1752  }
1753
1754  return preg_replace($search, $replace, $html);
1755}
1756
1757
1758/**
1759 * Check if working in SSL mode
1760 *
1761 * @param integer HTTPS port number
1762 * @param boolean Enables 'use_https' option checking
1763 * @return boolean
1764 */
1765function rcube_https_check($port=null, $use_https=true)
1766{
1767  global $RCMAIL;
1768
1769  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1770    return true;
1771  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1772    return true;
1773  if ($port && $_SERVER['SERVER_PORT'] == $port)
1774    return true;
1775  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1776    return true;
1777
1778  return false;
1779}
1780
1781
1782/**
1783 * For backward compatibility.
1784 *
1785 * @global rcmail $RCMAIL
1786 * @param string $var_name Variable name.
1787 * @return void
1788 */
1789function rcube_sess_unset($var_name=null)
1790{
1791  global $RCMAIL;
1792
1793  $RCMAIL->session->remove($var_name);
1794}
1795
1796
1797
1798/**
1799 * Replaces hostname variables
1800 *
1801 * @param string $name Hostname
1802 * @param string $host Optional IMAP hostname
1803 * @return string
1804 */
1805function rcube_parse_host($name, $host='')
1806{
1807  // %n - host
1808  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1809  // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1810  $d = preg_replace('/^[^\.]+\./', '', $n);
1811  // %h - IMAP host
1812  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1813  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1814  $z = preg_replace('/^[^\.]+\./', '', $h);
1815
1816  $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1817  return $name;
1818}
1819
1820
1821/**
1822 * E-mail address validation
1823 *
1824 * @param string $email Email address
1825 * @param boolean $dns_check True to check dns
1826 * @return boolean
1827 */
1828function check_email($email, $dns_check=true)
1829{
1830  // Check for invalid characters
1831  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1832    return false;
1833
1834  // Check for length limit specified by RFC 5321 (#1486453)
1835  if (strlen($email) > 254)
1836    return false;
1837
1838  $email_array = explode('@', $email);
1839
1840  // Check that there's one @ symbol
1841  if (count($email_array) < 2)
1842    return false;
1843
1844  $domain_part = array_pop($email_array);
1845  $local_part = implode('@', $email_array);
1846
1847  // from PEAR::Validate
1848  $regexp = '&^(?:
1849        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1850        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1851        $&xi';
1852
1853  if (!preg_match($regexp, $local_part))
1854    return false;
1855
1856  // Check domain part
1857  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))
1858    return true; // IP address
1859  else {
1860    // If not an IP address
1861    $domain_array = explode('.', $domain_part);
1862    if (sizeof($domain_array) < 2)
1863      return false; // Not enough parts to be a valid domain
1864
1865    foreach ($domain_array as $part)
1866      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1867        return false;
1868
1869    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1870      return true;
1871
1872    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1873      $lookup = array();
1874      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1875      foreach ($lookup as $line) {
1876        if (strpos($line, 'MX preference'))
1877          return true;
1878      }
1879      return false;
1880    }
1881
1882    // find MX record(s)
1883    if (getmxrr($domain_part, $mx_records))
1884      return true;
1885
1886    // find any DNS record
1887    if (checkdnsrr($domain_part, 'ANY'))
1888      return true;
1889  }
1890
1891  return false;
1892}
1893
1894
1895/**
1896 * Helper class to turn relative urls into absolute ones
1897 * using a predefined base
1898 */
1899class rcube_base_replacer
1900{
1901  private $base_url;
1902
1903  public function __construct($base)
1904  {
1905    $this->base_url = $base;
1906  }
1907
1908  public function callback($matches)
1909  {
1910    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1911  }
1912}
1913
1914
1915/**
1916 * Throw system error and show error page
1917 *
1918 * @param array Named parameters
1919 *  - code: Error code (required)
1920 *  - type: Error type [php|db|imap|javascript] (required)
1921 *  - message: Error message
1922 *  - file: File where error occured
1923 *  - line: Line where error occured
1924 * @param boolean True to log the error
1925 * @param boolean Terminate script execution
1926 */
1927// may be defined in Installer
1928if (!function_exists('raise_error')) {
1929function raise_error($arg=array(), $log=false, $terminate=false)
1930{
1931    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1932
1933    // report bug (if not incompatible browser)
1934    if ($log && $arg['type'] && $arg['message'])
1935        log_bug($arg);
1936
1937    // display error page and terminate script
1938    if ($terminate) {
1939        $ERROR_CODE = $arg['code'];
1940        $ERROR_MESSAGE = $arg['message'];
1941        include('program/steps/utils/error.inc');
1942        exit;
1943    }
1944}
1945}
1946
1947
1948/**
1949 * Report error according to configured debug_level
1950 *
1951 * @param array Named parameters
1952 * @return void
1953 * @see raise_error()
1954 */
1955function log_bug($arg_arr)
1956{
1957    global $CONFIG;
1958    $program = strtoupper($arg_arr['type']);
1959
1960    // write error to local log file
1961    if ($CONFIG['debug_level'] & 1) {
1962        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1963        $log_entry = sprintf("%s Error: %s%s (%s %s)",
1964            $program,
1965            $arg_arr['message'],
1966            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1967            $_SERVER['REQUEST_METHOD'],
1968            $_SERVER['REQUEST_URI'] . $post_query);
1969
1970        if (!write_log('errors', $log_entry)) {
1971            // send error to PHPs error handler if write_log didn't succeed
1972            trigger_error($arg_arr['message']);
1973        }
1974    }
1975
1976    // resport the bug to the global bug reporting system
1977    if ($CONFIG['debug_level'] & 2) {
1978        // TODO: Send error via HTTP
1979    }
1980
1981    // show error if debug_mode is on
1982    if ($CONFIG['debug_level'] & 4) {
1983        print "<b>$program Error";
1984
1985        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
1986            print " in $arg_arr[file] ($arg_arr[line])";
1987
1988        print ':</b>&nbsp;';
1989        print nl2br($arg_arr['message']);
1990        print '<br />';
1991        flush();
1992    }
1993}
1994
Note: See TracBrowser for help on using the repository browser.