source: github/program/include/main.inc @ a5a4bf4

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