source: subversion/trunk/roundcubemail/program/include/main.inc @ 5926

Last change on this file since 5926 was 5926, checked in by alec, 15 months ago
  • Add 'type' and 'attribs' parameters for render_mailboxlist hook
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 61.0 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/main.inc                                              |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
9 |                                                                       |
10 | Licensed under the GNU General Public License version 3 or            |
11 | any later version with exceptions for skins & plugins.                |
12 | See the README file for a full license statement.                     |
13 |                                                                       |
14 | PURPOSE:                                                              |
15 |   Provide basic functions for the webmail package                     |
16 |                                                                       |
17 +-----------------------------------------------------------------------+
18 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
19 +-----------------------------------------------------------------------+
20
21 $Id$
22
23*/
24
25/**
26 * Roundcube Webmail common functions
27 *
28 * @package Core
29 * @author Thomas Bruederli <roundcube@gmail.com>
30 */
31
32require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
33
34// define constannts for input reading
35define('RCUBE_INPUT_GET', 0x0101);
36define('RCUBE_INPUT_POST', 0x0102);
37define('RCUBE_INPUT_GPC', 0x0103);
38
39
40
41/**
42 * Return correct name for a specific database table
43 *
44 * @param string Table name
45 * @return string Translated table name
46 */
47function get_table_name($table)
48  {
49  global $CONFIG;
50
51  // return table name if configured
52  $config_key = 'db_table_'.$table;
53
54  if (strlen($CONFIG[$config_key]))
55    return $CONFIG[$config_key];
56
57  return $table;
58  }
59
60
61/**
62 * Return correct name for a specific database sequence
63 * (used for Postgres only)
64 *
65 * @param string Secuence name
66 * @return string Translated sequence name
67 */
68function get_sequence_name($sequence)
69  {
70  // return sequence name if configured
71  $config_key = 'db_sequence_'.$sequence;
72  $opt = rcmail::get_instance()->config->get($config_key);
73
74  if (!empty($opt))
75    return $opt;
76   
77  return $sequence;
78  }
79
80
81/**
82 * Get localized text in the desired language
83 * It's a global wrapper for rcmail::gettext()
84 *
85 * @param mixed Named parameters array or label name
86 * @param string Domain to search in (e.g. plugin 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 * Global wrapper of rcmail::text_exists()
98 * to check whether a text label is defined
99 *
100 * @see rcmail::text_exists()
101 */
102function rcube_label_exists($name, $domain=null, &$ref_domain = null)
103{
104  return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
105}
106
107
108/**
109 * Overwrite action variable
110 *
111 * @param string New action value
112 */
113function rcmail_overwrite_action($action)
114  {
115  $app = rcmail::get_instance();
116  $app->action = $action;
117  $app->output->set_env('action', $action);
118  }
119
120
121/**
122 * Compose an URL for a specific action
123 *
124 * @param string  Request action
125 * @param array   More URL parameters
126 * @param string  Request task (omit if the same)
127 * @return The application URL
128 */
129function rcmail_url($action, $p=array(), $task=null)
130{
131  $app = rcmail::get_instance();
132  return $app->url((array)$p + array('_action' => $action, 'task' => $task));
133}
134
135
136/**
137 * Garbage collector function for temp files.
138 * Remove temp files older than two days
139 */
140function rcmail_temp_gc()
141{
142  $rcmail = rcmail::get_instance();
143
144  $tmp = unslashify($rcmail->config->get('temp_dir'));
145  $expire = mktime() - 172800;  // expire in 48 hours
146
147  if ($dir = opendir($tmp)) {
148    while (($fname = readdir($dir)) !== false) {
149      if ($fname{0} == '.')
150        continue;
151
152      if (filemtime($tmp.'/'.$fname) < $expire)
153        @unlink($tmp.'/'.$fname);
154    }
155
156    closedir($dir);
157  }
158}
159
160
161// Deprecated
162function rcube_charset_convert($str, $from, $to=NULL)
163{
164    return rcube_charset::convert($str, $from, $to);
165}
166
167
168// Deprecated
169function rc_detect_encoding($string, $failover='')
170{
171    return rcube_charset::detect($string, $failover);
172}
173
174
175// Deprecated
176function rc_utf8_clean($input)
177{
178    return rcube_charset::clean($input);
179}
180
181
182/**
183 * Convert a variable into a javascript object notation
184 *
185 * @param mixed Input value
186 * @return string Serialized JSON string
187 */
188function json_serialize($input)
189{
190    $input = rcube_charset::clean($input);
191
192    // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
193    // that's why we have @ here
194    return @json_encode($input);
195}
196
197
198/**
199 * Replacing specials characters to a specific encoding type
200 *
201 * @param  string  Input string
202 * @param  string  Encoding type: text|html|xml|js|url
203 * @param  string  Replace mode for tags: show|replace|remove
204 * @param  boolean Convert newlines
205 * @return string  The quoted string
206 */
207function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
208  {
209  static $html_encode_arr = false;
210  static $js_rep_table = false;
211  static $xml_rep_table = false;
212
213  if (!$enctype)
214    $enctype = $OUTPUT->type;
215
216  // encode for HTML output
217  if ($enctype=='html')
218    {
219    if (!$html_encode_arr)
220      {
221      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
222      unset($html_encode_arr['?']);
223      }
224
225    $ltpos = strpos($str, '<');
226    $encode_arr = $html_encode_arr;
227
228    // don't replace quotes and html tags
229    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
230      {
231      unset($encode_arr['"']);
232      unset($encode_arr['<']);
233      unset($encode_arr['>']);
234      unset($encode_arr['&']);
235      }
236    else if ($mode=='remove')
237      $str = strip_tags($str);
238
239    $out = strtr($str, $encode_arr);
240
241    // avoid douple quotation of &
242    $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
243
244    return $newlines ? nl2br($out) : $out;
245    }
246
247  // if the replace tables for XML and JS are not yet defined
248  if ($js_rep_table===false)
249    {
250    $js_rep_table = $xml_rep_table = array();
251    $xml_rep_table['&'] = '&amp;';
252
253    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
254      $xml_rep_table[chr($c)] = "&#$c;";
255
256    $xml_rep_table['"'] = '&quot;';
257    $js_rep_table['"'] = '\\"';
258    $js_rep_table["'"] = "\\'";
259    $js_rep_table["\\"] = "\\\\";
260    // Unicode line and paragraph separators (#1486310)
261    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
262    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
263    }
264
265  // encode for javascript use
266  if ($enctype=='js')
267    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
268
269  // encode for plaintext
270  if ($enctype=='text')
271    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
272
273  if ($enctype=='url')
274    return rawurlencode($str);
275
276  // encode for XML
277  if ($enctype=='xml')
278    return strtr($str, $xml_rep_table);
279
280  // no encoding given -> return original string
281  return $str;
282  }
283 
284/**
285 * Quote a given string.
286 * Shortcut function for rep_specialchars_output
287 *
288 * @return string HTML-quoted string
289 * @see rep_specialchars_output()
290 */
291function Q($str, $mode='strict', $newlines=TRUE)
292  {
293  return rep_specialchars_output($str, 'html', $mode, $newlines);
294  }
295
296/**
297 * Quote a given string for javascript output.
298 * Shortcut function for rep_specialchars_output
299 *
300 * @return string JS-quoted string
301 * @see rep_specialchars_output()
302 */
303function JQ($str)
304  {
305  return rep_specialchars_output($str, 'js');
306  }
307
308
309/**
310 * Read input value and convert it for internal use
311 * Performs stripslashes() and charset conversion if necessary
312 *
313 * @param  string   Field name to read
314 * @param  int      Source to get value from (GPC)
315 * @param  boolean  Allow HTML tags in field value
316 * @param  string   Charset to convert into
317 * @return string   Field value or NULL if not available
318 */
319function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
320{
321  $value = NULL;
322
323  if ($source == RCUBE_INPUT_GET) {
324    if (isset($_GET[$fname]))
325      $value = $_GET[$fname];
326  }
327  else if ($source == RCUBE_INPUT_POST) {
328    if (isset($_POST[$fname]))
329      $value = $_POST[$fname];
330  }
331  else if ($source == RCUBE_INPUT_GPC) {
332    if (isset($_POST[$fname]))
333      $value = $_POST[$fname];
334    else if (isset($_GET[$fname]))
335      $value = $_GET[$fname];
336    else if (isset($_COOKIE[$fname]))
337      $value = $_COOKIE[$fname];
338  }
339
340  return parse_input_value($value, $allow_html, $charset);
341}
342
343/**
344 * Parse/validate input value. See get_input_value()
345 * Performs stripslashes() and charset conversion if necessary
346 *
347 * @param  string   Input value
348 * @param  boolean  Allow HTML tags in field value
349 * @param  string   Charset to convert into
350 * @return string   Parsed value
351 */
352function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
353{
354  global $OUTPUT;
355
356  if (empty($value))
357    return $value;
358
359  if (is_array($value)) {
360    foreach ($value as $idx => $val)
361      $value[$idx] = parse_input_value($val, $allow_html, $charset);
362    return $value;
363  }
364
365  // strip single quotes if magic_quotes_sybase is enabled
366  if (ini_get('magic_quotes_sybase'))
367    $value = str_replace("''", "'", $value);
368  // strip slashes if magic_quotes enabled
369  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
370    $value = stripslashes($value);
371
372  // remove HTML tags if not allowed
373  if (!$allow_html)
374    $value = strip_tags($value);
375
376  $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
377
378  // remove invalid characters (#1488124)
379  if ($output_charset == 'UTF-8')
380    $value = rc_utf8_clean($value);
381
382  // convert to internal charset
383  if ($charset && $output_charset)
384    $value = rcube_charset_convert($value, $output_charset, $charset);
385
386  return $value;
387}
388
389/**
390 * Convert array of request parameters (prefixed with _)
391 * to a regular array with non-prefixed keys.
392 *
393 * @param  int   Source to get value from (GPC)
394 * @return array Hash array with all request parameters
395 */
396function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action')
397{
398  $out = array();
399  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
400  foreach ($src as $key => $value) {
401    $fname = $key[0] == '_' ? substr($key, 1) : $key;
402    if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname))
403      $out[$fname] = get_input_value($key, $mode);
404  }
405
406  return $out;
407}
408
409/**
410 * Remove all non-ascii and non-word chars
411 * except ., -, _
412 */
413function asciiwords($str, $css_id = false, $replace_with = '')
414{
415  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
416  return preg_replace("/[^$allowed]/i", $replace_with, $str);
417}
418
419/**
420 * Convert the given string into a valid HTML identifier
421 * Same functionality as done in app.js with rcube_webmail.html_identifier()
422 */
423function html_identifier($str, $encode=false)
424{
425  if ($encode)
426    return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
427  else
428    return asciiwords($str, true, '_');
429}
430
431/**
432 * Remove single and double quotes from given string
433 *
434 * @param string Input value
435 * @return string Dequoted string
436 */
437function strip_quotes($str)
438{
439  return str_replace(array("'", '"'), '', $str);
440}
441
442
443/**
444 * Remove new lines characters from given string
445 *
446 * @param string Input value
447 * @return string Stripped string
448 */
449function strip_newlines($str)
450{
451  return preg_replace('/[\r\n]/', '', $str);
452}
453
454
455/**
456 * Create a HTML table based on the given data
457 *
458 * @param  array  Named table attributes
459 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
460 * @param  array  List of cols to show
461 * @param  string Name of the identifier col
462 * @return string HTML table code
463 */
464function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
465{
466  global $RCMAIL;
467
468  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
469
470  // add table header
471  if (!$attrib['noheader'])
472    foreach ($a_show_cols as $col)
473      $table->add_header($col, Q(rcube_label($col)));
474
475  $c = 0;
476  if (!is_array($table_data))
477  {
478    $db = $RCMAIL->get_dbh();
479    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
480    {
481      $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col])));
482
483      // format each col
484      foreach ($a_show_cols as $col)
485        $table->add($col, Q($sql_arr[$col]));
486
487      $c++;
488    }
489  }
490  else {
491    foreach ($table_data as $row_data)
492    {
493      $class = !empty($row_data['class']) ? $row_data['class'] : '';
494
495      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class));
496
497      // format each col
498      foreach ($a_show_cols as $col)
499        $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
500
501      $c++;
502    }
503  }
504
505  return $table->show($attrib);
506}
507
508
509/**
510 * Create an edit field for inclusion on a form
511 *
512 * @param string col field name
513 * @param string value field value
514 * @param array attrib HTML element attributes for field
515 * @param string type HTML element type (default 'text')
516 * @return string HTML field definition
517 */
518function rcmail_get_edit_field($col, $value, $attrib, $type='text')
519{
520  static $colcounts = array();
521
522  $fname = '_'.$col;
523  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
524  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
525
526  if ($type == 'checkbox') {
527    $attrib['value'] = '1';
528    $input = new html_checkbox($attrib);
529  }
530  else if ($type == 'textarea') {
531    $attrib['cols'] = $attrib['size'];
532    $input = new html_textarea($attrib);
533  }
534  else if ($type == 'select') {
535    $input = new html_select($attrib);
536    $input->add('---', '');
537    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
538  }
539  else if ($attrib['type'] == 'password') {
540    $input = new html_passwordfield($attrib);
541  }
542  else {
543    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
544        $attrib['type'] = 'text';
545    $input = new html_inputfield($attrib);
546  }
547
548  // use value from post
549  if (isset($_POST[$fname])) {
550    $postvalue = get_input_value($fname, RCUBE_INPUT_POST, true);
551    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
552  }
553
554  $out = $input->show($value);
555
556  return $out;
557}
558
559
560/**
561 * Replace all css definitions with #container [def]
562 * and remove css-inlined scripting
563 *
564 * @param string CSS source code
565 * @param string Container ID to use as prefix
566 * @return string Modified CSS source
567 */
568function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
569  {
570  $last_pos = 0;
571  $replacements = new rcube_string_replacer;
572
573  // ignore the whole block if evil styles are detected
574  $source = rcmail_xss_entity_decode($source);
575  $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
576  $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
577  if (preg_match("/$evilexpr/i", $stripped))
578    return '/* evil! */';
579
580  // cut out all contents between { and }
581  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
582    $styles = substr($source, $pos+1, $pos2-($pos+1));
583
584    // check every line of a style block...
585    if ($allow_remote) {
586      $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
587      foreach ($a_styles as $line) {
588        $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
589        // ... and only allow strict url() values
590        if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) {
591          $a_styles = array('/* evil! */');
592          break;
593        }
594      }
595      $styles = join(";\n", $a_styles);
596    }
597
598    $key = $replacements->add($styles);
599    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
600    $last_pos = $pos+2;
601  }
602
603  // remove html comments and add #container to each tag selector.
604  // also replace body definition because we also stripped off the <body> tag
605  $styles = preg_replace(
606    array(
607      '/(^\s*<!--)|(-->\s*$)/',
608      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
609      '/'.preg_quote($container_id, '/').'\s+body/i',
610    ),
611    array(
612      '',
613      "\\1#$container_id \\2",
614      $container_id,
615    ),
616    $source);
617
618  // put block contents back in
619  $styles = $replacements->resolve($styles);
620
621  return $styles;
622  }
623
624
625/**
626 * Decode escaped entities used by known XSS exploits.
627 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
628 *
629 * @param string CSS content to decode
630 * @return string Decoded string
631 */
632function rcmail_xss_entity_decode($content)
633{
634  $out = html_entity_decode(html_entity_decode($content));
635  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
636  $out = preg_replace('#/\*.*\*/#Ums', '', $out);
637  return $out;
638}
639
640
641/**
642 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
643 *
644 * @param array matches result from preg_replace_callback
645 * @return string decoded entity
646 */
647function rcmail_xss_entity_decode_callback($matches)
648{
649  return chr(hexdec($matches[1]));
650}
651
652/**
653 * Compose a valid attribute string for HTML tags
654 *
655 * @param array Named tag attributes
656 * @param array List of allowed attributes
657 * @return string HTML formatted attribute string
658 */
659function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
660  {
661  // allow the following attributes to be added to the <iframe> tag
662  $attrib_str = '';
663  foreach ($allowed_attribs as $a)
664    if (isset($attrib[$a]))
665      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
666
667  return $attrib_str;
668  }
669
670
671/**
672 * Convert a HTML attribute string attributes to an associative array (name => value)
673 *
674 * @param string Input string
675 * @return array Key-value pairs of parsed attributes
676 */
677function parse_attrib_string($str)
678  {
679  $attrib = array();
680  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
681
682  // convert attributes to an associative array (name => value)
683  if ($regs) {
684    foreach ($regs as $attr) {
685      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
686    }
687  }
688
689  return $attrib;
690  }
691
692
693/**
694 * Improved equivalent to strtotime()
695 *
696 * @param string Date string
697 * @return int
698 */
699function rcube_strtotime($date)
700{
701  // check for MS Outlook vCard date format YYYYMMDD
702  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
703    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
704  }
705  else if (is_numeric($date))
706    return $date;
707
708  // support non-standard "GMTXXXX" literal
709  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
710
711  // if date parsing fails, we have a date in non-rfc format.
712  // remove token from the end and try again
713  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
714    $d = explode(' ', $date);
715    array_pop($d);
716    if (!$d) break;
717    $date = implode(' ', $d);
718  }
719
720  return $ts;
721}
722
723
724/**
725 * Convert the given date to a human readable form
726 * This uses the date formatting properties from config
727 *
728 * @param mixed  Date representation (string or timestamp)
729 * @param string Date format to use
730 * @param bool   Enables date convertion according to user timezone
731 *
732 * @return string Formatted date string
733 */
734function format_date($date, $format=NULL, $convert=true)
735{
736  global $RCMAIL, $CONFIG;
737
738  if (!empty($date))
739    $ts = rcube_strtotime($date);
740
741  if (empty($ts))
742    return '';
743
744  try {
745    $date = new DateTime("@".$ts);
746  }
747  catch (Exception $e) {
748    return '';
749  }
750
751  try {
752    // convert to the right timezone
753    $stz = date_default_timezone_get();
754    $tz = new DateTimeZone($convert ? $RCMAIL->config->get('timezone') : 'GMT');
755    $date->setTimezone($tz);
756    date_default_timezone_set($tz->getName());
757
758    $timestamp = $date->format('U');
759  }
760  catch (Exception $e) {
761    $timestamp = $ts;
762  }
763
764  // define date format depending on current time
765  if (!$format) {
766    $now         = time();
767    $now_date    = getdate($now);
768    $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
769    $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
770
771    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
772      $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
773      $today  = true;
774    }
775    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
776      $format = $RCMAIL->config->get('date_short', 'D H:i');
777    else
778      $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
779  }
780
781  // strftime() format
782  if (preg_match('/%[a-z]+/i', $format)) {
783    $format = strftime($format, $timestamp);
784    date_default_timezone_set($stz);
785    return $today ? (rcube_label('today') . ' ' . $format) : $format;
786  }
787
788  // parse format string manually in order to provide localized weekday and month names
789  // an alternative would be to convert the date() format string to fit with strftime()
790  $out = '';
791  for($i=0; $i<strlen($format); $i++) {
792    if ($format[$i]=='\\')  // skip escape chars
793      continue;
794
795    // write char "as-is"
796    if ($format[$i]==' ' || $format{$i-1}=='\\')
797      $out .= $format[$i];
798    // weekday (short)
799    else if ($format[$i]=='D')
800      $out .= rcube_label(strtolower($date->format('D')));
801    // weekday long
802    else if ($format[$i]=='l')
803      $out .= rcube_label(strtolower($date->format('l')));
804    // month name (short)
805    else if ($format[$i]=='M')
806      $out .= rcube_label(strtolower($date->format('M')));
807    // month name (long)
808    else if ($format[$i]=='F')
809      $out .= rcube_label('long'.strtolower($date->format('M')));
810    else if ($format[$i]=='x')
811      $out .= strftime('%x %X', $timestamp);
812    else
813      $out .= $date->format($format[$i]);
814  }
815
816  if ($today) {
817    $label = rcube_label('today');
818    // replcae $ character with "Today" label (#1486120)
819    if (strpos($out, '$') !== false) {
820      $out = preg_replace('/\$/', $label, $out, 1);
821    }
822    else {
823      $out = $label . ' ' . $out;
824    }
825  }
826
827  date_default_timezone_set($stz);
828  return $out;
829}
830
831
832/**
833 * Compose a valid representation of name and e-mail address
834 *
835 * @param string E-mail address
836 * @param string Person name
837 * @return string Formatted string
838 */
839function format_email_recipient($email, $name='')
840{
841  if ($name && $name != $email) {
842    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
843    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
844  }
845
846  return trim($email);
847}
848
849
850/**
851 * Return the mailboxlist in HTML
852 *
853 * @param array Named parameters
854 * @return string HTML code for the gui object
855 */
856function rcmail_mailbox_list($attrib)
857{
858  global $RCMAIL;
859  static $a_mailboxes;
860
861  $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
862
863  // add some labels to client
864  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
865
866  $type = $attrib['type'] ? $attrib['type'] : 'ul';
867  unset($attrib['type']);
868
869  if ($type=='ul' && !$attrib['id'])
870    $attrib['id'] = 'rcmboxlist';
871
872  if (empty($attrib['folder_name']))
873    $attrib['folder_name'] = '*';
874
875  // get mailbox list
876  $mbox_name = $RCMAIL->storage->get_folder();
877
878  // build the folders tree
879  if (empty($a_mailboxes)) {
880    // get mailbox list
881    $a_folders = $RCMAIL->storage->list_folders_subscribed('', $attrib['folder_name'], $attrib['folder_filter']);
882    $delimiter = $RCMAIL->storage->get_hierarchy_delimiter();
883    $a_mailboxes = array();
884
885    foreach ($a_folders as $folder)
886      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
887  }
888
889  // allow plugins to alter the folder tree or to localize folder names
890  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array(
891    'list'      => $a_mailboxes,
892    'delimiter' => $delimiter,
893    'type'      => $type,
894    'attribs'   => $attrib,
895  ));
896
897  $a_mailboxes = $hook['list'];
898  $attrib      = $hook['attribs'];
899
900  if ($type == 'select') {
901    $select = new html_select($attrib);
902
903    // add no-selection option
904    if ($attrib['noselection'])
905      $select->add(rcube_label($attrib['noselection']), '');
906
907    rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
908    $out = $select->show($attrib['default']);
909  }
910  else {
911    $js_mailboxlist = array();
912    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
913
914    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
915    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
916    $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']);
917    $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
918  }
919
920  return $out;
921}
922
923
924/**
925 * Return the mailboxlist as html_select object
926 *
927 * @param array Named parameters
928 * @return html_select HTML drop-down object
929 */
930function rcmail_mailbox_select($p = array())
931{
932  global $RCMAIL;
933
934  $p += array('maxlength' => 100, 'realnames' => false);
935  $a_mailboxes = array();
936  $storage = $RCMAIL->get_storage();
937
938  if (empty($p['folder_name'])) {
939    $p['folder_name'] = '*';
940  }
941
942  if ($p['unsubscribed'])
943    $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
944  else
945    $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
946
947  $delimiter = $storage->get_hierarchy_delimiter();
948
949  foreach ($list as $folder) {
950    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
951      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
952  }
953
954  $select = new html_select($p);
955
956  if ($p['noselection'])
957    $select->add($p['noselection'], '');
958
959  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
960
961  return $select;
962}
963
964
965/**
966 * Create a hierarchical array of the mailbox list
967 * @access private
968 * @return void
969 */
970function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
971{
972  global $RCMAIL;
973
974  // Handle namespace prefix
975  $prefix = '';
976  if (!$path) {
977    $n_folder = $folder;
978    $folder = $RCMAIL->storage->mod_folder($folder);
979
980    if ($n_folder != $folder) {
981      $prefix = substr($n_folder, 0, -strlen($folder));
982    }
983  }
984
985  $pos = strpos($folder, $delm);
986
987  if ($pos !== false) {
988    $subFolders = substr($folder, $pos+1);
989    $currentFolder = substr($folder, 0, $pos);
990
991    // sometimes folder has a delimiter as the last character
992    if (!strlen($subFolders))
993      $virtual = false;
994    else if (!isset($arrFolders[$currentFolder]))
995      $virtual = true;
996    else
997      $virtual = $arrFolders[$currentFolder]['virtual'];
998  }
999  else {
1000    $subFolders = false;
1001    $currentFolder = $folder;
1002    $virtual = false;
1003  }
1004
1005  $path .= $prefix.$currentFolder;
1006
1007  if (!isset($arrFolders[$currentFolder])) {
1008    $arrFolders[$currentFolder] = array(
1009      'id' => $path,
1010      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1011      'virtual' => $virtual,
1012      'folders' => array());
1013  }
1014  else
1015    $arrFolders[$currentFolder]['virtual'] = $virtual;
1016
1017  if (strlen($subFolders))
1018    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1019}
1020
1021
1022/**
1023 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1024 * @access private
1025 * @return string
1026 */
1027function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1028{
1029  global $RCMAIL, $CONFIG;
1030
1031  $maxlength = intval($attrib['maxlength']);
1032  $realnames = (bool)$attrib['realnames'];
1033  $msgcounts = $RCMAIL->storage->get_cache('messagecount');
1034
1035  $out = '';
1036  foreach ($arrFolders as $key => $folder) {
1037    $title        = null;
1038    $folder_class = rcmail_folder_classname($folder['id']);
1039    $collapsed    = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
1040    $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1041
1042    if ($folder_class && !$realnames) {
1043      $foldername = rcube_label($folder_class);
1044    }
1045    else {
1046      $foldername = $folder['name'];
1047
1048      // shorten the folder name to a given length
1049      if ($maxlength && $maxlength > 1) {
1050        $fname = abbreviate_string($foldername, $maxlength);
1051        if ($fname != $foldername)
1052          $title = $foldername;
1053        $foldername = $fname;
1054      }
1055    }
1056
1057    // make folder name safe for ids and class names
1058    $folder_id = html_identifier($folder['id'], true);
1059    $classes = array('mailbox');
1060
1061    // set special class for Sent, Drafts, Trash and Junk
1062    if ($folder_class)
1063      $classes[] = $folder_class;
1064
1065    if ($folder['id'] == $mbox_name)
1066      $classes[] = 'selected';
1067
1068    if ($folder['virtual'])
1069      $classes[] = 'virtual';
1070    else if ($unread)
1071      $classes[] = 'unread';
1072
1073    $js_name = JQ($folder['id']);
1074    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
1075    $link_attrib = $folder['virtual'] ? array() : array(
1076      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1077      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1078      'rel' => $folder['id'],
1079      'title' => $title,
1080    );
1081
1082    $out .= html::tag('li', array(
1083        'id' => "rcmli".$folder_id,
1084        'class' => join(' ', $classes),
1085        'noclose' => true),
1086      html::a($link_attrib, $html_name) .
1087      (!empty($folder['folders']) ? html::div(array(
1088        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1089        'style' => "position:absolute",
1090        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1091      ), '&nbsp;') : ''));
1092
1093    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1094
1095    if (!empty($folder['folders'])) {
1096      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1097        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1098    }
1099
1100    $out .= "</li>\n";
1101  }
1102
1103  return $out;
1104}
1105
1106
1107/**
1108 * Return html for a flat list <select> for the mailbox tree
1109 * @access private
1110 * @return string
1111 */
1112function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array())
1113{
1114  global $RCMAIL;
1115
1116  $out = '';
1117
1118  foreach ($arrFolders as $key => $folder) {
1119    // skip exceptions (and its subfolders)
1120    if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
1121      continue;
1122    }
1123
1124    // skip folders in which it isn't possible to create subfolders
1125    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->storage->folder_attributes($folder['id']))
1126        && in_array('\\Noinferiors', $attrs)
1127    ) {
1128      continue;
1129    }
1130
1131    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1132      $foldername = rcube_label($folder_class);
1133    else {
1134      $foldername = $folder['name'];
1135
1136      // shorten the folder name to a given length
1137      if ($maxlength && $maxlength>1)
1138        $foldername = abbreviate_string($foldername, $maxlength);
1139    }
1140
1141    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1142
1143    if (!empty($folder['folders']))
1144      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
1145        $select, $realnames, $nestLevel+1, $opts);
1146  }
1147
1148  return $out;
1149}
1150
1151
1152/**
1153 * Return internal name for the given folder if it matches the configured special folders
1154 * @access private
1155 * @return string
1156 */
1157function rcmail_folder_classname($folder_id)
1158{
1159  global $CONFIG;
1160
1161  if ($folder_id == 'INBOX')
1162    return 'inbox';
1163
1164  // for these mailboxes we have localized labels and css classes
1165  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1166  {
1167    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1168      return $smbx;
1169  }
1170}
1171
1172
1173/**
1174 * Try to localize the given IMAP folder name.
1175 * UTF-7 decode it in case no localized text was found
1176 *
1177 * @param string Folder name
1178 * @return string Localized folder name in UTF-8 encoding
1179 */
1180function rcmail_localize_foldername($name)
1181{
1182  if ($folder_class = rcmail_folder_classname($name))
1183    return rcube_label($folder_class);
1184  else
1185    return rcube_charset_convert($name, 'UTF7-IMAP');
1186}
1187
1188
1189function rcmail_localize_folderpath($path)
1190{
1191    global $RCMAIL;
1192
1193    $protect_folders = $RCMAIL->config->get('protect_default_folders');
1194    $default_folders = (array) $RCMAIL->config->get('default_folders');
1195    $delimiter       = $RCMAIL->storage->get_hierarchy_delimiter();
1196    $path            = explode($delimiter, $path);
1197    $result          = array();
1198
1199    foreach ($path as $idx => $dir) {
1200        $directory = implode($delimiter, array_slice($path, 0, $idx+1));
1201        if ($protect_folders && in_array($directory, $default_folders)) {
1202            unset($result);
1203            $result[] = rcmail_localize_foldername($directory);
1204        }
1205        else {
1206            $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
1207        }
1208    }
1209
1210    return implode($delimiter, $result);
1211}
1212
1213
1214function rcmail_quota_display($attrib)
1215{
1216  global $OUTPUT;
1217
1218  if (!$attrib['id'])
1219    $attrib['id'] = 'rcmquotadisplay';
1220
1221  $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
1222
1223  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1224
1225  $quota = rcmail_quota_content($attrib);
1226
1227  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1228
1229  return html::span($attrib, '');
1230}
1231
1232
1233function rcmail_quota_content($attrib=NULL)
1234{
1235  global $RCMAIL;
1236
1237  $quota = $RCMAIL->storage->get_quota();
1238  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1239
1240  $quota_result = (array) $quota;
1241  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1242
1243  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1244    $quota_result['title'] = rcube_label('unlimited');
1245    $quota_result['percent'] = 0;
1246  }
1247  else if ($quota['total']) {
1248    if (!isset($quota['percent']))
1249      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1250
1251    $title = sprintf('%s / %s (%.0f%%)',
1252        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1253        $quota_result['percent']);
1254
1255    $quota_result['title'] = $title;
1256
1257    if ($attrib['width'])
1258      $quota_result['width'] = $attrib['width'];
1259    if ($attrib['height'])
1260      $quota_result['height']   = $attrib['height'];
1261  }
1262  else {
1263    $quota_result['title'] = rcube_label('unknown');
1264    $quota_result['percent'] = 0;
1265  }
1266
1267  return $quota_result;
1268}
1269
1270
1271/**
1272 * Outputs error message according to server error/response codes
1273 *
1274 * @param string Fallback message label
1275 * @param string Fallback message label arguments
1276 *
1277 * @return void
1278 */
1279function rcmail_display_server_error($fallback=null, $fallback_args=null)
1280{
1281    global $RCMAIL;
1282
1283    $err_code = $RCMAIL->storage->get_error_code();
1284    $res_code = $RCMAIL->storage->get_response_code();
1285
1286    if ($err_code < 0) {
1287        $RCMAIL->output->show_message('storageerror', 'error');
1288    }
1289    else if ($res_code == rcube_storage::NOPERM) {
1290        $RCMAIL->output->show_message('errornoperm', 'error');
1291    }
1292    else if ($res_code == rcube_storage::READONLY) {
1293        $RCMAIL->output->show_message('errorreadonly', 'error');
1294    }
1295    else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) {
1296        // try to detect access rights problem and display appropriate message
1297        if (stripos($err_str, 'Permission denied') !== false)
1298            $RCMAIL->output->show_message('errornoperm', 'error');
1299        else
1300            $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1301    }
1302    else if ($fallback) {
1303        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1304    }
1305
1306    return true;
1307}
1308
1309
1310/**
1311 * Generate CSS classes from mimetype and filename extension
1312 *
1313 * @param string Mimetype
1314 * @param string The filename
1315 * @return string CSS classes separated by space
1316 */
1317function rcmail_filetype2classname($mimetype, $filename)
1318{
1319  list($primary, $secondary) = explode('/', $mimetype);
1320
1321  $classes = array($primary ? $primary : 'unknown');
1322  if ($secondary) {
1323    $classes[] = $secondary;
1324  }
1325  if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
1326    $classes[] = $m[1];
1327  }
1328
1329  return strtolower(join(" ", $classes));
1330}
1331
1332/**
1333 * Output HTML editor scripts
1334 *
1335 * @param string Editor mode
1336 * @return void
1337 */
1338function rcube_html_editor($mode='')
1339{
1340  global $RCMAIL;
1341
1342  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1343
1344  if ($hook['abort'])
1345    return;
1346
1347  $lang = strtolower($_SESSION['language']);
1348
1349  // TinyMCE uses two-letter lang codes, with exception of Chinese
1350  if (strpos($lang, 'zh_') === 0)
1351    $lang = str_replace('_', '-', $lang);
1352  else
1353    $lang = substr($lang, 0, 2);
1354
1355  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1356    $lang = 'en';
1357
1358  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1359  $RCMAIL->output->include_script('editor.js');
1360  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
1361    json_encode(array(
1362        'mode'       => $mode,
1363        'lang'       => $lang,
1364        'skin_path'  => $RCMAIL->output->get_skin_path(),
1365        'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
1366        'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary')),
1367    ))), 'foot');
1368}
1369
1370
1371/**
1372 * Replaces TinyMCE's emoticon images with plain-text representation
1373 *
1374 * @param string HTML content
1375 * @return string HTML content
1376 */
1377function rcmail_replace_emoticons($html)
1378{
1379  $emoticons = array(
1380    '8-)' => 'smiley-cool',
1381    ':-#' => 'smiley-foot-in-mouth',
1382    ':-*' => 'smiley-kiss',
1383    ':-X' => 'smiley-sealed',
1384    ':-P' => 'smiley-tongue-out',
1385    ':-@' => 'smiley-yell',
1386    ":'(" => 'smiley-cry',
1387    ':-(' => 'smiley-frown',
1388    ':-D' => 'smiley-laughing',
1389    ':-)' => 'smiley-smile',
1390    ':-S' => 'smiley-undecided',
1391    ':-$' => 'smiley-embarassed',
1392    'O:-)' => 'smiley-innocent',
1393    ':-|' => 'smiley-money-mouth',
1394    ':-O' => 'smiley-surprised',
1395    ';-)' => 'smiley-wink',
1396  );
1397
1398  foreach ($emoticons as $idx => $file) {
1399    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1400    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1401    $replace[] = $idx;
1402  }
1403
1404  return preg_replace($search, $replace, $html);
1405}
1406
1407
1408/**
1409 * Send the given message using the configured method
1410 *
1411 * @param object $message    Reference to Mail_MIME object
1412 * @param string $from       Sender address string
1413 * @param array  $mailto     Array of recipient address strings
1414 * @param array  $smtp_error SMTP error array (reference)
1415 * @param string $body_file  Location of file with saved message body (reference),
1416 *                           used when delay_file_io is enabled
1417 * @param array  $smtp_opts  SMTP options (e.g. DSN request)
1418 *
1419 * @return boolean Send status.
1420 */
1421function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
1422{
1423  global $CONFIG, $RCMAIL;
1424
1425  $headers = $message->headers();
1426
1427  // send thru SMTP server using custom SMTP library
1428  if ($CONFIG['smtp_server']) {
1429    // generate list of recipients
1430    $a_recipients = array($mailto);
1431
1432    if (strlen($headers['Cc']))
1433      $a_recipients[] = $headers['Cc'];
1434    if (strlen($headers['Bcc']))
1435      $a_recipients[] = $headers['Bcc'];
1436
1437    // clean Bcc from header for recipients
1438    $send_headers = $headers;
1439    unset($send_headers['Bcc']);
1440    // here too, it because txtHeaders() below use $message->_headers not only $send_headers
1441    unset($message->_headers['Bcc']);
1442
1443    $smtp_headers = $message->txtHeaders($send_headers, true);
1444
1445    if ($message->getParam('delay_file_io')) {
1446      // use common temp dir
1447      $temp_dir = $RCMAIL->config->get('temp_dir');
1448      $body_file = tempnam($temp_dir, 'rcmMsg');
1449      if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
1450        raise_error(array('code' => 650, 'type' => 'php',
1451            'file' => __FILE__, 'line' => __LINE__,
1452            'message' => "Could not create message: ".$mime_result->getMessage()),
1453            TRUE, FALSE);
1454        return false;
1455      }
1456      $msg_body = fopen($body_file, 'r');
1457    } else {
1458      $msg_body = $message->get();
1459    }
1460
1461    // send message
1462    if (!is_object($RCMAIL->smtp))
1463      $RCMAIL->smtp_init(true);
1464
1465    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
1466    $smtp_response = $RCMAIL->smtp->get_response();
1467    $smtp_error = $RCMAIL->smtp->get_error();
1468
1469    // log error
1470    if (!$sent)
1471      raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
1472                        'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
1473  }
1474  // send mail using PHP's mail() function
1475  else {
1476    // unset some headers because they will be added by the mail() function
1477    $headers_enc = $message->headers($headers);
1478    $headers_php = $message->_headers;
1479    unset($headers_php['To'], $headers_php['Subject']);
1480
1481    // reset stored headers and overwrite
1482    $message->_headers = array();
1483    $header_str = $message->txtHeaders($headers_php);
1484
1485    // #1485779
1486    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1487      if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1488        $headers_enc['To'] = implode(', ', $m[1]);
1489      }
1490    }
1491
1492    $msg_body = $message->get();
1493
1494    if (PEAR::isError($msg_body))
1495      raise_error(array('code' => 650, 'type' => 'php',
1496            'file' => __FILE__, 'line' => __LINE__,
1497            'message' => "Could not create message: ".$msg_body->getMessage()),
1498            TRUE, FALSE);
1499    else {
1500      $delim   = $RCMAIL->config->header_delimiter();
1501      $to      = $headers_enc['To'];
1502      $subject = $headers_enc['Subject'];
1503      $header_str = rtrim($header_str);
1504
1505      if ($delim != "\r\n") {
1506        $header_str = str_replace("\r\n", $delim, $header_str);
1507        $msg_body   = str_replace("\r\n", $delim, $msg_body);
1508        $to         = str_replace("\r\n", $delim, $to);
1509        $subject    = str_replace("\r\n", $delim, $subject);
1510      }
1511
1512      if (ini_get('safe_mode'))
1513        $sent = mail($to, $subject, $msg_body, $header_str);
1514      else
1515        $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
1516    }
1517  }
1518
1519  if ($sent) {
1520    $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
1521
1522    // remove MDN headers after sending
1523    unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1524
1525    // get all recipients
1526    if ($headers['Cc'])
1527      $mailto .= $headers['Cc'];
1528    if ($headers['Bcc'])
1529      $mailto .= $headers['Bcc'];
1530    if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
1531      $mailto = implode(', ', array_unique($m[1]));
1532
1533    if ($CONFIG['smtp_log']) {
1534      write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1535        $RCMAIL->user->get_username(),
1536        $_SERVER['REMOTE_ADDR'],
1537        $mailto,
1538        !empty($smtp_response) ? join('; ', $smtp_response) : ''));
1539    }
1540  }
1541
1542  if (is_resource($msg_body)) {
1543    fclose($msg_body);
1544  }
1545
1546  $message->_headers = array();
1547  $message->headers($headers);
1548
1549  return $sent;
1550}
1551
1552
1553// Returns unique Message-ID
1554function rcmail_gen_message_id()
1555{
1556  global $RCMAIL;
1557
1558  $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
1559  $domain_part = $RCMAIL->user->get_username('domain');
1560
1561  // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1562  if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
1563    if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
1564      && preg_match('/\.[a-z]+$/i', $host)) {
1565        $domain_part = $host;
1566    }
1567    else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
1568      && preg_match('/\.[a-z]+$/i', $host)) {
1569        $domain_part = $host;
1570    }
1571  }
1572
1573  return sprintf('<%s@%s>', $local_part, $domain_part);
1574}
1575
1576
1577// Returns RFC2822 formatted current date in user's timezone
1578function rcmail_user_date()
1579{
1580  global $RCMAIL;
1581
1582  // get user's timezone
1583  try {
1584    $tz   = new DateTimeZone($RCMAIL->config->get('timezone'));
1585    $date = new DateTime('now', $tz);
1586  }
1587  catch (Exception $e) {
1588    $date = new DateTime();
1589  }
1590
1591  return $date->format('r');
1592}
1593
1594
1595/**
1596 * Check if we can process not exceeding memory_limit
1597 *
1598 * @param integer Required amount of memory
1599 * @return boolean
1600 */
1601function rcmail_mem_check($need)
1602{
1603  $mem_limit = parse_bytes(ini_get('memory_limit'));
1604  $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
1605
1606  return $mem_limit && $memory + $need > $mem_limit ? false : true;
1607}
1608
1609
1610/**
1611 * Check if working in SSL mode
1612 *
1613 * @param integer HTTPS port number
1614 * @param boolean Enables 'use_https' option checking
1615 * @return boolean
1616 */
1617function rcube_https_check($port=null, $use_https=true)
1618{
1619  global $RCMAIL;
1620
1621  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1622    return true;
1623  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1624    return true;
1625  if ($port && $_SERVER['SERVER_PORT'] == $port)
1626    return true;
1627  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1628    return true;
1629
1630  return false;
1631}
1632
1633
1634/**
1635 * For backward compatibility.
1636 *
1637 * @global rcmail $RCMAIL
1638 * @param string $var_name Variable name.
1639 * @return void
1640 */
1641function rcube_sess_unset($var_name=null)
1642{
1643  global $RCMAIL;
1644
1645  $RCMAIL->session->remove($var_name);
1646}
1647
1648
1649/**
1650 * Replaces hostname variables
1651 *
1652 * @param string $name Hostname
1653 * @param string $host Optional IMAP hostname
1654 * @return string
1655 */
1656function rcube_parse_host($name, $host='')
1657{
1658  // %n - host
1659  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1660  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1661  $d = preg_replace('/^[^\.]+\./', '', $n);
1662  // %h - IMAP host
1663  $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host;
1664  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1665  $z = preg_replace('/^[^\.]+\./', '', $h);
1666  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
1667  if ( strpos($name, '%s') !== false ){
1668    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1669    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1670      return false;
1671  }
1672
1673  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
1674  return $name;
1675}
1676
1677
1678/**
1679 * E-mail address validation
1680 *
1681 * @param string $email Email address
1682 * @param boolean $dns_check True to check dns
1683 * @return boolean
1684 */
1685function check_email($email, $dns_check=true)
1686{
1687  // Check for invalid characters
1688  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1689    return false;
1690
1691  // Check for length limit specified by RFC 5321 (#1486453)
1692  if (strlen($email) > 254)
1693    return false;
1694
1695  $email_array = explode('@', $email);
1696
1697  // Check that there's one @ symbol
1698  if (count($email_array) < 2)
1699    return false;
1700
1701  $domain_part = array_pop($email_array);
1702  $local_part = implode('@', $email_array);
1703
1704  // from PEAR::Validate
1705  $regexp = '&^(?:
1706        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1707        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1708        $&xi';
1709
1710  if (!preg_match($regexp, $local_part))
1711    return false;
1712
1713  // Check domain part
1714  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))
1715    return true; // IP address
1716  else {
1717    // If not an IP address
1718    $domain_array = explode('.', $domain_part);
1719    if (sizeof($domain_array) < 2)
1720      return false; // Not enough parts to be a valid domain
1721
1722    foreach ($domain_array as $part)
1723      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1724        return false;
1725
1726    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1727      return true;
1728
1729    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1730      $lookup = array();
1731      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1732      foreach ($lookup as $line) {
1733        if (strpos($line, 'MX preference'))
1734          return true;
1735      }
1736      return false;
1737    }
1738
1739    // find MX record(s)
1740    if (getmxrr($domain_part, $mx_records))
1741      return true;
1742
1743    // find any DNS record
1744    if (checkdnsrr($domain_part, 'ANY'))
1745      return true;
1746  }
1747
1748  return false;
1749}
1750
1751/*
1752 * Idn_to_ascii wrapper.
1753 * Intl/Idn modules version of this function doesn't work with e-mail address
1754 */
1755function rcube_idn_to_ascii($str)
1756{
1757  return rcube_idn_convert($str, true);
1758}
1759
1760/*
1761 * Idn_to_ascii wrapper.
1762 * Intl/Idn modules version of this function doesn't work with e-mail address
1763 */
1764function rcube_idn_to_utf8($str)
1765{
1766  return rcube_idn_convert($str, false);
1767}
1768
1769function rcube_idn_convert($input, $is_utf=false)
1770{
1771  if ($at = strpos($input, '@')) {
1772    $user   = substr($input, 0, $at);
1773    $domain = substr($input, $at+1);
1774  }
1775  else {
1776    $domain = $input;
1777  }
1778
1779  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1780
1781  if ($domain === false) {
1782    return '';
1783  }
1784
1785  return $at ? $user . '@' . $domain : $domain;
1786}
1787
1788
1789/**
1790 * Helper class to turn relative urls into absolute ones
1791 * using a predefined base
1792 */
1793class rcube_base_replacer
1794{
1795  private $base_url;
1796
1797  public function __construct($base)
1798  {
1799    $this->base_url = $base;
1800  }
1801
1802  public function callback($matches)
1803  {
1804    return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
1805  }
1806
1807  public function replace($body)
1808  {
1809    return preg_replace_callback(array(
1810      '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
1811      '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
1812      ),
1813      array($this, 'callback'), $body);
1814  }
1815
1816  /**
1817   * Convert paths like ../xxx to an absolute path using a base url
1818   *
1819   * @param string $path     Relative path
1820   * @param string $base_url Base URL
1821   *
1822   * @return string Absolute URL
1823   */
1824  public static function absolute_url($path, $base_url)
1825  {
1826    $host_url = $base_url;
1827    $abs_path = $path;
1828
1829    // check if path is an absolute URL
1830    if (preg_match('/^[fhtps]+:\/\//', $path)) {
1831      return $path;
1832    }
1833
1834    // check if path is a content-id scheme
1835    if (strpos($path, 'cid:') === 0) {
1836      return $path;
1837    }
1838
1839    // cut base_url to the last directory
1840    if (strrpos($base_url, '/') > 7) {
1841      $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
1842      $base_url = substr($base_url, 0, strrpos($base_url, '/'));
1843    }
1844
1845    // $path is absolute
1846    if ($path[0] == '/') {
1847      $abs_path = $host_url.$path;
1848    }
1849    else {
1850      // strip './' because its the same as ''
1851      $path = preg_replace('/^\.\//', '', $path);
1852
1853      if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
1854        foreach ($matches as $a_match) {
1855          if (strrpos($base_url, '/')) {
1856            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
1857          }
1858          $path = substr($path, 3);
1859        }
1860      }
1861
1862      $abs_path = $base_url.'/'.$path;
1863    }
1864
1865    return $abs_path;
1866  }
1867}
1868
1869
1870/****** debugging and logging functions ********/
1871
1872/**
1873 * Print or write debug messages
1874 *
1875 * @param mixed Debug message or data
1876 * @return void
1877 */
1878function console()
1879{
1880    $args = func_get_args();
1881
1882    if (class_exists('rcmail', false)) {
1883        $rcmail = rcmail::get_instance();
1884        if (is_object($rcmail->plugins)) {
1885            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
1886            if ($plugin['abort'])
1887                return;
1888            $args = $plugin['args'];
1889        }
1890    }
1891
1892    $msg = array();
1893    foreach ($args as $arg)
1894        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1895
1896    write_log('console', join(";\n", $msg));
1897}
1898
1899
1900/**
1901 * Append a line to a logfile in the logs directory.
1902 * Date will be added automatically to the line.
1903 *
1904 * @param $name name of log file
1905 * @param line Line to append
1906 * @return void
1907 */
1908function write_log($name, $line)
1909{
1910  global $CONFIG, $RCMAIL;
1911
1912  if (!is_string($line))
1913    $line = var_export($line, true);
1914 
1915  if (empty($CONFIG['log_date_format']))
1916    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1917 
1918  $date = date($CONFIG['log_date_format']);
1919 
1920  // trigger logging hook
1921  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1922    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1923    $name = $log['name'];
1924    $line = $log['line'];
1925    $date = $log['date'];
1926    if ($log['abort'])
1927      return true;
1928  }
1929 
1930  if ($CONFIG['log_driver'] == 'syslog') {
1931    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1932    syslog($prio, $line);
1933    return true;
1934  }
1935  else {
1936    $line = sprintf("[%s]: %s\n", $date, $line);
1937
1938    // log_driver == 'file' is assumed here
1939    if (empty($CONFIG['log_dir']))
1940      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1941
1942    // try to open specific log file for writing
1943    $logfile = $CONFIG['log_dir'].'/'.$name;
1944    if ($fp = @fopen($logfile, 'a')) {
1945      fwrite($fp, $line);
1946      fflush($fp);
1947      fclose($fp);
1948      return true;
1949    }
1950    else
1951      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1952  }
1953
1954  return false;
1955}
1956
1957
1958/**
1959 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1960 *
1961 * @return void
1962 */
1963function rcmail_log_login()
1964{
1965  global $RCMAIL;
1966
1967  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1968    return;
1969
1970  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
1971    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
1972}
1973
1974
1975/**
1976 * Returns remote IP address and forwarded addresses if found
1977 *
1978 * @return string Remote IP address(es)
1979 */
1980function rcmail_remote_ip()
1981{
1982    $address = $_SERVER['REMOTE_ADDR'];
1983
1984    // append the NGINX X-Real-IP header, if set
1985    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1986        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1987    }
1988    // append the X-Forwarded-For header, if set
1989    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1990        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1991    }
1992
1993    if (!empty($remote_ip))
1994        $address .= '(' . implode(',', $remote_ip) . ')';
1995
1996    return $address;
1997}
1998
1999
2000/**
2001 * Check whether the HTTP referer matches the current request
2002 *
2003 * @return boolean True if referer is the same host+path, false if not
2004 */
2005function rcube_check_referer()
2006{
2007  $uri = parse_url($_SERVER['REQUEST_URI']);
2008  $referer = parse_url(rc_request_header('Referer'));
2009  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
2010}
2011
2012
2013/**
2014 * @access private
2015 * @return mixed
2016 */
2017function rcube_timer()
2018{
2019  return microtime(true);
2020}
2021
2022
2023/**
2024 * @access private
2025 * @return void
2026 */
2027function rcube_print_time($timer, $label='Timer', $dest='console')
2028{
2029  static $print_count = 0;
2030
2031  $print_count++;
2032  $now = rcube_timer();
2033  $diff = $now-$timer;
2034
2035  if (empty($label))
2036    $label = 'Timer '.$print_count;
2037
2038  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
2039}
2040
2041
2042/**
2043 * Throw system error and show error page
2044 *
2045 * @param array Named parameters
2046 *  - code: Error code (required)
2047 *  - type: Error type [php|db|imap|javascript] (required)
2048 *  - message: Error message
2049 *  - file: File where error occured
2050 *  - line: Line where error occured
2051 * @param boolean True to log the error
2052 * @param boolean Terminate script execution
2053 */
2054// may be defined in Installer
2055if (!function_exists('raise_error')) {
2056function raise_error($arg=array(), $log=false, $terminate=false)
2057{
2058    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2059
2060    // report bug (if not incompatible browser)
2061    if ($log && $arg['type'] && $arg['message'])
2062        rcube_log_bug($arg);
2063
2064    // display error page and terminate script
2065    if ($terminate) {
2066        $ERROR_CODE = $arg['code'];
2067        $ERROR_MESSAGE = $arg['message'];
2068        include INSTALL_PATH . 'program/steps/utils/error.inc';
2069        exit;
2070    }
2071}
2072}
2073
2074
2075/**
2076 * Report error according to configured debug_level
2077 *
2078 * @param array Named parameters
2079 * @return void
2080 * @see raise_error()
2081 */
2082function rcube_log_bug($arg_arr)
2083{
2084    global $CONFIG;
2085
2086    $program = strtoupper($arg_arr['type']);
2087    $level   = $CONFIG['debug_level'];
2088
2089    // disable errors for ajax requests, write to log instead (#1487831)
2090    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2091        $level = ($level ^ 4) | 1;
2092    }
2093
2094    // write error to local log file
2095    if ($level & 1) {
2096        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2097        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2098            $program,
2099            $arg_arr['message'],
2100            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2101            $_SERVER['REQUEST_METHOD'],
2102            $_SERVER['REQUEST_URI'] . $post_query);
2103
2104        if (!write_log('errors', $log_entry)) {
2105            // send error to PHPs error handler if write_log didn't succeed
2106            trigger_error($arg_arr['message']);
2107        }
2108    }
2109
2110    // report the bug to the global bug reporting system
2111    if ($level & 2) {
2112        // TODO: Send error via HTTP
2113    }
2114
2115    // show error if debug_mode is on
2116    if ($level & 4) {
2117        print "<b>$program Error";
2118
2119        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2120            print " in $arg_arr[file] ($arg_arr[line])";
2121
2122        print ':</b>&nbsp;';
2123        print nl2br($arg_arr['message']);
2124        print '<br />';
2125        flush();
2126    }
2127}
2128
2129function rcube_upload_progress()
2130{
2131    global $RCMAIL;
2132
2133    $prefix = ini_get('apc.rfc1867_prefix');
2134    $params = array(
2135        'action' => $RCMAIL->action,
2136        'name' => get_input_value('_progress', RCUBE_INPUT_GET),
2137    );
2138
2139    if (function_exists('apc_fetch')) {
2140        $status = apc_fetch($prefix . $params['name']);
2141
2142        if (!empty($status)) {
2143            $status['percent'] = round($status['current']/$status['total']*100);
2144            $params = array_merge($status, $params);
2145        }
2146    }
2147
2148    if (isset($params['percent']))
2149        $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
2150            'percent' => $params['percent'] . '%',
2151            'current' => show_bytes($params['current']),
2152            'total'   => show_bytes($params['total'])
2153        )));
2154
2155    $RCMAIL->output->command('upload_progress_update', $params);
2156    $RCMAIL->output->send();
2157}
2158
2159function rcube_upload_init()
2160{
2161    global $RCMAIL;
2162
2163    // Enable upload progress bar
2164    if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
2165        if ($field_name = ini_get('apc.rfc1867_name')) {
2166            $RCMAIL->output->set_env('upload_progress_name', $field_name);
2167            $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
2168        }
2169    }
2170
2171    // find max filesize value
2172    $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
2173    $max_postsize = parse_bytes(ini_get('post_max_size'));
2174    if ($max_postsize && $max_postsize < $max_filesize)
2175        $max_filesize = $max_postsize;
2176
2177    $RCMAIL->output->set_env('max_filesize', $max_filesize);
2178    $max_filesize = show_bytes($max_filesize);
2179    $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
2180        'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
2181
2182    return $max_filesize;
2183}
2184
2185/**
2186 * Initializes client-side autocompletion
2187 */
2188function rcube_autocomplete_init()
2189{
2190    global $RCMAIL;
2191    static $init;
2192
2193    if ($init)
2194        return;
2195
2196    $init = 1;
2197
2198    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
2199      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
2200      if (count($book_types) > 1) {
2201        $RCMAIL->output->set_env('autocomplete_threads', $threads);
2202        $RCMAIL->output->set_env('autocomplete_sources', $book_types);
2203      }
2204    }
2205
2206    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
2207    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
2208    $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
2209}
2210
2211function rcube_fontdefs($font = null)
2212{
2213  $fonts = array(
2214    'Andale Mono'   => '"Andale Mono",Times,monospace',
2215    'Arial'         => 'Arial,Helvetica,sans-serif',
2216    'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
2217    'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
2218    'Courier New'   => '"Courier New",Courier,monospace',
2219    'Georgia'       => 'Georgia,Palatino,serif',
2220    'Helvetica'     => 'Helvetica,Arial,sans-serif',
2221    'Impact'        => 'Impact,Chicago,sans-serif',
2222    'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
2223    'Terminal'      => 'Terminal,Monaco,monospace',
2224    'Times New Roman' => '"Times New Roman",Times,serif',
2225    'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
2226    'Verdana'       => 'Verdana,Geneva,sans-serif',
2227  );
2228
2229  if ($font)
2230    return $fonts[$font];
2231
2232  return $fonts;
2233}
Note: See TracBrowser for help on using the repository browser.