4 +-----------------------------------------------------------------------+
5 | program/include/main.inc |
7 | This file is part of the Roundcube Webmail client |
8 | Copyright (C) 2005-2009, Roundcube Dev, - Switzerland |
9 | Licensed under the GNU GPL |
12 | Provide basic functions for the webmail package |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
16 +-----------------------------------------------------------------------+
18 $Id: main.inc 4830 2011-06-02 12:36:32Z alec $
23 * Roundcube Webmail common functions
26 * @author Thomas Bruederli <roundcube@gmail.com>
29 require_once('lib/utf7.inc');
30 require_once('include/rcube_shared.inc');
32 // define constannts for input reading
33 define('RCUBE_INPUT_GET', 0x0101);
34 define('RCUBE_INPUT_POST', 0x0102);
35 define('RCUBE_INPUT_GPC', 0x0103);
40 * Return correct name for a specific database table
42 * @param string Table name
43 * @return string Translated table name
45 function get_table_name($table)
49 // return table name if configured
50 $config_key = 'db_table_'.$table;
52 if (strlen($CONFIG[$config_key]))
53 return $CONFIG[$config_key];
60 * Return correct name for a specific database sequence
61 * (used for Postgres only)
63 * @param string Secuence name
64 * @return string Translated sequence name
66 function get_sequence_name($sequence)
68 // return sequence name if configured
69 $config_key = 'db_sequence_'.$sequence;
70 $opt = rcmail::get_instance()->config->get($config_key);
80 * Get localized text in the desired language
81 * It's a global wrapper for rcmail::gettext()
83 * @param mixed Named parameters array or label name
84 * @return string Localized text
85 * @see rcmail::gettext()
87 function rcube_label($p, $domain=null)
89 return rcmail::get_instance()->gettext($p, $domain);
93 * Global wrapper of rcmail::text_exists()
94 * to check whether a text label is defined
96 * @see rcmail::text_exists()
98 function rcube_label_exists($name, $domain=null)
100 return rcmail::get_instance()->text_exists($name, $domain);
104 * Overwrite action variable
106 * @param string New action value
108 function rcmail_overwrite_action($action)
110 $app = rcmail::get_instance();
111 $app->action = $action;
112 $app->output->set_env('action', $action);
117 * Compose an URL for a specific action
119 * @param string Request action
120 * @param array More URL parameters
121 * @param string Request task (omit if the same)
122 * @return The application URL
124 function rcmail_url($action, $p=array(), $task=null)
126 $app = rcmail::get_instance();
127 return $app->url((array)$p + array('_action' => $action, 'task' => $task));
132 * Garbage collector function for temp files.
133 * Remove temp files older than two days
135 function rcmail_temp_gc()
137 $rcmail = rcmail::get_instance();
139 $tmp = unslashify($rcmail->config->get('temp_dir'));
140 $expire = mktime() - 172800; // expire in 48 hours
142 if ($dir = opendir($tmp))
144 while (($fname = readdir($dir)) !== false)
146 if ($fname{0} == '.')
149 if (filemtime($tmp.'/'.$fname) < $expire)
150 @unlink($tmp.'/'.$fname);
159 * Garbage collector for cache entries.
160 * Remove all expired message cache records
163 function rcmail_cache_gc()
165 $rcmail = rcmail::get_instance();
166 $db = $rcmail->get_dbh();
168 // get target timestamp
169 $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
171 $db->query("DELETE FROM ".get_table_name('messages')."
172 WHERE created < " . $db->fromunixtime($ts));
174 $db->query("DELETE FROM ".get_table_name('cache')."
175 WHERE created < " . $db->fromunixtime($ts));
180 * Catch an error and throw an exception.
182 * @param int Level of the error
183 * @param string Error message
185 function rcube_error_handler($errno, $errstr)
187 throw new ErrorException($errstr, 0, $errno);
192 * Convert a string from one charset to another.
193 * Uses mbstring and iconv functions if possible
195 * @param string Input string
196 * @param string Suspected charset of the input string
197 * @param string Target charset to convert to; defaults to RCMAIL_CHARSET
198 * @return string Converted string
200 function rcube_charset_convert($str, $from, $to=NULL)
202 static $iconv_options = null;
203 static $mbstring_loaded = null;
204 static $mbstring_list = null;
205 static $convert_warning = false;
210 $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
211 $from = rcube_parse_charset($from);
213 if ($from == $to || empty($str) || empty($from))
216 // convert charset using iconv module
217 if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
218 if ($iconv_options === null) {
219 // ignore characters not available in output charset
220 $iconv_options = '//IGNORE';
221 if (iconv('', $iconv_options, '') === false) {
222 // iconv implementation does not support options
227 // throw an exception if iconv reports an illegal character in input
228 // it means that input string has been truncated
229 set_error_handler('rcube_error_handler', E_NOTICE);
231 $_iconv = iconv($from, $to . $iconv_options, $str);
232 } catch (ErrorException $e) {
235 restore_error_handler();
236 if ($_iconv !== false) {
241 if ($mbstring_loaded === null)
242 $mbstring_loaded = extension_loaded('mbstring');
244 // convert charset using mbstring module
245 if ($mbstring_loaded) {
246 $aliases['WINDOWS-1257'] = 'ISO-8859-13';
248 if ($mbstring_list === null) {
249 $mbstring_list = mb_list_encodings();
250 $mbstring_list = array_map('strtoupper', $mbstring_list);
253 $mb_from = $aliases[$from] ? $aliases[$from] : $from;
254 $mb_to = $aliases[$to] ? $aliases[$to] : $to;
256 // return if encoding found, string matches encoding and convert succeeded
257 if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
258 if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
263 // convert charset using bundled classes/functions
264 if ($to == 'UTF-8') {
265 if ($from == 'UTF7-IMAP') {
266 if ($_str = utf7_to_utf8($str))
269 else if ($from == 'UTF-7') {
270 if ($_str = rcube_utf7_to_utf8($str))
273 else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
274 return utf8_encode($str);
276 else if (class_exists('utf8')) {
278 $conv = new utf8($from);
280 $conv->loadCharset($from);
282 if($_str = $conv->strToUtf8($str))
288 // encode string for output
289 if ($from == 'UTF-8') {
290 // @TODO: we need a function for UTF-7 (RFC2152) conversion
291 if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
292 if ($_str = utf8_to_utf7($str))
295 else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
296 return utf8_decode($str);
298 else if (class_exists('utf8')) {
300 $conv = new utf8($to);
302 $conv->loadCharset($from);
304 if ($_str = $conv->strToUtf8($str))
311 if ($error && !$convert_warning) {
317 'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
320 $convert_warning = true;
323 // return UTF-8 or original string
329 * Parse and validate charset name string (see #1485758).
330 * Sometimes charset string is malformed, there are also charset aliases
331 * but we need strict names for charset conversion (specially utf8 class)
333 * @param string Input charset name
334 * @return string The validated charset name
336 function rcube_parse_charset($input)
338 static $charsets = array();
339 $charset = strtoupper($input);
341 if (isset($charsets[$input]))
342 return $charsets[$input];
344 $charset = preg_replace(array(
345 '/^[^0-9A-Z]+/', // e.g. _ISO-8859-JP$SIO
346 '/\$.*$/', // e.g. _ISO-8859-JP$SIO
347 '/UNICODE-1-1-*/', // RFC1641/1642
348 '/^X-/', // X- prefix (e.g. X-ROMAN8 => ROMAN8)
351 if ($charset == 'BINARY')
352 return $charsets[$input] = null;
354 # Aliases: some of them from HTML5 spec.
356 'USASCII' => 'WINDOWS-1252',
357 'ANSIX31101983' => 'WINDOWS-1252',
358 'ANSIX341968' => 'WINDOWS-1252',
359 'UNKNOWN8BIT' => 'ISO-8859-15',
360 'UNKNOWN' => 'ISO-8859-15',
361 'USERDEFINED' => 'ISO-8859-15',
362 'KSC56011987' => 'EUC-KR',
365 'UNICODE' => 'UTF-8',
366 'UTF7IMAP' => 'UTF7-IMAP',
367 'TIS620' => 'WINDOWS-874',
368 'ISO88599' => 'WINDOWS-1254',
369 'ISO885911' => 'WINDOWS-874',
370 'MACROMAN' => 'MACINTOSH',
372 '128' => 'SHIFT-JIS',
377 '161' => 'WINDOWS-1253',
378 '162' => 'WINDOWS-1254',
379 '163' => 'WINDOWS-1258',
380 '177' => 'WINDOWS-1255',
381 '178' => 'WINDOWS-1256',
382 '186' => 'WINDOWS-1257',
383 '204' => 'WINDOWS-1251',
384 '222' => 'WINDOWS-874',
385 '238' => 'WINDOWS-1250',
387 'WINDOWS949' => 'UHC',
390 // allow A-Z and 0-9 only
391 $str = preg_replace('/[^A-Z0-9]/', '', $charset);
393 if (isset($aliases[$str]))
394 $result = $aliases[$str];
396 else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
397 $result = 'UTF-' . $m[1] . $m[2];
399 else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
400 $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
401 // some clients sends windows-1252 text as latin1,
402 // it is safe to use windows-1252 for all latin1
403 $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
405 // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
406 else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
407 $result = 'WINDOWS-' . $m[2];
410 else if (preg_match('/LATIN(.*)/', $str, $m)) {
411 $aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
412 '7' => 13, '8' => 14, '9' => 15, '10' => 16,
413 'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
415 // some clients sends windows-1252 text as latin1,
416 // it is safe to use windows-1252 for all latin1
418 $result = 'WINDOWS-1252';
420 // if iconv is not supported we need ISO labels, it's also safe for iconv
421 else if (!empty($aliases[$m[1]])) {
422 $result = 'ISO-8859-'.$aliases[$m[1]];
424 // iconv requires convertion of e.g. LATIN-1 to LATIN1
433 $charsets[$input] = $result;
440 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
442 * @param string Input string
443 * @return string The converted string
445 function rcube_utf7_to_utf8($str)
448 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
449 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
450 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
451 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
452 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
453 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
454 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
455 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
458 $u7len = strlen($str);
462 for ($i=0; $u7len > 0; $i++, $u7len--)
471 for (; $u7len > 0; $i++, $u7len--)
475 if (!$Index_64[ord($u7)])
487 $res .= rcube_utf16_to_utf8(base64_decode($ch));
499 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
501 * @param string Input string
502 * @return string The converted string
504 function rcube_utf16_to_utf8($str)
509 for ($i = 0; $i < $len; $i += 2) {
510 $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
511 if ($c >= 0x0001 && $c <= 0x007F) {
513 } else if ($c > 0x07FF) {
514 $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
515 $dec .= chr(0x80 | (($c >> 6) & 0x3F));
516 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
518 $dec .= chr(0xC0 | (($c >> 6) & 0x1F));
519 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
527 * Replacing specials characters to a specific encoding type
529 * @param string Input string
530 * @param string Encoding type: text|html|xml|js|url
531 * @param string Replace mode for tags: show|replace|remove
532 * @param boolean Convert newlines
533 * @return string The quoted string
535 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
537 static $html_encode_arr = false;
538 static $js_rep_table = false;
539 static $xml_rep_table = false;
542 $enctype = $OUTPUT->type;
544 // encode for HTML output
545 if ($enctype=='html')
547 if (!$html_encode_arr)
549 $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
550 unset($html_encode_arr['?']);
553 $ltpos = strpos($str, '<');
554 $encode_arr = $html_encode_arr;
556 // don't replace quotes and html tags
557 if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
559 unset($encode_arr['"']);
560 unset($encode_arr['<']);
561 unset($encode_arr['>']);
562 unset($encode_arr['&']);
564 else if ($mode=='remove')
565 $str = strip_tags($str);
567 $out = strtr($str, $encode_arr);
569 // avoid douple quotation of &
570 $out = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
572 return $newlines ? nl2br($out) : $out;
575 // if the replace tables for XML and JS are not yet defined
576 if ($js_rep_table===false)
578 $js_rep_table = $xml_rep_table = array();
579 $xml_rep_table['&'] = '&';
581 for ($c=160; $c<256; $c++) // can be increased to support more charsets
582 $xml_rep_table[chr($c)] = "&#$c;";
584 $xml_rep_table['"'] = '"';
585 $js_rep_table['"'] = '\\"';
586 $js_rep_table["'"] = "\\'";
587 $js_rep_table["\\"] = "\\\\";
588 // Unicode line and paragraph separators (#1486310)
589 $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '
';
590 $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '
';
593 // encode for javascript use
595 return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
597 // encode for plaintext
598 if ($enctype=='text')
599 return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
602 return rawurlencode($str);
606 return strtr($str, $xml_rep_table);
608 // no encoding given -> return original string
613 * Quote a given string.
614 * Shortcut function for rep_specialchars_output
616 * @return string HTML-quoted string
617 * @see rep_specialchars_output()
619 function Q($str, $mode='strict', $newlines=TRUE)
621 return rep_specialchars_output($str, 'html', $mode, $newlines);
625 * Quote a given string for javascript output.
626 * Shortcut function for rep_specialchars_output
628 * @return string JS-quoted string
629 * @see rep_specialchars_output()
633 return rep_specialchars_output($str, 'js');
638 * Read input value and convert it for internal use
639 * Performs stripslashes() and charset conversion if necessary
641 * @param string Field name to read
642 * @param int Source to get value from (GPC)
643 * @param boolean Allow HTML tags in field value
644 * @param string Charset to convert into
645 * @return string Field value or NULL if not available
647 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
651 if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
652 $value = $_GET[$fname];
653 else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
654 $value = $_POST[$fname];
655 else if ($source==RCUBE_INPUT_GPC)
657 if (isset($_POST[$fname]))
658 $value = $_POST[$fname];
659 else if (isset($_GET[$fname]))
660 $value = $_GET[$fname];
661 else if (isset($_COOKIE[$fname]))
662 $value = $_COOKIE[$fname];
665 return parse_input_value($value, $allow_html, $charset);
669 * Parse/validate input value. See get_input_value()
670 * Performs stripslashes() and charset conversion if necessary
672 * @param string Input value
673 * @param boolean Allow HTML tags in field value
674 * @param string Charset to convert into
675 * @return string Parsed value
677 function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
684 if (is_array($value)) {
685 foreach ($value as $idx => $val)
686 $value[$idx] = parse_input_value($val, $allow_html, $charset);
690 // strip single quotes if magic_quotes_sybase is enabled
691 if (ini_get('magic_quotes_sybase'))
692 $value = str_replace("''", "'", $value);
693 // strip slashes if magic_quotes enabled
694 else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
695 $value = stripslashes($value);
697 // remove HTML tags if not allowed
699 $value = strip_tags($value);
701 // convert to internal charset
702 if (is_object($OUTPUT) && $charset)
703 return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
709 * Convert array of request parameters (prefixed with _)
710 * to a regular array with non-prefixed keys.
712 * @param int Source to get value from (GPC)
713 * @return array Hash array with all request parameters
715 function request2param($mode = RCUBE_INPUT_GPC)
718 $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
719 foreach ($src as $key => $value) {
720 $fname = $key[0] == '_' ? substr($key, 1) : $key;
721 $out[$fname] = get_input_value($key, $mode);
728 * Remove all non-ascii and non-word chars
731 function asciiwords($str, $css_id = false, $replace_with = '')
733 $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
734 return preg_replace("/[^$allowed]/i", $replace_with, $str);
738 * Remove single and double quotes from given string
740 * @param string Input value
741 * @return string Dequoted string
743 function strip_quotes($str)
745 return str_replace(array("'", '"'), '', $str);
750 * Remove new lines characters from given string
752 * @param string Input value
753 * @return string Stripped string
755 function strip_newlines($str)
757 return preg_replace('/[\r\n]/', '', $str);
762 * Create a HTML table based on the given data
764 * @param array Named table attributes
765 * @param mixed Table row data. Either a two-dimensional array or a valid SQL result set
766 * @param array List of cols to show
767 * @param string Name of the identifier col
768 * @return string HTML table code
770 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
774 $table = new html_table(/*array('cols' => count($a_show_cols))*/);
777 if (!$attrib['noheader'])
778 foreach ($a_show_cols as $col)
779 $table->add_header($col, Q(rcube_label($col)));
782 if (!is_array($table_data))
784 $db = $RCMAIL->get_dbh();
785 while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
787 $zebra_class = $c % 2 ? 'even' : 'odd';
788 $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
791 foreach ($a_show_cols as $col)
792 $table->add($col, Q($sql_arr[$col]));
799 foreach ($table_data as $row_data)
801 $zebra_class = $c % 2 ? 'even' : 'odd';
802 if (!empty($row_data['class']))
803 $zebra_class .= ' '.$row_data['class'];
805 $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
808 foreach ($a_show_cols as $col)
809 $table->add($col, Q($row_data[$col]));
815 return $table->show($attrib);
820 * Create an edit field for inclusion on a form
822 * @param string col field name
823 * @param string value field value
824 * @param array attrib HTML element attributes for field
825 * @param string type HTML element type (default 'text')
826 * @return string HTML field definition
828 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
831 $attrib['name'] = $fname;
833 if ($type=='checkbox')
835 $attrib['value'] = '1';
836 $input = new html_checkbox($attrib);
838 else if ($type=='textarea')
840 $attrib['cols'] = $attrib['size'];
841 $input = new html_textarea($attrib);
844 $input = new html_inputfield($attrib);
846 // use value from post
847 if (!empty($_POST[$fname]))
848 $value = get_input_value($fname, RCUBE_INPUT_POST,
849 $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
851 $out = $input->show($value);
858 * Replace all css definitions with #container [def]
859 * and remove css-inlined scripting
861 * @param string CSS source code
862 * @param string Container ID to use as prefix
863 * @return string Modified CSS source
865 function rcmail_mod_css_styles($source, $container_id)
868 $replacements = new rcube_string_replacer;
870 // ignore the whole block if evil styles are detected
871 $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
872 if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
873 return '/* evil! */';
875 // remove css comments (sometimes used for some ugly hacks)
876 $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
878 // cut out all contents between { and }
879 while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
881 $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
882 $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
886 // remove html comments and add #container to each tag selector.
887 // also replace body definition because we also stripped off the <body> tag
888 $styles = preg_replace(
890 '/(^\s*<!--)|(-->\s*$)/',
891 '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
892 '/'.preg_quote($container_id, '/').'\s+body/i',
896 "\\1#$container_id \\2",
901 // put block contents back in
902 $styles = $replacements->resolve($styles);
909 * Decode escaped entities used by known XSS exploits.
910 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
912 * @param string CSS content to decode
913 * @return string Decoded string
915 function rcmail_xss_entity_decode($content)
917 $out = html_entity_decode(html_entity_decode($content));
918 $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
919 $out = preg_replace('#/\*.*\*/#Um', '', $out);
925 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
927 * @param array matches result from preg_replace_callback
928 * @return string decoded entity
930 function rcmail_xss_entity_decode_callback($matches)
932 return chr(hexdec($matches[1]));
936 * Compose a valid attribute string for HTML tags
938 * @param array Named tag attributes
939 * @param array List of allowed attributes
940 * @return string HTML formatted attribute string
942 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
944 // allow the following attributes to be added to the <iframe> tag
946 foreach ($allowed_attribs as $a)
947 if (isset($attrib[$a]))
948 $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '"', $attrib[$a]));
955 * Convert a HTML attribute string attributes to an associative array (name => value)
957 * @param string Input string
958 * @return array Key-value pairs of parsed attributes
960 function parse_attrib_string($str)
963 preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
965 // convert attributes to an associative array (name => value)
967 foreach ($regs as $attr) {
968 $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
977 * Convert the given date to a human readable form
978 * This uses the date formatting properties from config
980 * @param mixed Date representation (string or timestamp)
981 * @param string Date format to use
982 * @return string Formatted date string
984 function format_date($date, $format=NULL)
990 if (is_numeric($date))
992 else if (!empty($date))
994 // support non-standard "GMTXXXX" literal
995 $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
996 // if date parsing fails, we have a date in non-rfc format.
997 // remove token from the end and try again
998 while ((($ts = @strtotime($date))===false) || ($ts < 0))
1000 $d = explode(' ', $date);
1003 $date = implode(' ', $d);
1010 // get user's timezone
1011 if ($CONFIG['timezone'] === 'auto')
1012 $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1014 $tz = $CONFIG['timezone'];
1015 if ($CONFIG['dst_active'])
1019 // convert time to user's timezone
1020 $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1022 // get current timestamp in user's timezone
1023 $now = time(); // local time
1024 $now -= (int)date('Z'); // make GMT time
1025 $now += ($tz * 3600); // user's time
1026 $now_date = getdate($now);
1028 $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1029 $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1031 // define date format depending on current time
1033 if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1034 $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1037 else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1038 $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1040 $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1043 // strftime() format
1044 if (preg_match('/%[a-z]+/i', $format)) {
1045 $format = strftime($format, $timestamp);
1046 return $today ? (rcube_label('today') . ' ' . $format) : $format;
1049 // parse format string manually in order to provide localized weekday and month names
1050 // an alternative would be to convert the date() format string to fit with strftime()
1052 for($i=0; $i<strlen($format); $i++) {
1053 if ($format{$i}=='\\') // skip escape chars
1056 // write char "as-is"
1057 if ($format{$i}==' ' || $format{$i-1}=='\\')
1058 $out .= $format{$i};
1060 else if ($format{$i}=='D')
1061 $out .= rcube_label(strtolower(date('D', $timestamp)));
1063 else if ($format{$i}=='l')
1064 $out .= rcube_label(strtolower(date('l', $timestamp)));
1065 // month name (short)
1066 else if ($format{$i}=='M')
1067 $out .= rcube_label(strtolower(date('M', $timestamp)));
1068 // month name (long)
1069 else if ($format{$i}=='F')
1070 $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1071 else if ($format{$i}=='x')
1072 $out .= strftime('%x %X', $timestamp);
1074 $out .= date($format{$i}, $timestamp);
1078 $label = rcube_label('today');
1079 // replcae $ character with "Today" label (#1486120)
1080 if (strpos($out, '$') !== false) {
1081 $out = preg_replace('/\$/', $label, $out, 1);
1084 $out = $label . ' ' . $out;
1093 * Compose a valid representation of name and e-mail address
1095 * @param string E-mail address
1096 * @param string Person name
1097 * @return string Formatted string
1099 function format_email_recipient($email, $name='')
1101 if ($name && $name != $email)
1103 // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1104 return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1107 return trim($email);
1112 /****** debugging functions ********/
1116 * Print or write debug messages
1118 * @param mixed Debug message or data
1123 $args = func_get_args();
1125 if (class_exists('rcmail', false)) {
1126 $rcmail = rcmail::get_instance();
1127 if (is_object($rcmail->plugins))
1128 $rcmail->plugins->exec_hook('console', $args);
1132 foreach ($args as $arg)
1133 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1135 if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1136 write_log('console', join(";\n", $msg));
1137 else if ($GLOBALS['OUTPUT']->ajax_call)
1138 print "/*\n " . join(";\n", $msg) . " \n*/\n";
1141 print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1142 print join(";<br/>\n", $msg);
1143 print "</pre></div>\n";
1149 * Append a line to a logfile in the logs directory.
1150 * Date will be added automatically to the line.
1152 * @param $name name of log file
1153 * @param line Line to append
1156 function write_log($name, $line)
1158 global $CONFIG, $RCMAIL;
1160 if (!is_string($line))
1161 $line = var_export($line, true);
1163 if (empty($CONFIG['log_date_format']))
1164 $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1166 $date = date($CONFIG['log_date_format']);
1168 // trigger logging hook
1169 if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1170 $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1171 $name = $log['name'];
1172 $line = $log['line'];
1173 $date = $log['date'];
1178 if ($CONFIG['log_driver'] == 'syslog') {
1179 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1180 syslog($prio, $line);
1184 $line = sprintf("[%s]: %s\n", $date, $line);
1186 // log_driver == 'file' is assumed here
1187 if (empty($CONFIG['log_dir']))
1188 $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1190 // try to open specific log file for writing
1191 $logfile = $CONFIG['log_dir'].'/'.$name;
1192 if ($fp = @fopen($logfile, 'a')) {
1199 trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1206 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1210 function rcmail_log_login()
1214 if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1217 write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1218 $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1223 * Returns remote IP address and forwarded addresses if found
1225 * @return string Remote IP address(es)
1227 function rcmail_remote_ip()
1229 $address = $_SERVER['REMOTE_ADDR'];
1231 // append the NGINX X-Real-IP header, if set
1232 if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1233 $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1235 // append the X-Forwarded-For header, if set
1236 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1237 $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1240 if (!empty($remote_ip))
1241 $address .= '(' . implode(',', $remote_ip) . ')';
1248 * Check whether the HTTP referer matches the current request
1250 * @return boolean True if referer is the same host+path, false if not
1252 function rcube_check_referer()
1254 $uri = parse_url($_SERVER['REQUEST_URI']);
1255 $referer = parse_url(rc_request_header('Referer'));
1256 return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1264 function rcube_timer()
1266 return microtime(true);
1274 function rcube_print_time($timer, $label='Timer', $dest='console')
1276 static $print_count = 0;
1279 $now = rcube_timer();
1280 $diff = $now-$timer;
1283 $label = 'Timer '.$print_count;
1285 write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1290 * Return the mailboxlist in HTML
1292 * @param array Named parameters
1293 * @return string HTML code for the gui object
1295 function rcmail_mailbox_list($attrib)
1298 static $a_mailboxes;
1300 $attrib += array('maxlength' => 100, 'realnames' => false);
1302 // add some labels to client
1303 $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1305 $type = $attrib['type'] ? $attrib['type'] : 'ul';
1306 unset($attrib['type']);
1308 if ($type=='ul' && !$attrib['id'])
1309 $attrib['id'] = 'rcmboxlist';
1312 $mbox_name = $RCMAIL->imap->get_mailbox_name();
1314 // build the folders tree
1315 if (empty($a_mailboxes)) {
1317 $a_folders = $RCMAIL->imap->list_mailboxes();
1318 $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1319 $a_mailboxes = array();
1321 foreach ($a_folders as $folder)
1322 rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1325 // allow plugins to alter the folder tree or to localize folder names
1326 $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1328 if ($type == 'select') {
1329 $select = new html_select($attrib);
1331 // add no-selection option
1332 if ($attrib['noselection'])
1333 $select->add(rcube_label($attrib['noselection']), '');
1335 rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1336 $out = $select->show();
1339 $js_mailboxlist = array();
1340 $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1342 $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1343 $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1344 $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1352 * Return the mailboxlist as html_select object
1354 * @param array Named parameters
1355 * @return html_select HTML drop-down object
1357 function rcmail_mailbox_select($p = array())
1361 $p += array('maxlength' => 100, 'realnames' => false);
1362 $a_mailboxes = array();
1364 if ($p['unsubscribed'])
1365 $list = $RCMAIL->imap->list_unsubscribed();
1367 $list = $RCMAIL->imap->list_mailboxes();
1369 foreach ($list as $folder)
1370 if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1371 rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1373 $select = new html_select($p);
1375 if ($p['noselection'])
1376 $select->add($p['noselection'], '');
1378 rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1385 * Create a hierarchical array of the mailbox list
1389 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1393 $pos = strpos($folder, $delm);
1395 if ($pos !== false) {
1396 $subFolders = substr($folder, $pos+1);
1397 $currentFolder = substr($folder, 0, $pos);
1399 // sometimes folder has a delimiter as the last character
1400 if (!strlen($subFolders))
1402 else if (!isset($arrFolders[$currentFolder]))
1405 $virtual = $arrFolders[$currentFolder]['virtual'];
1408 $subFolders = false;
1409 $currentFolder = $folder;
1413 $path .= $currentFolder;
1415 // Check \Noselect option (if options are in cache)
1416 if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1417 $virtual = in_array('\\Noselect', $opts);
1420 if (!isset($arrFolders[$currentFolder])) {
1421 $arrFolders[$currentFolder] = array(
1423 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1424 'virtual' => $virtual,
1425 'folders' => array());
1428 $arrFolders[$currentFolder]['virtual'] = $virtual;
1430 if (strlen($subFolders))
1431 rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1436 * Return html for a structured list <ul> for the mailbox tree
1440 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1442 global $RCMAIL, $CONFIG;
1444 $maxlength = intval($attrib['maxlength']);
1445 $realnames = (bool)$attrib['realnames'];
1446 $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1450 foreach ($arrFolders as $key => $folder) {
1451 $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1454 if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1455 $foldername = rcube_label($folder_class);
1458 $foldername = $folder['name'];
1460 // shorten the folder name to a given length
1461 if ($maxlength && $maxlength > 1) {
1462 $fname = abbreviate_string($foldername, $maxlength);
1463 if ($fname != $foldername)
1464 $title = $foldername;
1465 $foldername = $fname;
1469 // make folder name safe for ids and class names
1470 $folder_id = asciiwords($folder['id'], true, '_');
1471 $classes = array('mailbox');
1473 // set special class for Sent, Drafts, Trash and Junk
1474 if ($folder['id']==$CONFIG['sent_mbox'])
1475 $classes[] = 'sent';
1476 else if ($folder['id']==$CONFIG['drafts_mbox'])
1477 $classes[] = 'drafts';
1478 else if ($folder['id']==$CONFIG['trash_mbox'])
1479 $classes[] = 'trash';
1480 else if ($folder['id']==$CONFIG['junk_mbox'])
1481 $classes[] = 'junk';
1482 else if ($folder['id']=='INBOX')
1483 $classes[] = 'inbox';
1485 $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1487 $classes[] = $zebra_class;
1489 if ($folder['id'] == $mbox_name)
1490 $classes[] = 'selected';
1492 $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1493 $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1495 if ($folder['virtual'])
1496 $classes[] = 'virtual';
1498 $classes[] = 'unread';
1500 $js_name = JQ($folder['id']);
1501 $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1502 $link_attrib = $folder['virtual'] ? array() : array(
1503 'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1504 'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1508 $out .= html::tag('li', array(
1509 'id' => "rcmli".$folder_id,
1510 'class' => join(' ', $classes),
1512 html::a($link_attrib, $html_name) .
1513 (!empty($folder['folders']) ? html::div(array(
1514 'class' => ($collapsed ? 'collapsed' : 'expanded'),
1515 'style' => "position:absolute",
1516 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1517 ), ' ') : ''));
1519 $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1521 if (!empty($folder['folders'])) {
1522 $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1523 rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1535 * Return html for a flat list <select> for the mailbox tree
1539 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1543 foreach ($arrFolders as $key=>$folder) {
1544 if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1545 $foldername = rcube_label($folder_class);
1547 $foldername = $folder['name'];
1549 // shorten the folder name to a given length
1550 if ($maxlength && $maxlength>1)
1551 $foldername = abbreviate_string($foldername, $maxlength);
1554 $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']);
1556 if (!empty($folder['folders']))
1557 $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1565 * Return internal name for the given folder if it matches the configured special folders
1569 function rcmail_folder_classname($folder_id)
1573 if ($folder_id == 'INBOX')
1576 // for these mailboxes we have localized labels and css classes
1577 foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1579 if ($folder_id == $CONFIG[$smbx.'_mbox'])
1586 * Try to localize the given IMAP folder name.
1587 * UTF-7 decode it in case no localized text was found
1589 * @param string Folder name
1590 * @return string Localized folder name in UTF-8 encoding
1592 function rcmail_localize_foldername($name)
1594 if ($folder_class = rcmail_folder_classname($name))
1595 return rcube_label($folder_class);
1597 return rcube_charset_convert($name, 'UTF7-IMAP');
1601 function rcmail_quota_display($attrib)
1606 $attrib['id'] = 'rcmquotadisplay';
1608 if(isset($attrib['display']))
1609 $_SESSION['quota_display'] = $attrib['display'];
1611 $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1613 $quota = rcmail_quota_content($attrib);
1615 $OUTPUT->add_script('$(document).ready(function(){
1616 rcmail.set_quota('.json_serialize($quota).')});', 'foot');
1618 return html::span($attrib, '');
1622 function rcmail_quota_content($attrib=NULL)
1626 $quota = $RCMAIL->imap->get_quota();
1627 $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1629 $quota_result = (array) $quota;
1630 $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1632 if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1633 $quota_result['title'] = rcube_label('unlimited');
1634 $quota_result['percent'] = 0;
1636 else if ($quota['total']) {
1637 if (!isset($quota['percent']))
1638 $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1640 $title = sprintf('%s / %s (%.0f%%)',
1641 show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1642 $quota_result['percent']);
1644 $quota_result['title'] = $title;
1646 if ($attrib['width'])
1647 $quota_result['width'] = $attrib['width'];
1648 if ($attrib['height'])
1649 $quota_result['height'] = $attrib['height'];
1652 $quota_result['title'] = rcube_label('unknown');
1653 $quota_result['percent'] = 0;
1656 return $quota_result;
1661 * Outputs error message according to server error/response codes
1663 * @param string Fallback message label
1664 * @param string Fallback message label arguments
1668 function rcmail_display_server_error($fallback=null, $fallback_args=null)
1672 $err_code = $RCMAIL->imap->get_error_code();
1673 $res_code = $RCMAIL->imap->get_response_code();
1675 if ($res_code == rcube_imap::NOPERM) {
1676 $RCMAIL->output->show_message('errornoperm', 'error');
1678 else if ($res_code == rcube_imap::READONLY) {
1679 $RCMAIL->output->show_message('errorreadonly', 'error');
1681 else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1682 $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1684 else if ($fallback) {
1685 $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1693 * Output HTML editor scripts
1695 * @param string Editor mode
1698 function rcube_html_editor($mode='')
1700 global $RCMAIL, $CONFIG;
1702 $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1707 $lang = strtolower($_SESSION['language']);
1709 // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1710 $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1712 if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1715 $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1716 $RCMAIL->output->include_script('editor.js');
1717 $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1718 JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1724 * Replaces TinyMCE's emoticon images with plain-text representation
1726 * @param string HTML content
1727 * @return string HTML content
1729 function rcmail_replace_emoticons($html)
1732 '8-)' => 'smiley-cool',
1733 ':-#' => 'smiley-foot-in-mouth',
1734 ':-*' => 'smiley-kiss',
1735 ':-X' => 'smiley-sealed',
1736 ':-P' => 'smiley-tongue-out',
1737 ':-@' => 'smiley-yell',
1738 ":'(" => 'smiley-cry',
1739 ':-(' => 'smiley-frown',
1740 ':-D' => 'smiley-laughing',
1741 ':-)' => 'smiley-smile',
1742 ':-S' => 'smiley-undecided',
1743 ':-$' => 'smiley-embarassed',
1744 'O:-)' => 'smiley-innocent',
1745 ':-|' => 'smiley-money-mouth',
1746 ':-O' => 'smiley-surprised',
1747 ';-)' => 'smiley-wink',
1750 foreach ($emoticons as $idx => $file) {
1751 // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1752 $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1756 return preg_replace($search, $replace, $html);
1761 * Check if working in SSL mode
1763 * @param integer HTTPS port number
1764 * @param boolean Enables 'use_https' option checking
1767 function rcube_https_check($port=null, $use_https=true)
1771 if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1773 if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1775 if ($port && $_SERVER['SERVER_PORT'] == $port)
1777 if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1785 * For backward compatibility.
1787 * @global rcmail $RCMAIL
1788 * @param string $var_name Variable name.
1791 function rcube_sess_unset($var_name=null)
1795 $RCMAIL->session->remove($var_name);
1801 * Replaces hostname variables
1803 * @param string $name Hostname
1804 * @param string $host Optional IMAP hostname
1807 function rcube_parse_host($name, $host='')
1810 $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1811 // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1812 $d = preg_replace('/^[^\.]+\./', '', $n);
1814 $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1815 // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1816 $z = preg_replace('/^[^\.]+\./', '', $h);
1818 $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1824 * E-mail address validation
1826 * @param string $email Email address
1827 * @param boolean $dns_check True to check dns
1830 function check_email($email, $dns_check=true)
1832 // Check for invalid characters
1833 if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1836 // Check for length limit specified by RFC 5321 (#1486453)
1837 if (strlen($email) > 254)
1840 $email_array = explode('@', $email);
1842 // Check that there's one @ symbol
1843 if (count($email_array) < 2)
1846 $domain_part = array_pop($email_array);
1847 $local_part = implode('@', $email_array);
1849 // from PEAR::Validate
1851 ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
1852 ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322)
1855 if (!preg_match($regexp, $local_part))
1858 // Check domain part
1859 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))
1860 return true; // IP address
1862 // If not an IP address
1863 $domain_array = explode('.', $domain_part);
1864 if (sizeof($domain_array) < 2)
1865 return false; // Not enough parts to be a valid domain
1867 foreach ($domain_array as $part)
1868 if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1871 if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1874 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1876 @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1877 foreach ($lookup as $line) {
1878 if (strpos($line, 'MX preference'))
1884 // find MX record(s)
1885 if (getmxrr($domain_part, $mx_records))
1888 // find any DNS record
1889 if (checkdnsrr($domain_part, 'ANY'))
1897 * Idn_to_ascii wrapper.
1898 * Intl/Idn modules version of this function doesn't work with e-mail address
1900 function rcube_idn_to_ascii($str)
1902 return rcube_idn_convert($str, true);
1906 * Idn_to_ascii wrapper.
1907 * Intl/Idn modules version of this function doesn't work with e-mail address
1909 function rcube_idn_to_utf8($str)
1911 return rcube_idn_convert($str, false);
1914 function rcube_idn_convert($input, $is_utf=false)
1916 if ($at = strpos($input, '@')) {
1917 $user = substr($input, 0, $at);
1918 $domain = substr($input, $at+1);
1924 $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1926 if ($domain === false) {
1930 return $at ? $user . '@' . $domain : $domain;
1935 * Helper class to turn relative urls into absolute ones
1936 * using a predefined base
1938 class rcube_base_replacer
1942 public function __construct($base)
1944 $this->base_url = $base;
1947 public function callback($matches)
1949 return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1955 * Throw system error and show error page
1957 * @param array Named parameters
1958 * - code: Error code (required)
1959 * - type: Error type [php|db|imap|javascript] (required)
1960 * - message: Error message
1961 * - file: File where error occured
1962 * - line: Line where error occured
1963 * @param boolean True to log the error
1964 * @param boolean Terminate script execution
1966 // may be defined in Installer
1967 if (!function_exists('raise_error')) {
1968 function raise_error($arg=array(), $log=false, $terminate=false)
1970 global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1972 // report bug (if not incompatible browser)
1973 if ($log && $arg['type'] && $arg['message'])
1976 // display error page and terminate script
1978 $ERROR_CODE = $arg['code'];
1979 $ERROR_MESSAGE = $arg['message'];
1980 include('program/steps/utils/error.inc');
1988 * Report error according to configured debug_level
1990 * @param array Named parameters
1992 * @see raise_error()
1994 function log_bug($arg_arr)
1997 $program = strtoupper($arg_arr['type']);
1999 // write error to local log file
2000 if ($CONFIG['debug_level'] & 1) {
2001 $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2002 $log_entry = sprintf("%s Error: %s%s (%s %s)",
2004 $arg_arr['message'],
2005 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2006 $_SERVER['REQUEST_METHOD'],
2007 $_SERVER['REQUEST_URI'] . $post_query);
2009 if (!write_log('errors', $log_entry)) {
2010 // send error to PHPs error handler if write_log didn't succeed
2011 trigger_error($arg_arr['message']);
2015 // resport the bug to the global bug reporting system
2016 if ($CONFIG['debug_level'] & 2) {
2017 // TODO: Send error via HTTP
2020 // show error if debug_mode is on
2021 if ($CONFIG['debug_level'] & 4) {
2022 print "<b>$program Error";
2024 if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2025 print " in $arg_arr[file] ($arg_arr[line])";
2027 print ':</b> ';
2028 print nl2br($arg_arr['message']);