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

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