source: subversion/branches/release-0.5/program/include/main.inc @ 4568

Last change on this file since 4568 was 4568, checked in by alec, 2 years ago
  • Merge fixes from trunk
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 53.5 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[^a]/', $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      $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1028      $today  = true;
1029    }
1030    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1031      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1032    else
1033      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1034  }
1035
1036  // strftime() format
1037  if (preg_match('/%[a-z]+/i', $format)) {
1038    $format = strftime($format, $timestamp);
1039    return $today ? (rcube_label('today') . ' ' . $format) : $format;
1040  }
1041
1042  // parse format string manually in order to provide localized weekday and month names
1043  // an alternative would be to convert the date() format string to fit with strftime()
1044  $out = '';
1045  for($i=0; $i<strlen($format); $i++) {
1046    if ($format{$i}=='\\')  // skip escape chars
1047      continue;
1048
1049    // write char "as-is"
1050    if ($format{$i}==' ' || $format{$i-1}=='\\')
1051      $out .= $format{$i};
1052    // weekday (short)
1053    else if ($format{$i}=='D')
1054      $out .= rcube_label(strtolower(date('D', $timestamp)));
1055    // weekday long
1056    else if ($format{$i}=='l')
1057      $out .= rcube_label(strtolower(date('l', $timestamp)));
1058    // month name (short)
1059    else if ($format{$i}=='M')
1060      $out .= rcube_label(strtolower(date('M', $timestamp)));
1061    // month name (long)
1062    else if ($format{$i}=='F')
1063      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1064    else if ($format{$i}=='x')
1065      $out .= strftime('%x %X', $timestamp);
1066    else
1067      $out .= date($format{$i}, $timestamp);
1068  }
1069
1070  if ($today) {
1071    $label = rcube_label('today');
1072    // replcae $ character with "Today" label (#1486120)
1073    if (strpos($out, '$') !== false) {
1074      $out = preg_replace('/\$/', $label, $out, 1);
1075    }
1076    else {
1077      $out = $label . ' ' . $out;
1078    }
1079  }
1080
1081  return $out;
1082}
1083
1084
1085/**
1086 * Compose a valid representation of name and e-mail address
1087 *
1088 * @param string E-mail address
1089 * @param string Person name
1090 * @return string Formatted string
1091 */
1092function format_email_recipient($email, $name='')
1093  {
1094  if ($name && $name != $email)
1095    {
1096    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1097    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1098    }
1099  else
1100    return trim($email);
1101  }
1102
1103
1104
1105/****** debugging functions ********/
1106
1107
1108/**
1109 * Print or write debug messages
1110 *
1111 * @param mixed Debug message or data
1112 * @return void
1113 */
1114function console()
1115  {
1116  $args = func_get_args();
1117
1118  if (class_exists('rcmail', false)) {
1119    $rcmail = rcmail::get_instance();
1120    if (is_object($rcmail->plugins))
1121      $rcmail->plugins->exec_hook('console', $args);
1122  }
1123
1124  $msg = array();
1125  foreach ($args as $arg)
1126    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1127
1128  if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1129    write_log('console', join(";\n", $msg));
1130  else if ($GLOBALS['OUTPUT']->ajax_call)
1131    print "/*\n " . join(";\n", $msg) . " \n*/\n";
1132  else
1133    {
1134    print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1135    print join(";<br/>\n", $msg);
1136    print "</pre></div>\n";
1137    }
1138  }
1139
1140
1141/**
1142 * Append a line to a logfile in the logs directory.
1143 * Date will be added automatically to the line.
1144 *
1145 * @param $name name of log file
1146 * @param line Line to append
1147 * @return void
1148 */
1149function write_log($name, $line)
1150  {
1151  global $CONFIG, $RCMAIL;
1152
1153  if (!is_string($line))
1154    $line = var_export($line, true);
1155 
1156  if (empty($CONFIG['log_date_format']))
1157    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1158 
1159  $date = date($CONFIG['log_date_format']);
1160 
1161  // trigger logging hook
1162  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1163    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1164    $name = $log['name'];
1165    $line = $log['line'];
1166    $date = $log['date'];
1167    if ($log['abort'])
1168      return true;
1169  }
1170 
1171  if ($CONFIG['log_driver'] == 'syslog') {
1172    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1173    syslog($prio, $line);
1174    return true;
1175  }
1176  else {
1177    $line = sprintf("[%s]: %s\n", $date, $line);
1178
1179    // log_driver == 'file' is assumed here
1180    if (empty($CONFIG['log_dir']))
1181      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1182
1183    // try to open specific log file for writing
1184    $logfile = $CONFIG['log_dir'].'/'.$name;
1185    if ($fp = @fopen($logfile, 'a')) {
1186      fwrite($fp, $line);
1187      fflush($fp);
1188      fclose($fp);
1189      return true;
1190    }
1191    else
1192      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1193  }
1194  return false;
1195}
1196
1197
1198/**
1199 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1200 *
1201 * @return void
1202 */
1203function rcmail_log_login()
1204{
1205  global $RCMAIL;
1206
1207  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1208    return;
1209
1210  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1211    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1212}
1213
1214
1215/**
1216 * Returns remote IP address and forwarded addresses if found
1217 *
1218 * @return string Remote IP address(es)
1219 */
1220function rcmail_remote_ip()
1221{
1222    $address = $_SERVER['REMOTE_ADDR'];
1223
1224    // append the NGINX X-Real-IP header, if set
1225    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1226        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1227    }
1228    // append the X-Forwarded-For header, if set
1229    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1230        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1231    }
1232
1233    if (!empty($remote_ip))
1234        $address .= '(' . implode(',', $remote_ip) . ')';
1235
1236    return $address;
1237}
1238
1239
1240/**
1241 * Check whether the HTTP referer matches the current request
1242 *
1243 * @return boolean True if referer is the same host+path, false if not
1244 */
1245function rcube_check_referer()
1246{
1247  $uri = parse_url($_SERVER['REQUEST_URI']);
1248  $referer = parse_url(rc_request_header('Referer'));
1249  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1250}
1251
1252
1253/**
1254 * @access private
1255 * @return mixed
1256 */
1257function rcube_timer()
1258{
1259  return microtime(true);
1260}
1261
1262
1263/**
1264 * @access private
1265 * @return void
1266 */
1267function rcube_print_time($timer, $label='Timer', $dest='console')
1268{
1269  static $print_count = 0;
1270 
1271  $print_count++;
1272  $now = rcube_timer();
1273  $diff = $now-$timer;
1274 
1275  if (empty($label))
1276    $label = 'Timer '.$print_count;
1277 
1278  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1279}
1280
1281
1282/**
1283 * Return the mailboxlist in HTML
1284 *
1285 * @param array Named parameters
1286 * @return string HTML code for the gui object
1287 */
1288function rcmail_mailbox_list($attrib)
1289{
1290  global $RCMAIL;
1291  static $a_mailboxes;
1292 
1293  $attrib += array('maxlength' => 100, 'realnames' => false);
1294
1295  // add some labels to client
1296  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1297 
1298  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1299  unset($attrib['type']);
1300
1301  if ($type=='ul' && !$attrib['id'])
1302    $attrib['id'] = 'rcmboxlist';
1303
1304  // get mailbox list
1305  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1306 
1307  // build the folders tree
1308  if (empty($a_mailboxes)) {
1309    // get mailbox list
1310    $a_folders = $RCMAIL->imap->list_mailboxes();
1311    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1312    $a_mailboxes = array();
1313
1314    foreach ($a_folders as $folder)
1315      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1316  }
1317
1318  // allow plugins to alter the folder tree or to localize folder names
1319  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1320
1321  if ($type=='select') {
1322    $select = new html_select($attrib);
1323   
1324    // add no-selection option
1325    if ($attrib['noselection'])
1326      $select->add(rcube_label($attrib['noselection']), '0');
1327   
1328    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1329    $out = $select->show();
1330  }
1331  else {
1332    $js_mailboxlist = array();
1333    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1334   
1335    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1336    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1337    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1338  }
1339
1340  return $out;
1341}
1342
1343
1344/**
1345 * Return the mailboxlist as html_select object
1346 *
1347 * @param array Named parameters
1348 * @return html_select HTML drop-down object
1349 */
1350function rcmail_mailbox_select($p = array())
1351{
1352  global $RCMAIL;
1353 
1354  $p += array('maxlength' => 100, 'realnames' => false);
1355  $a_mailboxes = array();
1356
1357  if ($p['unsubscribed'])
1358    $list = $RCMAIL->imap->list_unsubscribed();
1359  else
1360    $list = $RCMAIL->imap->list_mailboxes();
1361
1362  foreach ($list as $folder)
1363    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1364      rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1365
1366  $select = new html_select($p);
1367 
1368  if ($p['noselection'])
1369    $select->add($p['noselection'], '');
1370   
1371  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1372 
1373  return $select;
1374}
1375
1376
1377/**
1378 * Create a hierarchical array of the mailbox list
1379 * @access private
1380 * @return void
1381 */
1382function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1383{
1384  global $RCMAIL;
1385
1386  $pos = strpos($folder, $delm);
1387
1388  if ($pos !== false) {
1389    $subFolders = substr($folder, $pos+1);
1390    $currentFolder = substr($folder, 0, $pos);
1391
1392    // sometimes folder has a delimiter as the last character
1393    if (!strlen($subFolders))
1394      $virtual = false;
1395    else if (!isset($arrFolders[$currentFolder]))
1396      $virtual = true;
1397    else
1398      $virtual = $arrFolders[$currentFolder]['virtual'];
1399  }
1400  else {
1401    $subFolders = false;
1402    $currentFolder = $folder;
1403    $virtual = false;
1404  }
1405
1406  $path .= $currentFolder;
1407
1408  // Check \Noselect option (if options are in cache)
1409  if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1410    $virtual = in_array('\\Noselect', $opts);
1411  }
1412
1413  if (!isset($arrFolders[$currentFolder])) {
1414    $arrFolders[$currentFolder] = array(
1415      'id' => $path,
1416      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1417      'virtual' => $virtual,
1418      'folders' => array());
1419  }
1420  else
1421    $arrFolders[$currentFolder]['virtual'] = $virtual;
1422
1423  if (strlen($subFolders))
1424    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1425}
1426 
1427
1428/**
1429 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1430 * @access private
1431 * @return string
1432 */
1433function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1434{
1435  global $RCMAIL, $CONFIG;
1436 
1437  $maxlength = intval($attrib['maxlength']);
1438  $realnames = (bool)$attrib['realnames'];
1439  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1440
1441  $idx = 0;
1442  $out = '';
1443  foreach ($arrFolders as $key => $folder) {
1444    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1445    $title = null;
1446
1447    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1448      $foldername = rcube_label($folder_class);
1449    }
1450    else {
1451      $foldername = $folder['name'];
1452
1453      // shorten the folder name to a given length
1454      if ($maxlength && $maxlength > 1) {
1455        $fname = abbreviate_string($foldername, $maxlength);
1456        if ($fname != $foldername)
1457          $title = $foldername;
1458        $foldername = $fname;
1459      }
1460    }
1461
1462    // make folder name safe for ids and class names
1463    $folder_id = asciiwords($folder['id'], true, '_');
1464    $classes = array('mailbox');
1465
1466    // set special class for Sent, Drafts, Trash and Junk
1467    if ($folder['id']==$CONFIG['sent_mbox'])
1468      $classes[] = 'sent';
1469    else if ($folder['id']==$CONFIG['drafts_mbox'])
1470      $classes[] = 'drafts';
1471    else if ($folder['id']==$CONFIG['trash_mbox'])
1472      $classes[] = 'trash';
1473    else if ($folder['id']==$CONFIG['junk_mbox'])
1474      $classes[] = 'junk';
1475    else if ($folder['id']=='INBOX')
1476      $classes[] = 'inbox';
1477    else
1478      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1479     
1480    $classes[] = $zebra_class;
1481   
1482    if ($folder['id'] == $mbox_name)
1483      $classes[] = 'selected';
1484
1485    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1486    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1487   
1488    if ($folder['virtual'])
1489      $classes[] = 'virtual';
1490    else if ($unread)
1491      $classes[] = 'unread';
1492
1493    $js_name = JQ($folder['id']);
1494    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1495    $link_attrib = $folder['virtual'] ? array() : array(
1496      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1497      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1498      'title' => $title,
1499    );
1500
1501    $out .= html::tag('li', array(
1502        'id' => "rcmli".$folder_id,
1503        'class' => join(' ', $classes),
1504        'noclose' => true),
1505      html::a($link_attrib, $html_name) .
1506      (!empty($folder['folders']) ? html::div(array(
1507        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1508        'style' => "position:absolute",
1509        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1510      ), '&nbsp;') : ''));
1511   
1512    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1513   
1514    if (!empty($folder['folders'])) {
1515      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1516        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1517    }
1518
1519    $out .= "</li>\n";
1520    $idx++;
1521  }
1522
1523  return $out;
1524}
1525
1526
1527/**
1528 * Return html for a flat list <select> for the mailbox tree
1529 * @access private
1530 * @return string
1531 */
1532function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1533  {
1534  $idx = 0;
1535  $out = '';
1536  foreach ($arrFolders as $key=>$folder)
1537    {
1538    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1539      $foldername = rcube_label($folder_class);
1540    else
1541      {
1542      $foldername = $folder['name'];
1543     
1544      // shorten the folder name to a given length
1545      if ($maxlength && $maxlength>1)
1546        $foldername = abbreviate_string($foldername, $maxlength);
1547      }
1548
1549    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1550
1551    if (!empty($folder['folders']))
1552      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1553
1554    $idx++;
1555    }
1556
1557  return $out;
1558  }
1559
1560
1561/**
1562 * Return internal name for the given folder if it matches the configured special folders
1563 * @access private
1564 * @return string
1565 */
1566function rcmail_folder_classname($folder_id)
1567{
1568  global $CONFIG;
1569
1570  if ($folder_id == 'INBOX')
1571    return 'inbox';
1572
1573  // for these mailboxes we have localized labels and css classes
1574  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1575  {
1576    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1577      return $smbx;
1578  }
1579}
1580
1581
1582/**
1583 * Try to localize the given IMAP folder name.
1584 * UTF-7 decode it in case no localized text was found
1585 *
1586 * @param string Folder name
1587 * @return string Localized folder name in UTF-8 encoding
1588 */
1589function rcmail_localize_foldername($name)
1590{
1591  if ($folder_class = rcmail_folder_classname($name))
1592    return rcube_label($folder_class);
1593  else
1594    return rcube_charset_convert($name, 'UTF7-IMAP');
1595}
1596
1597
1598function rcmail_quota_display($attrib)
1599{
1600  global $OUTPUT;
1601
1602  if (!$attrib['id'])
1603    $attrib['id'] = 'rcmquotadisplay';
1604
1605  if(isset($attrib['display']))
1606    $_SESSION['quota_display'] = $attrib['display'];
1607
1608  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1609
1610  $quota = rcmail_quota_content($attrib);
1611
1612  $OUTPUT->add_script('$(document).ready(function(){
1613        rcmail.set_quota('.json_serialize($quota).')});', 'foot');
1614
1615  return html::span($attrib, '');
1616}
1617
1618
1619function rcmail_quota_content($attrib=NULL)
1620{
1621  global $RCMAIL;
1622
1623  $quota = $RCMAIL->imap->get_quota();
1624  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1625
1626  $quota_result = (array) $quota;
1627  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1628
1629  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1630    $quota_result['title'] = rcube_label('unlimited');
1631    $quota_result['percent'] = 0;
1632  }
1633  else if ($quota['total']) {
1634    if (!isset($quota['percent']))
1635      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1636
1637    $title = sprintf('%s / %s (%.0f%%)',
1638        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1639        $quota_result['percent']);
1640
1641    $quota_result['title'] = $title;
1642
1643    if ($attrib['width'])
1644      $quota_result['width'] = $attrib['width'];
1645    if ($attrib['height'])
1646      $quota_result['height']   = $attrib['height'];
1647  }
1648  else {
1649    $quota_result['title'] = rcube_label('unknown');
1650    $quota_result['percent'] = 0;
1651  }
1652
1653  return $quota_result;
1654}
1655
1656
1657/**
1658 * Outputs error message according to server error/response codes
1659 *
1660 * @param string Fallback message label
1661 * @param string Fallback message label arguments
1662 *
1663 * @return void
1664 */
1665function rcmail_display_server_error($fallback=null, $fallback_args=null)
1666{
1667    global $RCMAIL;
1668
1669    $err_code = $RCMAIL->imap->get_error_code();
1670    $res_code = $RCMAIL->imap->get_response_code();
1671
1672    if ($res_code == rcube_imap::NOPERM) {
1673        $RCMAIL->output->show_message('errornoperm', 'error');
1674    }
1675    else if ($res_code == rcube_imap::READONLY) {
1676        $RCMAIL->output->show_message('errorreadonly', 'error');
1677    }
1678    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1679        $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1680    }
1681    else if ($fallback) {
1682        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1683    }
1684
1685    return true;
1686}
1687
1688
1689/**
1690 * Output HTML editor scripts
1691 *
1692 * @param string Editor mode
1693 * @return void
1694 */
1695function rcube_html_editor($mode='')
1696{
1697  global $RCMAIL, $CONFIG;
1698
1699  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1700
1701  if ($hook['abort'])
1702    return; 
1703
1704  $lang = strtolower($_SESSION['language']);
1705
1706  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1707  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1708
1709  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1710    $lang = 'en';
1711
1712  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1713  $RCMAIL->output->include_script('editor.js');
1714  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1715    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1716    'foot');
1717}
1718
1719
1720/**
1721 * Replaces TinyMCE's emoticon images with plain-text representation
1722 *
1723 * @param string HTML content
1724 * @return string HTML content
1725 */
1726function rcmail_replace_emoticons($html)
1727{
1728  $emoticons = array(
1729    '8-)' => 'smiley-cool',
1730    ':-#' => 'smiley-foot-in-mouth',
1731    ':-*' => 'smiley-kiss',
1732    ':-X' => 'smiley-sealed',
1733    ':-P' => 'smiley-tongue-out',
1734    ':-@' => 'smiley-yell',
1735    ":'(" => 'smiley-cry',
1736    ':-(' => 'smiley-frown',
1737    ':-D' => 'smiley-laughing',
1738    ':-)' => 'smiley-smile',
1739    ':-S' => 'smiley-undecided',
1740    ':-$' => 'smiley-embarassed',
1741    'O:-)' => 'smiley-innocent',
1742    ':-|' => 'smiley-money-mouth',
1743    ':-O' => 'smiley-surprised',
1744    ';-)' => 'smiley-wink',
1745  );
1746
1747  foreach ($emoticons as $idx => $file) {
1748    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1749    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1750    $replace[] = $idx;
1751  }
1752
1753  return preg_replace($search, $replace, $html);
1754}
1755
1756
1757/**
1758 * Check if working in SSL mode
1759 *
1760 * @param integer HTTPS port number
1761 * @param boolean Enables 'use_https' option checking
1762 * @return boolean
1763 */
1764function rcube_https_check($port=null, $use_https=true)
1765{
1766  global $RCMAIL;
1767
1768  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1769    return true;
1770  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1771    return true;
1772  if ($port && $_SERVER['SERVER_PORT'] == $port)
1773    return true;
1774  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1775    return true;
1776
1777  return false;
1778}
1779
1780
1781/**
1782 * For backward compatibility.
1783 *
1784 * @global rcmail $RCMAIL
1785 * @param string $var_name Variable name.
1786 * @return void
1787 */
1788function rcube_sess_unset($var_name=null)
1789{
1790  global $RCMAIL;
1791
1792  $RCMAIL->session->remove($var_name);
1793}
1794
1795
1796
1797/**
1798 * Replaces hostname variables
1799 *
1800 * @param string $name Hostname
1801 * @param string $host Optional IMAP hostname
1802 * @return string
1803 */
1804function rcube_parse_host($name, $host='')
1805{
1806  // %n - host
1807  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1808  // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1809  $d = preg_replace('/^[^\.]+\./', '', $n);
1810  // %h - IMAP host
1811  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1812  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1813  $z = preg_replace('/^[^\.]+\./', '', $h);
1814
1815  $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1816  return $name;
1817}
1818
1819
1820/**
1821 * E-mail address validation
1822 *
1823 * @param string $email Email address
1824 * @param boolean $dns_check True to check dns
1825 * @return boolean
1826 */
1827function check_email($email, $dns_check=true)
1828{
1829  // Check for invalid characters
1830  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1831    return false;
1832
1833  // Check for length limit specified by RFC 5321 (#1486453)
1834  if (strlen($email) > 254)
1835    return false;
1836
1837  $email_array = explode('@', $email);
1838
1839  // Check that there's one @ symbol
1840  if (count($email_array) < 2)
1841    return false;
1842
1843  $domain_part = array_pop($email_array);
1844  $local_part = implode('@', $email_array);
1845
1846  // from PEAR::Validate
1847  $regexp = '&^(?:
1848        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1849        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1850        $&xi';
1851
1852  if (!preg_match($regexp, $local_part))
1853    return false;
1854
1855  // Check domain part
1856  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))
1857    return true; // IP address
1858  else {
1859    // If not an IP address
1860    $domain_array = explode('.', $domain_part);
1861    if (sizeof($domain_array) < 2)
1862      return false; // Not enough parts to be a valid domain
1863
1864    foreach ($domain_array as $part)
1865      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1866        return false;
1867
1868    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1869      return true;
1870
1871    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1872      $lookup = array();
1873      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1874      foreach ($lookup as $line) {
1875        if (strpos($line, 'MX preference'))
1876          return true;
1877      }
1878      return false;
1879    }
1880
1881    // find MX record(s)
1882    if (getmxrr($domain_part, $mx_records))
1883      return true;
1884
1885    // find any DNS record
1886    if (checkdnsrr($domain_part, 'ANY'))
1887      return true;
1888  }
1889
1890  return false;
1891}
1892
1893/*
1894 * Idn_to_ascii wrapper.
1895 * Intl/Idn modules version of this function doesn't work with e-mail address
1896 */
1897function rcube_idn_to_ascii($str)
1898{
1899  return rcube_idn_convert($str, true);
1900}
1901
1902/*
1903 * Idn_to_ascii wrapper.
1904 * Intl/Idn modules version of this function doesn't work with e-mail address
1905 */
1906function rcube_idn_to_utf8($str)
1907{
1908  return rcube_idn_convert($str, false);
1909}
1910
1911function rcube_idn_convert($input, $is_utf=false)
1912{
1913  if ($at = strpos($input, '@')) {
1914    $user   = substr($input, 0, $at);
1915    $domain = substr($input, $at+1);
1916  }
1917  else {
1918    $domain = $input;
1919  }
1920
1921  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1922
1923  return $at ? $user . '@' . $domain : $domain;
1924}
1925
1926
1927/**
1928 * Helper class to turn relative urls into absolute ones
1929 * using a predefined base
1930 */
1931class rcube_base_replacer
1932{
1933  private $base_url;
1934
1935  public function __construct($base)
1936  {
1937    $this->base_url = $base;
1938  }
1939
1940  public function callback($matches)
1941  {
1942    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1943  }
1944}
1945
1946
1947/**
1948 * Throw system error and show error page
1949 *
1950 * @param array Named parameters
1951 *  - code: Error code (required)
1952 *  - type: Error type [php|db|imap|javascript] (required)
1953 *  - message: Error message
1954 *  - file: File where error occured
1955 *  - line: Line where error occured
1956 * @param boolean True to log the error
1957 * @param boolean Terminate script execution
1958 */
1959// may be defined in Installer
1960if (!function_exists('raise_error')) {
1961function raise_error($arg=array(), $log=false, $terminate=false)
1962{
1963    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1964
1965    // report bug (if not incompatible browser)
1966    if ($log && $arg['type'] && $arg['message'])
1967        log_bug($arg);
1968
1969    // display error page and terminate script
1970    if ($terminate) {
1971        $ERROR_CODE = $arg['code'];
1972        $ERROR_MESSAGE = $arg['message'];
1973        include('program/steps/utils/error.inc');
1974        exit;
1975    }
1976}
1977}
1978
1979
1980/**
1981 * Report error according to configured debug_level
1982 *
1983 * @param array Named parameters
1984 * @return void
1985 * @see raise_error()
1986 */
1987function log_bug($arg_arr)
1988{
1989    global $CONFIG;
1990    $program = strtoupper($arg_arr['type']);
1991
1992    // write error to local log file
1993    if ($CONFIG['debug_level'] & 1) {
1994        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1995        $log_entry = sprintf("%s Error: %s%s (%s %s)",
1996            $program,
1997            $arg_arr['message'],
1998            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1999            $_SERVER['REQUEST_METHOD'],
2000            $_SERVER['REQUEST_URI'] . $post_query);
2001
2002        if (!write_log('errors', $log_entry)) {
2003            // send error to PHPs error handler if write_log didn't succeed
2004            trigger_error($arg_arr['message']);
2005        }
2006    }
2007
2008    // resport the bug to the global bug reporting system
2009    if ($CONFIG['debug_level'] & 2) {
2010        // TODO: Send error via HTTP
2011    }
2012
2013    // show error if debug_mode is on
2014    if ($CONFIG['debug_level'] & 4) {
2015        print "<b>$program Error";
2016
2017        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2018            print " in $arg_arr[file] ($arg_arr[line])";
2019
2020        print ':</b>&nbsp;';
2021        print nl2br($arg_arr['message']);
2022        print '<br />';
2023        flush();
2024    }
2025}
2026
Note: See TracBrowser for help on using the repository browser.