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 3063 2009-10-27 09:43:39Z 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 table 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);
94 * Overwrite action variable
96 * @param string New action value
98 function rcmail_overwrite_action($action)
100 $app = rcmail::get_instance();
101 $app->action = $action;
102 $app->output->set_env('action', $action);
107 * Compose an URL for a specific action
109 * @param string Request action
110 * @param array More URL parameters
111 * @param string Request task (omit if the same)
112 * @return The application URL
114 function rcmail_url($action, $p=array(), $task=null)
116 $app = rcmail::get_instance();
117 return $app->url((array)$p + array('_action' => $action, 'task' => $task));
122 * Garbage collector function for temp files.
123 * Remove temp files older than two days
125 function rcmail_temp_gc()
127 $rcmail = rcmail::get_instance();
129 $tmp = unslashify($rcmail->config->get('temp_dir'));
130 $expire = mktime() - 172800; // expire in 48 hours
132 if ($dir = opendir($tmp))
134 while (($fname = readdir($dir)) !== false)
136 if ($fname{0} == '.')
139 if (filemtime($tmp.'/'.$fname) < $expire)
140 @unlink($tmp.'/'.$fname);
149 * Garbage collector for cache entries.
150 * Remove all expired message cache records
152 function rcmail_cache_gc()
154 $rcmail = rcmail::get_instance();
155 $db = $rcmail->get_dbh();
157 // get target timestamp
158 $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
160 $db->query("DELETE FROM ".get_table_name('messages')."
161 WHERE created < " . $db->fromunixtime($ts));
163 $db->query("DELETE FROM ".get_table_name('cache')."
164 WHERE created < " . $db->fromunixtime($ts));
169 * Convert a string from one charset to another.
170 * Uses mbstring and iconv functions if possible
172 * @param string Input string
173 * @param string Suspected charset of the input string
174 * @param string Target charset to convert to; defaults to RCMAIL_CHARSET
175 * @return Converted string
177 function rcube_charset_convert($str, $from, $to=NULL)
179 static $mbstring_loaded = null;
180 static $mbstring_list = null;
181 static $convert_warning = false;
186 $to = empty($to) ? $to = strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
187 $from = rcube_parse_charset($from);
189 if ($from == $to || empty($str) || empty($from))
192 // convert charset using iconv module
193 if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
194 $_iconv = iconv($from, $to . '//IGNORE', $str);
195 if ($_iconv !== false) {
200 if (is_null($mbstring_loaded))
201 $mbstring_loaded = extension_loaded('mbstring');
203 // convert charset using mbstring module
204 if ($mbstring_loaded) {
205 $aliases['WINDOWS-1257'] = 'ISO-8859-13';
207 if (is_null($mbstring_list)) {
208 $mbstring_list = mb_list_encodings();
209 $mbstring_list = array_map('strtoupper', $mbstring_list);
212 $mb_from = $aliases[$from] ? $aliases[$from] : $from;
213 $mb_to = $aliases[$to] ? $aliases[$to] : $to;
215 // return if encoding found, string matches encoding and convert succeeded
216 if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
217 if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
222 // convert charset using bundled classes/functions
223 if ($to == 'UTF-8') {
224 if ($from == 'UTF7-IMAP') {
225 if ($_str = utf7_to_utf8($str))
228 else if ($from == 'UTF-7') {
229 if ($_str = rcube_utf7_to_utf8($str))
232 else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
233 return utf8_encode($str);
235 else if (class_exists('utf8')) {
237 $conv = new utf8($from);
239 $conv->loadCharset($from);
241 if($_str = $conv->strToUtf8($str))
247 // encode string for output
248 if ($from == 'UTF-8') {
249 // @TODO: we need a function for UTF-7 (RFC2152) conversion
250 if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
251 if ($_str = utf8_to_utf7($str))
254 else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
255 return utf8_decode($str);
257 else if (class_exists('utf8')) {
259 $conv = new utf8($to);
261 $conv->loadCharset($from);
263 if ($_str = $conv->strToUtf8($str))
270 if ($error && !$convert_warning) {
276 'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
279 $convert_warning = true;
282 // return UTF-8 or original string
288 * Parse and validate charset name string (see #1485758).
289 * Sometimes charset string is malformed, there are also charset aliases
290 * but we need strict names for charset conversion (specially utf8 class)
292 * @param string Input charset name
293 * @return The validated charset name
295 function rcube_parse_charset($charset)
297 $charset = strtoupper($charset);
300 $charset = str_replace('UNICODE-1-1-', '', $charset);
302 # Aliases: some of them from HTML5 spec.
304 'USASCII' => 'WINDOWS-1252',
305 'ANSIX31101983' => 'WINDOWS-1252',
306 'ANSIX341968' => 'WINDOWS-1252',
307 'UNKNOWN8BIT' => 'ISO-8859-15',
308 'UNKNOWN' => 'ISO-8859-15',
309 'USERDEFINED' => 'ISO-8859-15',
310 'KSC56011987' => 'EUC-KR',
313 'UNICODE' => 'UTF-8',
314 'UTF7IMAP' => 'UTF7-IMAP',
315 'TIS620' => 'WINDOWS-874',
316 'ISO88599' => 'WINDOWS-1254',
317 'ISO885911' => 'WINDOWS-874',
320 // allow a-z and 0-9 only and remove X- prefix (e.g. X-ROMAN8 => ROMAN8)
321 $str = preg_replace(array('/[^a-z0-9]/i', '/^x+/i'), '', $charset);
323 if (isset($aliases[$str]))
324 return $aliases[$str];
326 if (preg_match('/UTF(7|8|16|32)(BE|LE)*/', $str, $m))
327 return 'UTF-' . $m[1] . $m[2];
329 if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
330 $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
331 # some clients sends windows-1252 text as latin1,
332 # it is safe to use windows-1252 for all latin1
333 return $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
341 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
343 * @param string Input string
344 * @return The converted string
346 function rcube_utf7_to_utf8($str)
349 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
350 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
351 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
352 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
353 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
354 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
355 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
356 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
359 $u7len = strlen($str);
363 for ($i=0; $u7len > 0; $i++, $u7len--)
372 for (; $u7len > 0; $i++, $u7len--)
376 if (!$Index_64[ord($u7)])
388 $res .= rcube_utf16_to_utf8(base64_decode($ch));
400 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
402 * @param string Input string
403 * @return The converted string
405 function rcube_utf16_to_utf8($str)
410 for ($i = 0; $i < $len; $i += 2) {
411 $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
412 if ($c >= 0x0001 && $c <= 0x007F) {
414 } else if ($c > 0x07FF) {
415 $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
416 $dec .= chr(0x80 | (($c >> 6) & 0x3F));
417 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
419 $dec .= chr(0xC0 | (($c >> 6) & 0x1F));
420 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
428 * Replacing specials characters to a specific encoding type
430 * @param string Input string
431 * @param string Encoding type: text|html|xml|js|url
432 * @param string Replace mode for tags: show|replace|remove
433 * @param boolean Convert newlines
434 * @return The quoted string
436 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
438 static $html_encode_arr = false;
439 static $js_rep_table = false;
440 static $xml_rep_table = false;
443 $enctype = $OUTPUT->type;
445 // encode for HTML output
446 if ($enctype=='html')
448 if (!$html_encode_arr)
450 $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
451 unset($html_encode_arr['?']);
454 $ltpos = strpos($str, '<');
455 $encode_arr = $html_encode_arr;
457 // don't replace quotes and html tags
458 if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
460 unset($encode_arr['"']);
461 unset($encode_arr['<']);
462 unset($encode_arr['>']);
463 unset($encode_arr['&']);
465 else if ($mode=='remove')
466 $str = strip_tags($str);
468 // avoid douple quotation of &
469 $out = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
471 return $newlines ? nl2br($out) : $out;
474 // if the replace tables for XML and JS are not yet defined
475 if ($js_rep_table===false)
477 $js_rep_table = $xml_rep_table = array();
478 $xml_rep_table['&'] = '&';
480 for ($c=160; $c<256; $c++) // can be increased to support more charsets
481 $xml_rep_table[Chr($c)] = "&#$c;";
483 $xml_rep_table['"'] = '"';
484 $js_rep_table['"'] = '\\"';
485 $js_rep_table["'"] = "\\'";
486 $js_rep_table["\\"] = "\\\\";
489 // encode for javascript use
491 return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
493 // encode for plaintext
494 if ($enctype=='text')
495 return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
498 return rawurlencode($str);
502 return strtr($str, $xml_rep_table);
504 // no encoding given -> return original string
509 * Quote a given string.
510 * Shortcut function for rep_specialchars_output
512 * @return string HTML-quoted string
513 * @see rep_specialchars_output()
515 function Q($str, $mode='strict', $newlines=TRUE)
517 return rep_specialchars_output($str, 'html', $mode, $newlines);
521 * Quote a given string for javascript output.
522 * Shortcut function for rep_specialchars_output
524 * @return string JS-quoted string
525 * @see rep_specialchars_output()
529 return rep_specialchars_output($str, 'js');
534 * Read input value and convert it for internal use
535 * Performs stripslashes() and charset conversion if necessary
537 * @param string Field name to read
538 * @param int Source to get value from (GPC)
539 * @param boolean Allow HTML tags in field value
540 * @param string Charset to convert into
541 * @return string Field value or NULL if not available
543 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
548 if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
549 $value = $_GET[$fname];
550 else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
551 $value = $_POST[$fname];
552 else if ($source==RCUBE_INPUT_GPC)
554 if (isset($_POST[$fname]))
555 $value = $_POST[$fname];
556 else if (isset($_GET[$fname]))
557 $value = $_GET[$fname];
558 else if (isset($_COOKIE[$fname]))
559 $value = $_COOKIE[$fname];
565 // strip single quotes if magic_quotes_sybase is enabled
566 if (ini_get('magic_quotes_sybase'))
567 $value = str_replace("''", "'", $value);
568 // strip slashes if magic_quotes enabled
569 else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
570 $value = stripslashes($value);
572 // remove HTML tags if not allowed
574 $value = strip_tags($value);
576 // convert to internal charset
577 if (is_object($OUTPUT))
578 return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
584 * Convert array of request parameters (prefixed with _)
585 * to a regular array with non-prefixed keys.
587 * @param int Source to get value from (GPC)
588 * @return array Hash array with all request parameters
590 function request2param($mode = RCUBE_INPUT_GPC)
593 $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
594 foreach ($src as $key => $value) {
595 $fname = $key[0] == '_' ? substr($key, 1) : $key;
596 $out[$fname] = get_input_value($key, $mode);
603 * Remove all non-ascii and non-word chars
606 function asciiwords($str, $css_id = false, $replace_with = '')
608 $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
609 return preg_replace("/[^$allowed]/i", $replace_with, $str);
613 * Remove single and double quotes from given string
615 * @param string Input value
616 * @return string Dequoted string
618 function strip_quotes($str)
620 return preg_replace('/[\'"]/', '', $str);
625 * Remove new lines characters from given string
627 * @param string Input value
628 * @return string Stripped string
630 function strip_newlines($str)
632 return preg_replace('/[\r\n]/', '', $str);
637 * Create a HTML table based on the given data
639 * @param array Named table attributes
640 * @param mixed Table row data. Either a two-dimensional array or a valid SQL result set
641 * @param array List of cols to show
642 * @param string Name of the identifier col
643 * @return string HTML table code
645 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
649 $table = new html_table(/*array('cols' => count($a_show_cols))*/);
652 foreach ($a_show_cols as $col)
653 $table->add_header($col, Q(rcube_label($col)));
656 if (!is_array($table_data))
658 $db = $RCMAIL->get_dbh();
659 while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
661 $zebra_class = $c % 2 ? 'even' : 'odd';
662 $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
665 foreach ($a_show_cols as $col)
666 $table->add($col, Q($sql_arr[$col]));
673 foreach ($table_data as $row_data)
675 $zebra_class = $c % 2 ? 'even' : 'odd';
676 $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
679 foreach ($a_show_cols as $col)
680 $table->add($col, Q($row_data[$col]));
686 return $table->show($attrib);
691 * Create an edit field for inclusion on a form
693 * @param string col field name
694 * @param string value field value
695 * @param array attrib HTML element attributes for field
696 * @param string type HTML element type (default 'text')
697 * @return string HTML field definition
699 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
702 $attrib['name'] = $fname;
704 if ($type=='checkbox')
706 $attrib['value'] = '1';
707 $input = new html_checkbox($attrib);
709 else if ($type=='textarea')
711 $attrib['cols'] = $attrib['size'];
712 $input = new html_textarea($attrib);
715 $input = new html_inputfield($attrib);
717 // use value from post
718 if (!empty($_POST[$fname]))
719 $value = get_input_value($fname, RCUBE_INPUT_POST,
720 $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
722 $out = $input->show($value);
729 * Replace all css definitions with #container [def]
730 * and remove css-inlined scripting
732 * @param string CSS source code
733 * @param string Container ID to use as prefix
734 * @return string Modified CSS source
736 function rcmail_mod_css_styles($source, $container_id)
739 $replacements = new rcube_string_replacer;
741 // ignore the whole block if evil styles are detected
742 $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
743 if (preg_match('/expression|behavior|url\(|import/', $stripped))
744 return '/* evil! */';
746 // cut out all contents between { and }
747 while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
749 $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
750 $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
754 // remove html comments and add #container to each tag selector.
755 // also replace body definition because we also stripped off the <body> tag
756 $styles = preg_replace(
758 '/(^\s*<!--)|(-->\s*$)/',
759 '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
760 "/$container_id\s+body/i",
764 "\\1#$container_id \\2",
765 "$container_id div.rcmBody",
769 // put block contents back in
770 $styles = $replacements->resolve($styles);
777 * Decode escaped entities used by known XSS exploits.
778 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
780 * @param string CSS content to decode
781 * @return string Decoded string
783 function rcmail_xss_entity_decode($content)
785 $out = html_entity_decode(html_entity_decode($content));
786 $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
787 $out = preg_replace('#/\*.*\*/#Um', '', $out);
793 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
795 * @param array matches result from preg_replace_callback
796 * @return string decoded entity
798 function rcmail_xss_entity_decode_callback($matches)
800 return chr(hexdec($matches[1]));
804 * Compose a valid attribute string for HTML tags
806 * @param array Named tag attributes
807 * @param array List of allowed attributes
808 * @return string HTML formatted attribute string
810 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
812 // allow the following attributes to be added to the <iframe> tag
814 foreach ($allowed_attribs as $a)
815 if (isset($attrib[$a]))
816 $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '"', $attrib[$a]));
823 * Convert a HTML attribute string attributes to an associative array (name => value)
825 * @param string Input string
826 * @return array Key-value pairs of parsed attributes
828 function parse_attrib_string($str)
831 preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
833 // convert attributes to an associative array (name => value)
835 foreach ($regs as $attr) {
836 $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
845 * Convert the given date to a human readable form
846 * This uses the date formatting properties from config
848 * @param mixed Date representation (string or timestamp)
849 * @param string Date format to use
850 * @return string Formatted date string
852 function format_date($date, $format=NULL)
858 if (is_numeric($date))
860 else if (!empty($date))
862 // support non-standard "GMTXXXX" literal
863 $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
864 // if date parsing fails, we have a date in non-rfc format.
865 // remove token from the end and try again
866 while ((($ts = @strtotime($date))===false) || ($ts < 0))
868 $d = explode(' ', $date);
871 $date = implode(' ', $d);
878 // get user's timezone
879 if ($CONFIG['timezone'] === 'auto')
880 $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
882 $tz = $CONFIG['timezone'];
883 if ($CONFIG['dst_active'])
887 // convert time to user's timezone
888 $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
890 // get current timestamp in user's timezone
891 $now = time(); // local time
892 $now -= (int)date('Z'); // make GMT time
893 $now += ($tz * 3600); // user's time
894 $now_date = getdate($now);
896 $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
897 $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
899 // define date format depending on current time
900 if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit && $timestamp < $now)
901 return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
902 else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit && $timestamp < $now)
903 $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
905 $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
908 if (preg_match('/%[a-z]+/i', $format))
909 return strftime($format, $timestamp);
911 // parse format string manually in order to provide localized weekday and month names
912 // an alternative would be to convert the date() format string to fit with strftime()
914 for($i=0; $i<strlen($format); $i++)
916 if ($format{$i}=='\\') // skip escape chars
919 // write char "as-is"
920 if ($format{$i}==' ' || $format{$i-1}=='\\')
923 else if ($format{$i}=='D')
924 $out .= rcube_label(strtolower(date('D', $timestamp)));
926 else if ($format{$i}=='l')
927 $out .= rcube_label(strtolower(date('l', $timestamp)));
928 // month name (short)
929 else if ($format{$i}=='M')
930 $out .= rcube_label(strtolower(date('M', $timestamp)));
932 else if ($format{$i}=='F')
933 $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
934 else if ($format{$i}=='x')
935 $out .= strftime('%x %X', $timestamp);
937 $out .= date($format{$i}, $timestamp);
945 * Compose a valid representation of name and e-mail address
947 * @param string E-mail address
948 * @param string Person name
949 * @return string Formatted string
951 function format_email_recipient($email, $name='')
953 if ($name && $name != $email)
955 // Special chars as defined by RFC 822 need to in quoted string (or escaped).
956 return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
964 /****** debugging functions ********/
968 * Print or write debug messages
970 * @param mixed Debug message or data
974 $args = func_get_args();
976 if (class_exists('rcmail', false)) {
977 $rcmail = rcmail::get_instance();
978 if (is_object($rcmail->plugins))
979 $rcmail->plugins->exec_hook('console', $args);
983 foreach ($args as $arg)
984 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
986 if (!($GLOBALS['CONFIG']['debug_level'] & 4))
987 write_log('console', join(";\n", $msg));
988 else if ($GLOBALS['OUTPUT']->ajax_call)
989 print "/*\n " . join(";\n", $msg) . " \n*/\n";
992 print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
993 print join(";<br/>\n", $msg);
994 print "</pre></div>\n";
1000 * Append a line to a logfile in the logs directory.
1001 * Date will be added automatically to the line.
1003 * @param $name name of log file
1004 * @param line Line to append
1006 function write_log($name, $line)
1008 global $CONFIG, $RCMAIL;
1010 if (!is_string($line))
1011 $line = var_export($line, true);
1013 if (empty($CONFIG['log_date_format']))
1014 $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1016 $date = date($CONFIG['log_date_format']);
1018 // trigger logging hook
1019 if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1020 $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1021 $name = $log['name'];
1022 $line = $log['line'];
1023 $date = $log['date'];
1028 $log_entry = sprintf("[%s]: %s\n", $date, $line);
1030 if ($CONFIG['log_driver'] == 'syslog') {
1031 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1032 syslog($prio, $log_entry);
1036 // log_driver == 'file' is assumed here
1037 if (empty($CONFIG['log_dir']))
1038 $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1040 // try to open specific log file for writing
1041 $logfile = $CONFIG['log_dir'].'/'.$name;
1042 if ($fp = @fopen($logfile, 'a')) {
1043 fwrite($fp, $log_entry);
1049 trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1058 function rcube_timer()
1060 return microtime(true);
1067 function rcube_print_time($timer, $label='Timer', $dest='console')
1069 static $print_count = 0;
1072 $now = rcube_timer();
1073 $diff = $now-$timer;
1076 $label = 'Timer '.$print_count;
1078 write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1083 * Return the mailboxlist in HTML
1085 * @param array Named parameters
1086 * @return string HTML code for the gui object
1088 function rcmail_mailbox_list($attrib)
1091 static $a_mailboxes;
1093 $attrib += array('maxlength' => 100, 'relanames' => false);
1095 // add some labels to client
1096 $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1098 $type = $attrib['type'] ? $attrib['type'] : 'ul';
1099 unset($attrib['type']);
1101 if ($type=='ul' && !$attrib['id'])
1102 $attrib['id'] = 'rcmboxlist';
1105 $mbox_name = $RCMAIL->imap->get_mailbox_name();
1107 // build the folders tree
1108 if (empty($a_mailboxes)) {
1110 $a_folders = $RCMAIL->imap->list_mailboxes();
1111 $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1112 $a_mailboxes = array();
1114 foreach ($a_folders as $folder)
1115 rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1118 // allow plugins to alter the folder tree or to localize folder names
1119 $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1121 if ($type=='select') {
1122 $select = new html_select($attrib);
1124 // add no-selection option
1125 if ($attrib['noselection'])
1126 $select->add(rcube_label($attrib['noselection']), '0');
1128 rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1129 $out = $select->show();
1132 $js_mailboxlist = array();
1133 $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1135 $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1136 $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1137 $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1145 * Return the mailboxlist as html_select object
1147 * @param array Named parameters
1148 * @return object html_select HTML drop-down object
1150 function rcmail_mailbox_select($p = array())
1154 $p += array('maxlength' => 100, 'realnames' => false);
1155 $a_mailboxes = array();
1157 foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1158 if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1159 rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1161 $select = new html_select($p);
1163 if ($p['noselection'])
1164 $select->add($p['noselection'], '');
1166 rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1173 * Create a hierarchical array of the mailbox list
1176 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1178 $pos = strpos($folder, $delm);
1179 if ($pos !== false) {
1180 $subFolders = substr($folder, $pos+1);
1181 $currentFolder = substr($folder, 0, $pos);
1182 $virtual = !isset($arrFolders[$currentFolder]);
1185 $subFolders = false;
1186 $currentFolder = $folder;
1190 $path .= $currentFolder;
1192 if (!isset($arrFolders[$currentFolder])) {
1193 $arrFolders[$currentFolder] = array(
1195 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1196 'virtual' => $virtual,
1197 'folders' => array());
1200 $arrFolders[$currentFolder]['virtual'] = $virtual;
1202 if (!empty($subFolders))
1203 rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1208 * Return html for a structured list <ul> for the mailbox tree
1211 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1213 global $RCMAIL, $CONFIG;
1215 $maxlength = intval($attrib['maxlength']);
1216 $realnames = (bool)$attrib['realnames'];
1217 $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1221 foreach ($arrFolders as $key => $folder) {
1222 $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1225 if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1226 $foldername = rcube_label($folder_class);
1229 $foldername = $folder['name'];
1231 // shorten the folder name to a given length
1232 if ($maxlength && $maxlength > 1) {
1233 $fname = abbreviate_string($foldername, $maxlength);
1234 if ($fname != $foldername)
1235 $title = $foldername;
1236 $foldername = $fname;
1240 // make folder name safe for ids and class names
1241 $folder_id = asciiwords($folder['id'], true, '_');
1242 $classes = array('mailbox');
1244 // set special class for Sent, Drafts, Trash and Junk
1245 if ($folder['id']==$CONFIG['sent_mbox'])
1246 $classes[] = 'sent';
1247 else if ($folder['id']==$CONFIG['drafts_mbox'])
1248 $classes[] = 'drafts';
1249 else if ($folder['id']==$CONFIG['trash_mbox'])
1250 $classes[] = 'trash';
1251 else if ($folder['id']==$CONFIG['junk_mbox'])
1252 $classes[] = 'junk';
1253 else if ($folder['id']=='INBOX')
1254 $classes[] = 'inbox';
1256 $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1258 $classes[] = $zebra_class;
1260 if ($folder['id'] == $mbox_name)
1261 $classes[] = 'selected';
1263 $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1264 $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1266 if ($folder['virtual'])
1267 $classes[] = 'virtual';
1269 $classes[] = 'unread';
1271 $js_name = JQ($folder['id']);
1272 $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1273 $link_attrib = $folder['virtual'] ? array() : array(
1274 'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1275 'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1279 $out .= html::tag('li', array(
1280 'id' => "rcmli".$folder_id,
1281 'class' => join(' ', $classes),
1283 html::a($link_attrib, $html_name) .
1284 (!empty($folder['folders']) ? html::div(array(
1285 'class' => ($collapsed ? 'collapsed' : 'expanded'),
1286 'style' => "position:absolute",
1287 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1288 ), ' ') : ''));
1290 $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1292 if (!empty($folder['folders'])) {
1293 $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1294 rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1306 * Return html for a flat list <select> for the mailbox tree
1309 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1313 foreach ($arrFolders as $key=>$folder)
1315 if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1316 $foldername = rcube_label($folder_class);
1319 $foldername = $folder['name'];
1321 // shorten the folder name to a given length
1322 if ($maxlength && $maxlength>1)
1323 $foldername = abbreviate_string($foldername, $maxlength);
1326 $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']);
1328 if (!empty($folder['folders']))
1329 $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1339 * Return internal name for the given folder if it matches the configured special folders
1342 function rcmail_folder_classname($folder_id)
1346 // for these mailboxes we have localized labels and css classes
1347 foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1349 if ($folder_id == $CONFIG[$smbx.'_mbox'])
1353 if ($folder_id == 'INBOX')
1359 * Try to localize the given IMAP folder name.
1360 * UTF-7 decode it in case no localized text was found
1362 * @param string Folder name
1363 * @return string Localized folder name in UTF-8 encoding
1365 function rcmail_localize_foldername($name)
1367 if ($folder_class = rcmail_folder_classname($name))
1368 return rcube_label($folder_class);
1370 return rcube_charset_convert($name, 'UTF7-IMAP');
1375 * Output HTML editor scripts
1377 * @param string Editor mode
1379 function rcube_html_editor($mode='')
1381 global $RCMAIL, $CONFIG;
1383 $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1388 $lang = strtolower(substr($_SESSION['language'], 0, 2));
1389 if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1392 $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1393 $RCMAIL->output->include_script('editor.js');
1394 $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
1395 "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1400 * Check if working in SSL mode
1402 * @param integer HTTPS port number
1403 * @param boolean Enables 'use_https' option checking
1405 function rcube_https_check($port=null, $use_https=true)
1409 if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
1411 if ($port && $_SERVER['SERVER_PORT'] == $port)
1413 if ($use_https && $RCMAIL->config->get('use_https'))
1421 * E-mail address validation
1423 function check_email($email)
1425 // Check for invalid characters
1426 if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1429 // Check that there's one @ symbol, and that the lengths are right
1430 if (!preg_match('/^([^@]{1,64})@([^@]{1,255})$/', $email, $email_array))
1434 $local_array = explode('.', $email_array[1]);
1435 foreach ($local_array as $local_part)
1436 if (!preg_match('/^(([A-Za-z0-9!#$%&\'*+\/=?^_`{|}~-]+)|("[^"]+"))$/', $local_part))
1439 // Check domain part
1440 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}$/', $email_array[2])
1441 || 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}\]$/', $email_array[2]))
1442 return true; // If an IP address
1444 // If not an IP address
1445 $domain_array = explode('.', $email_array[2]);
1446 if (sizeof($domain_array) < 2)
1447 return false; // Not enough parts to be a valid domain
1449 foreach ($domain_array as $domain_part)
1450 if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $domain_part))
1453 if (!rcmail::get_instance()->config->get('email_dns_check'))
1456 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<'))
1459 // find MX record(s)
1460 if (getmxrr($email_array[2], $mx_records))
1463 // find any DNS record
1464 if (checkdnsrr($email_array[2], 'ANY'))
1473 * Helper class to turn relative urls into absolute ones
1474 * using a predefined base
1476 class rcube_base_replacer
1480 public function __construct($base)
1482 $this->base_url = $base;
1485 public function callback($matches)
1487 return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';