source: github/program/include/main.inc @ 044d664

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

Make it easier to add code to $(document).ready()

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